<?php

declare(strict_types=1);

use Builder\Core\Foundation\Definition\Field;
use Builder\Core\Foundation\Definition\FieldBuilder;
use Builder\Core\Foundation\Definition\Hook;
use Builder\Core\Foundation\Definition\Section;
use Builder\Core\Foundation\Resources\Extension;
use Builder\Core\Foundation\Resources\Resource;
use Builder\Core\Foundation\Resources\ResourceDefinition;
use Builder\Core\Foundation\Tables\BulkAction;
use Builder\Core\Foundation\Tables\RowAction;
use Illuminate\Database\Eloquent\Model;

final class DefinitionResource extends Resource
{
    public static function getSlug(): string
    {
        return 'articles';
    }

    public static function getLabel(): string
    {
        return 'Articles';
    }

    /** @return class-string<Model> */
    public static function getModel(): string
    {
        return Model::class;
    }

    public function fields(): FieldBuilder
    {
        return FieldBuilder::make()->add(
            Section::make('content')->label('Content')->priority(0)->fields([
                Field::make('title'),
            ]),
        );
    }

    /** @return list<string> */
    public function eagerLoad(): array
    {
        return ['author'];
    }

    /** @return array<string, mixed> */
    public function rules(string $action): array
    {
        return ['title' => 'required'];
    }

    /** @return list<Hook> */
    public function hooks(): array
    {
        return [Hook::on('saving', function (mixed $model, array $data): void {})];
    }
}

final class NoTimestampsResource extends Resource
{
    public static function getSlug(): string
    {
        return 'no-timestamps';
    }

    public static function getLabel(): string
    {
        return 'No Timestamps';
    }

    /** @return class-string<Model> */
    public static function getModel(): string
    {
        return Model::class;
    }

    public function showTimestamps(): bool
    {
        return false;
    }
}

final class DefinitionExtension extends Extension
{
    /** @return class-string<resource> */
    public static function getExtends(): string
    {
        return DefinitionResource::class;
    }

    public function fields(): FieldBuilder
    {
        return FieldBuilder::make()->add(
            Section::make('seo')->label('SEO')->priority(10)->fields([
                Field::make('meta_title'),
            ]),
        );
    }

    /** @return list<string> */
    public function eagerLoad(): array
    {
        return ['seoMeta'];
    }

    /** @return array<string, mixed> */
    public function rules(string $action): array
    {
        return ['meta_title' => 'nullable|string'];
    }

    /** @return list<Hook> */
    public function hooks(): array
    {
        return [Hook::on('saved', function (mixed $model, array $data): void {})];
    }
}

it('make builds from resource only', function (): void {
    $def = ResourceDefinition::make(new DefinitionResource);

    expect($def->getSlug())->toBe('articles')
        ->and($def->getLabel())->toBe('Articles')
        ->and($def->getModel())->toBe(Model::class)
        ->and($def->getSections())->toHaveCount(1)
        ->and($def->getAllFields())->toHaveCount(1)
        ->and($def->getEagerLoad())->toBe(['author'])
        ->and($def->getRules())->toBe(['title' => 'required'])
        ->and($def->getHooks())->toHaveCount(1);
});

it('make merges extension sections, fields, eagerLoad, rules and hooks', function (): void {
    $def = ResourceDefinition::make(new DefinitionResource, [new DefinitionExtension]);

    expect($def->getSections())->toHaveCount(2)
        ->and($def->getAllFields())->toHaveCount(2)
        ->and($def->getEagerLoad())->toBe(['author', 'seoMeta'])
        ->and($def->getRules())->toHaveKey('title')
        ->and($def->getRules())->toHaveKey('meta_title')
        ->and($def->getHooks())->toHaveCount(2);
});

it('sections are sorted by priority', function (): void {
    $def = ResourceDefinition::make(new DefinitionResource, [new DefinitionExtension]);
    $sections = $def->getSections();

    expect($sections[0]->getPriority())->toBe(0)
        ->and($sections[1]->getPriority())->toBe(10);
});

it('toArray returns slug, label, sections', function (): void {
    $def = ResourceDefinition::make(new DefinitionResource);
    $array = $def->toArray();

    expect($array)->toHaveKeys(['slug', 'label', 'sections'])
        ->and($array['slug'])->toBe('articles')
        ->and($array['label'])->toBe('Articles');
});

it('showTimestamps defaults to true', function (): void {
    $def = ResourceDefinition::make(new DefinitionResource);

    expect($def->toArray()['showTimestamps'])->toBeTrue();
});

it('toArray includes showTimestamps', function (): void {
    $array = ResourceDefinition::make(new DefinitionResource)->toArray();

    expect($array)->toHaveKey('showTimestamps')
        ->and($array['showTimestamps'])->toBeTrue();
});

it('showTimestamps override is carried through', function (): void {
    $array = ResourceDefinition::make(new NoTimestampsResource)->toArray();

    expect($array['showTimestamps'])->toBeFalse();
});

it('getBulkActions returns empty array by default', function (): void {
    $def = ResourceDefinition::make(new DefinitionResource);

    expect($def->getBulkActions())->toBe([]);
});

it('getBulkActions returns bulk actions from resource', function (): void {
    $resource = new class extends Resource
    {
        public static function getSlug(): string
        {
            return 'with-bulk';
        }

        public static function getLabel(): string
        {
            return 'With Bulk';
        }

        /** @return class-string<Model> */
        public static function getModel(): string
        {
            return Model::class;
        }

        /** @return list<BulkAction> */
        public function bulkActions(): array
        {
            return [BulkAction::make('delete')];
        }
    };
    $def = ResourceDefinition::make($resource);

    expect($def->getBulkActions())->toHaveCount(1)
        ->and($def->getBulkActions()[0]->getKey())->toBe('delete');
});

it('getActionPermissions returns empty array by default', function (): void {
    $def = ResourceDefinition::make(new DefinitionResource);

    expect($def->getActionPermissions())->toBe([]);
});

it('getRowActions returns empty array by default', function (): void {
    $def = ResourceDefinition::make(new DefinitionResource);

    expect($def->getRowActions())->toBe([]);
});

it('getRowActions returns row actions from resource', function (): void {
    $resource = new class extends Resource
    {
        public static function getSlug(): string
        {
            return 'with-row-actions';
        }

        public static function getLabel(): string
        {
            return 'With Row Actions';
        }

        /** @return class-string<Model> */
        public static function getModel(): string
        {
            return Model::class;
        }

        /** @return list<RowAction> */
        public function rowActions(): array
        {
            return [RowAction::make('edit')->url('/edit')];
        }
    };
    $def = ResourceDefinition::make($resource);

    expect($def->getRowActions())->toHaveCount(1)
        ->and($def->getRowActions()[0]->getKey())->toBe('edit');
});

it('toArray hasCustomRowActions is false when no row actions', function (): void {
    $array = ResourceDefinition::make(new DefinitionResource)->toArray();

    expect($array)->toHaveKey('hasCustomRowActions')
        ->and($array['hasCustomRowActions'])->toBeFalse();
});

it('toArray hasCustomRowActions is true when resource has row actions', function (): void {
    $resource = new class extends Resource
    {
        public static function getSlug(): string
        {
            return 'has-row-actions';
        }

        public static function getLabel(): string
        {
            return 'Has Row Actions';
        }

        /** @return class-string<Model> */
        public static function getModel(): string
        {
            return Model::class;
        }

        /** @return list<RowAction> */
        public function rowActions(): array
        {
            return [RowAction::make('delete')->destructive()];
        }
    };

    $array = ResourceDefinition::make($resource)->toArray();

    expect($array['hasCustomRowActions'])->toBeTrue();
});

it('isHierarchical defaults to false in toArray', function (): void {
    $array = ResourceDefinition::make(new DefinitionResource)->toArray();

    expect($array)->toHaveKey('isHierarchical')
        ->and($array['isHierarchical'])->toBeFalse();
});

it('isPublic defaults to false in toArray', function (): void {
    $array = ResourceDefinition::make(new DefinitionResource)->toArray();

    expect($array)->toHaveKey('isPublic')
        ->and($array['isPublic'])->toBeFalse();
});

it('parentField defaults to parent_id in toArray', function (): void {
    $array = ResourceDefinition::make(new DefinitionResource)->toArray();

    expect($array)->toHaveKey('parentField')
        ->and($array['parentField'])->toBe('parent_id');
});

it('publicUrlBase defaults to empty string in toArray', function (): void {
    $array = ResourceDefinition::make(new DefinitionResource)->toArray();

    expect($array)->toHaveKey('publicUrlBase')
        ->and($array['publicUrlBase'])->toBe('');
});

it('isHierarchical and isPublic are reflected from resource', function (): void {
    $resource = new class extends Resource
    {
        public static function getSlug(): string
        {
            return 'hierarchical-public';
        }

        public static function getLabel(): string
        {
            return 'Hierarchical Public';
        }

        /** @return class-string<Model> */
        public static function getModel(): string
        {
            return Model::class;
        }

        public function isHierarchical(): bool
        {
            return true;
        }

        public function isPublic(): bool
        {
            return true;
        }

        public function getPublicUrlBase(): string
        {
            return '/blog';
        }

        public function getParentField(): string
        {
            return 'category_id';
        }
    };

    $def = ResourceDefinition::make($resource);
    $array = $def->toArray();

    expect($def->isHierarchical())->toBeTrue()
        ->and($def->isPublic())->toBeTrue()
        ->and($def->getParentField())->toBe('category_id')
        ->and($def->getPublicUrlBase())->toBe('/blog')
        ->and($array['isHierarchical'])->toBeTrue()
        ->and($array['isPublic'])->toBeTrue()
        ->and($array['parentField'])->toBe('category_id')
        ->and($array['publicUrlBase'])->toBe('/blog');
});

it('make merges row actions from extensions', function (): void {
    $resource = new class extends Resource
    {
        public static function getSlug(): string
        {
            return 'ext-row-actions';
        }

        public static function getLabel(): string
        {
            return 'Ext Row Actions';
        }

        /** @return class-string<Model> */
        public static function getModel(): string
        {
            return Model::class;
        }

        /** @return list<RowAction> */
        public function rowActions(): array
        {
            return [RowAction::make('edit')];
        }
    };

    $extension = new class extends Extension
    {
        /** @return class-string<resource> */
        public static function getExtends(): string
        {
            return Resource::class;
        }

        /** @return list<RowAction> */
        public function rowActions(): array
        {
            return [RowAction::make('delete')];
        }
    };

    $def = ResourceDefinition::make($resource, [$extension]);

    expect($def->getRowActions())->toHaveCount(2);
});

it('isPublishable() returns false for non-publishable resource', function (): void {
    $resource = new class extends Resource
    {
        public static function getSlug(): string
        {
            return 'test';
        }

        public static function getLabel(): string
        {
            return 'Test';
        }

        /** @return class-string */
        public static function getModel(): string
        {
            return Model::class;
        }
    };

    $def = ResourceDefinition::make($resource);

    expect($def->isPublishable())->toBeFalse();
});

it('getFieldsByComponent returns fields matching the given component', function (): void {
    $resource = new class extends Resource
    {
        public static function getSlug(): string
        {
            return 'test';
        }

        public static function getLabel(): string
        {
            return 'Test';
        }

        /** @return class-string<Model> */
        public static function getModel(): string
        {
            return Model::class;
        }

        public function fields(): FieldBuilder
        {
            return FieldBuilder::make()->add(
                Section::make('main')->label('Main')->priority(0)->fields([
                    Field::text('title'),
                    Field::email('email'),
                    Field::text('name'),
                ]),
            );
        }
    };

    $def = ResourceDefinition::make($resource);
    $textFields = $def->getFieldsByComponent('TextInput');

    expect($textFields)->toHaveCount(2)
        ->and($textFields[0]->getName())->toBe('title')
        ->and($textFields[1]->getName())->toBe('name');
});

it('getFieldsByComponent returns empty array when no fields match', function (): void {
    $def = ResourceDefinition::make(new DefinitionResource);

    expect($def->getFieldsByComponent('FieldBlockEditor'))->toBe([]);
});

it('getFieldDefinitionsByComponent returns keyed definitions', function (): void {
    $resource = new class extends Resource
    {
        public static function getSlug(): string
        {
            return 'test';
        }

        public static function getLabel(): string
        {
            return 'Test';
        }

        /** @return class-string<Model> */
        public static function getModel(): string
        {
            return Model::class;
        }

        public function fields(): FieldBuilder
        {
            return FieldBuilder::make()->add(
                Section::make('main')->label('Main')->priority(0)->fields([
                    Field::text('title'),
                    Field::email('email'),
                ]),
            );
        }
    };

    $def = ResourceDefinition::make($resource);
    $defs = $def->getFieldDefinitionsByComponent('TextInput');

    expect($defs)->toHaveKey('title')
        ->and($defs)->not->toHaveKey('email')
        ->and($defs['title'])->toBeArray();
});

it('getFieldDefinitionsByComponent returns empty array when no fields match', function (): void {
    $def = ResourceDefinition::make(new DefinitionResource);

    expect($def->getFieldDefinitionsByComponent('FieldMediaInput'))->toBe([]);
});
