<?php

declare(strict_types=1);

namespace Builder\Core\Foundation\Resources;

use Builder\Core\Foundation\Definition\Field;
use Builder\Core\Foundation\Definition\Hook;
use Builder\Core\Foundation\Definition\Section;
use Builder\Core\Foundation\Tables\BulkAction;
use Builder\Core\Foundation\Tables\Column;
use Builder\Core\Foundation\Tables\Filter;
use Builder\Core\Foundation\Tables\RowAction;
use Illuminate\Database\Eloquent\Model;

final class ResourceDefinition
{
    /**
     * @param  class-string<Model>  $model
     * @param  list<Section>  $sections
     * @param  list<string>  $eagerLoad
     * @param  array<string, mixed>  $rules
     * @param  array<string, mixed>  $createRules
     * @param  array<string, mixed>  $updateRules
     * @param  list<Hook>  $hooks
     * @param  list<Column>  $columns
     * @param  list<Filter>  $filters
     * @param  list<BulkAction>  $bulkActions
     * @param  list<RowAction>  $rowActions
     * @param  list<string>  $searchable
     * @param  array<string, string>  $actionPermissions
     * @param  list<string>  $permissionSet
     * @param  list<RecordAction>  $recordActions
     */
    private function __construct(
        private readonly string $slug,
        private readonly string $label,
        private readonly string $singularLabel,
        private readonly string $model,
        private readonly array $sections,
        private readonly array $eagerLoad,
        private readonly array $rules,
        private readonly array $createRules,
        private readonly array $updateRules,
        private readonly array $hooks,
        private readonly array $columns,
        private readonly array $filters,
        private readonly array $bulkActions,
        private readonly array $rowActions,
        private readonly array $searchable,
        private readonly ?string $permission,
        private readonly array $actionPermissions,
        private readonly array $permissionSet,
        private readonly bool $showTimestamps,
        private readonly array $recordActions,
        private readonly bool $duplicatable,
        private readonly Resource $resource,
        private readonly bool $isHierarchical,
        private readonly string $parentField,
        private readonly bool $isPublic,
        private readonly string $publicUrlBase,
        private readonly bool $isRevisable,
        private readonly int $maxRevisions,
    ) {}

    /** @var list<Field>|null */
    private ?array $allFieldsCache = null;

    /** @param list<Extension> $extensions */
    public static function make(Resource $resource, array $extensions = []): self
    {
        $sections = $resource->fields()->toSections();
        $eagerLoad = $resource->eagerLoad();
        $rules = $resource->rules('any');
        $createRules = $resource->rules('create');
        $updateRules = $resource->rules('update');
        $hooks = $resource->hooks();
        $columns = $resource->columns();
        $filters = $resource->filters();
        $bulkActions = $resource->bulkActions();
        $rowActions = $resource->rowActions();
        $searchable = $resource->searchable();

        foreach ($extensions as $extension) {
            $sections = array_merge($sections, $extension->fields()->toSections());
            $eagerLoad = array_merge($eagerLoad, $extension->eagerLoad());
            $rules = array_merge($rules, $extension->rules('any'));
            $createRules = array_merge($createRules, $extension->rules('create'));
            $updateRules = array_merge($updateRules, $extension->rules('update'));
            $hooks = array_merge($hooks, $extension->hooks());
            $columns = array_merge($columns, $extension->columns());
            $filters = array_merge($filters, $extension->filters());
            $bulkActions = array_merge($bulkActions, $extension->bulkActions());
            $rowActions = array_merge($rowActions, $extension->rowActions());
        }

        usort($sections, fn (Section $a, Section $b): int => $a->getPriority() <=> $b->getPriority());

        return new self(
            slug: $resource::getSlug(),
            label: $resource::getLabel(),
            singularLabel: $resource::getSingularLabel(),
            model: $resource::getModel(),
            sections: $sections,
            eagerLoad: $eagerLoad,
            rules: $rules,
            createRules: $createRules,
            updateRules: $updateRules,
            hooks: $hooks,
            columns: $columns,
            filters: $filters,
            bulkActions: $bulkActions,
            rowActions: $rowActions,
            searchable: $searchable,
            permission: $resource->getPermission(),
            actionPermissions: $resource->permissions(),
            permissionSet: $resource->getPermissionSet(),
            showTimestamps: $resource->showTimestamps(),
            recordActions: $resource->recordActions(),
            duplicatable: $resource->isDuplicatable(),
            resource: $resource,
            isHierarchical: $resource->isHierarchical(),
            parentField: $resource->getParentField(),
            isPublic: $resource->isPublic(),
            publicUrlBase: $resource->getPublicUrlBase(),
            isRevisable: $resource->isRevisable(),
            maxRevisions: $resource->getMaxRevisions(),
        );
    }

    public function getSlug(): string
    {
        return $this->slug;
    }

    public function getLabel(): string
    {
        return $this->label;
    }

    public function getSingularLabel(): string
    {
        return $this->singularLabel;
    }

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

    /** @return list<Section> */
    public function getSections(): array
    {
        return $this->sections;
    }

    /** @return list<string> */
    public function getEagerLoad(): array
    {
        return $this->eagerLoad;
    }

    /** @return array<string, mixed> */
    public function getRules(): array
    {
        return $this->rules;
    }

    /**
     * Returns validation rules for a specific action ('create' or 'update').
     * Falls back to the default 'any' rules for unknown actions.
     *
     * @return array<string, mixed>
     */
    public function getRulesForAction(string $action): array
    {
        return match ($action) {
            'create' => $this->createRules,
            'update' => $this->updateRules,
            default => $this->rules,
        };
    }

    /** @return list<Hook> */
    public function getHooks(): array
    {
        return $this->hooks;
    }

    /** @return list<Column> */
    public function getColumns(): array
    {
        return $this->columns;
    }

    /** @return list<Filter> */
    public function getFilters(): array
    {
        return $this->filters;
    }

    /** @return list<BulkAction> */
    public function getBulkActions(): array
    {
        return $this->bulkActions;
    }

    /** @return list<RowAction> */
    public function getRowActions(): array
    {
        return $this->rowActions;
    }

    /** @return list<RecordAction> */
    public function getRecordActions(): array
    {
        return $this->recordActions;
    }

    /** @return list<string> */
    public function getSearchable(): array
    {
        return $this->searchable;
    }

    public function getPermission(): ?string
    {
        return $this->permission;
    }

    /** @return array<string, string> */
    public function getActionPermissions(): array
    {
        return $this->actionPermissions;
    }

    /** @return list<string> */
    public function getPermissionSet(): array
    {
        return $this->permissionSet;
    }

    /**
     * Returns all fields from all sections combined.
     *
     * @return list<Field>
     */
    public function getAllFields(): array
    {
        if ($this->allFieldsCache !== null) {
            return $this->allFieldsCache;
        }

        $fields = [];
        foreach ($this->sections as $section) {
            $fields = array_merge($fields, $section->getFields());
        }

        return $this->allFieldsCache = $fields;
    }

    /**
     * Returns all fields whose component matches the given component name.
     *
     * @return list<Field>
     */
    public function getFieldsByComponent(string $component): array
    {
        return array_values(array_filter(
            $this->getAllFields(),
            fn (Field $field): bool => $field->getComponent() === $component,
        ));
    }

    /**
     * Returns field definitions for all fields matching a component, keyed by field name.
     *
     * @return array<string, array<string, mixed>>
     */
    public function getFieldDefinitionsByComponent(string $component): array
    {
        $result = [];

        foreach ($this->getFieldsByComponent($component) as $field) {
            $result[$field->getName()] = $field->toArray();
        }

        return $result;
    }

    /** @return list<string> */
    public function getTranslatableFieldNames(): array
    {
        return array_values(array_map(
            fn (Field $field): string => $field->getName(),
            array_filter(
                $this->getAllFields(),
                fn (Field $field): bool => $field->isTranslatable() && ! $field->getMeta('virtual', false),
            ),
        ));
    }

    /** @return list<string> */
    public function getVirtualFieldNames(): array
    {
        return array_values(array_map(
            fn (Field $field): string => $field->getName(),
            array_filter(
                $this->getAllFields(),
                fn (Field $field): bool => $field->getMeta('virtual') === true,
            ),
        ));
    }

    /**
     * Resolves the permission required for the given controller action.
     */
    public function resolvePermissionForAction(string $action): ?string
    {
        if (array_key_exists($action, $this->actionPermissions)) {
            return $this->actionPermissions[$action] ?: null;
        }

        return $this->permission;
    }

    public function isDuplicatable(): bool
    {
        return $this->duplicatable;
    }

    public function isHierarchical(): bool
    {
        return $this->isHierarchical;
    }

    public function getParentField(): string
    {
        return $this->parentField;
    }

    public function isPublic(): bool
    {
        return $this->isPublic;
    }

    public function getPublicUrlBase(): string
    {
        return $this->publicUrlBase;
    }

    public function isRevisable(): bool
    {
        return $this->isRevisable;
    }

    public function getMaxRevisions(): int
    {
        return $this->maxRevisions;
    }

    public function isPublishable(): bool
    {
        return $this->resource->isPublishable();
    }

    public function canDelete(mixed $record): void
    {
        $this->resource->canDelete($record);
    }

    public function getResource(): Resource
    {
        return $this->resource;
    }

    /** @return array<string, mixed> */
    public function toArray(): array
    {
        return [
            'slug' => $this->slug,
            'label' => $this->label,
            'singularLabel' => $this->singularLabel,
            'sections' => array_map(fn (Section $s): array => $s->toArray(), $this->sections),
            'columns' => array_map(fn (Column $c): array => $c->toArray(), $this->columns),
            'filters' => array_map(fn (Filter $f): array => $f->toArray(), $this->filters),
            'bulkActions' => array_map(fn (BulkAction $b): array => $b->toArray(), $this->bulkActions),
            'hasCustomRowActions' => count($this->rowActions) > 0,
            'record_actions' => array_map(fn (RecordAction $a): array => $a->toArray(), $this->recordActions),
            'permission' => $this->permission,
            'showTimestamps' => $this->showTimestamps,
            'duplicatable' => $this->duplicatable,
            'isHierarchical' => $this->isHierarchical,
            'parentField' => $this->parentField,
            'isPublic' => $this->isPublic,
            'publicUrlBase' => $this->publicUrlBase,
            'isRevisable' => $this->isRevisable,
        ];
    }
}
