<?php

declare(strict_types=1);

namespace Builder\Core\Foundation\Definition;

use Builder\Core\Foundation\Definition\Concerns\HasOptions;
use Builder\Core\Foundation\Definition\Concerns\HasPermissions;
use Builder\Core\Foundation\Definition\Concerns\Translatable;
use Builder\Core\Foundation\Definition\Enums\FieldComponent;
use Builder\Core\Foundation\Enums\PublicationStatus;
use Builder\Core\Foundation\Resources\NestedResource;
use Builder\Core\Support\Module;
use Closure;
use Illuminate\Support\Traits\Conditionable;
use Illuminate\Support\Traits\Macroable;

final class Field
{
    use Conditionable;
    use HasOptions;
    use HasPermissions;
    use Macroable;
    use Translatable;

    private string $label;

    private string $component = 'TextInput';

    /** @var list<string> */
    private array $rules = [];

    private mixed $default = null;

    private ?string $hint = null;

    private bool $readonly = false;

    private ?string $quickCreateResource = null;

    private string $quickCreateLabelKey = 'name';

    /** @var array<string, mixed> */
    private array $meta = [];

    /** @var ?Closure(mixed, mixed): bool */
    private ?Closure $condition = null;

    private function __construct(private readonly string $name)
    {
        $this->label = $name;
    }

    public static function make(string $name): self
    {
        return new self($name);
    }

    // --- Type static helpers ---

    public static function text(string $name): self
    {
        return (new self($name))->component(FieldComponent::Text->value);
    }

    public static function email(string $name): self
    {
        return (new self($name))->component(FieldComponent::Email->value);
    }

    public static function tel(string $name): self
    {
        return (new self($name))->component(FieldComponent::Tel->value);
    }

    public static function url(string $name): self
    {
        return (new self($name))->component(FieldComponent::Url->value);
    }

    public static function number(string $name): self
    {
        return (new self($name))->component(FieldComponent::Number->value);
    }

    public static function password(string $name): self
    {
        return (new self($name))->component(FieldComponent::Password->value);
    }

    public static function textarea(string $name): self
    {
        return (new self($name))->component(FieldComponent::Textarea->value);
    }

    public static function select(string $name): self
    {
        return (new self($name))->component(FieldComponent::Select->value);
    }

    public static function radio(string $name): self
    {
        return (new self($name))->component(FieldComponent::Radio->value);
    }

    public static function checkbox(string $name): self
    {
        return (new self($name))->component(FieldComponent::Checkbox->value);
    }

    public static function toggle(string $name): self
    {
        return (new self($name))->component(FieldComponent::Toggle->value)->default(false);
    }

    public static function wysiwyg(string $name): self
    {
        return (new self($name))->component(FieldComponent::Wysiwyg->value);
    }

    public static function hidden(string $name): self
    {
        return (new self($name))->component(FieldComponent::Hidden->value);
    }

    public static function datepicker(string $name): self
    {
        return (new self($name))->component(FieldComponent::Datepicker->value);
    }

    /**
     * A nested resource editor that renders an inline list of child records.
     * The field is automatically marked virtual (excluded from model fill).
     *
     * @param  class-string<NestedResource>  $nestedClass
     */
    public static function nestedResource(string $fieldName, string $nestedClass): self
    {
        $nested = new $nestedClass;

        return (new self($fieldName))
            ->component(FieldComponent::NestedResource->value)
            ->meta('schema', array_map(fn (self $f): array => $f->toArray(), $nested->fields()->toFields()))
            ->meta('isSortable', $nestedClass::sortField() !== null)
            ->meta('isNestable', $nestedClass::isNestable())
            ->meta('parentField', $nestedClass::parentField())
            ->virtual();
    }

    public static function related(string $name): self
    {
        return (new self($name))
            ->component(FieldComponent::Related->value)
            ->meta('relatedResource', null)
            ->meta('relatedLabelKey', null);
    }

    public static function repeater(string $name): self
    {
        return (new self($name))->component(FieldComponent::Repeater->value);
    }

    /**
     * A select field pre-configured for draft/published/scheduled publication statuses.
     */
    public static function publishingStatus(string $name = 'status'): self
    {
        return (new self($name))
            ->component(FieldComponent::Select->value)
            ->label(__('builder::builder.publishing.status'))
            ->default(PublicationStatus::Draft->value)
            ->options([
                PublicationStatus::Draft->value => __('builder::builder.publishing.status_draft'),
                PublicationStatus::Published->value => __('builder::builder.publishing.status_published'),
                PublicationStatus::Scheduled->value => __('builder::builder.publishing.status_scheduled'),
            ]);
    }

    /**
     * A datetime field for the scheduled publish time.
     * Only visible when the status field equals 'scheduled'.
     */
    public static function publishAt(string $name = 'publish_at', string $statusField = 'status'): self
    {
        return (new self($name))
            ->component(FieldComponent::Datetime->value)
            ->label(__('builder::builder.publishing.publish_at'))
            ->meta('visibleWhen', ['field' => $statusField, 'value' => PublicationStatus::Scheduled->value]);
    }

    // --- Fluent setters ---

    public function label(string $label): self
    {
        $this->label = $label;

        return $this;
    }

    public function component(string $component): self
    {
        $this->component = $component;

        return $this;
    }

    /** @param list<string>|string $rules */
    public function rules(array|string $rules): self
    {
        $this->rules = is_string($rules) ? [$rules] : $rules;

        return $this;
    }

    public function default(mixed $value): self
    {
        $this->default = $value;

        return $this;
    }

    public function hint(string $hint): self
    {
        $this->hint = $hint;

        return $this;
    }

    public function readonly(bool $value = true): self
    {
        $this->readonly = $value;

        return $this;
    }

    public function relatedTo(string $resourceSlug): self
    {
        return $this->meta('relatedResource', $resourceSlug);
    }

    public function labelFrom(string $key): self
    {
        return $this->meta('relatedLabelKey', $key);
    }

    /**
     * Set an arbitrary meta key for custom field-component use.
     * Prefer typed builder methods (schema, maxRows, visibleWhen, etc.) when available.
     */
    public function meta(string $key, mixed $value): self
    {
        $this->meta[$key] = $value;

        return $this;
    }

    public function getMeta(string $key, mixed $default = null): mixed
    {
        return $this->meta[$key] ?? $default;
    }

    /** @param list<Field> $fields */
    public function schema(array $fields): self
    {
        return $this->meta('fields', array_map(fn (Field $f): array => $f->toArray(), $fields));
    }

    public function maxRows(?int $max): self
    {
        return $this->meta('maxRows', $max);
    }

    public function minRows(?int $min): self
    {
        return $this->meta('minRows', $min);
    }

    /**
     * Show this field only when another field has a specific value.
     * Evaluated client-side — use for form-state-dependent visibility.
     */
    public function visibleWhen(string $field, mixed $value): self
    {
        return $this->meta('visibleWhen', compact('field', 'value'));
    }

    /**
     * Mark the field as virtual so it is excluded from model fill.
     * Virtual fields are passed to field handlers but never written to the DB directly.
     */
    public function virtual(bool $value = true): self
    {
        return $this->meta('virtual', $value);
    }

    /**
     * Show a quick-create button next to the select to inline-create a related resource.
     * Only applies to single-select fields. The created record's $labelKey attribute is
     * used as the option label after creation.
     */
    public function quickCreate(string $resourceSlug, string $labelKey = 'name'): self
    {
        $this->quickCreateResource = $resourceSlug;
        $this->quickCreateLabelKey = $labelKey;

        return $this;
    }

    public function getQuickCreateResource(): ?string
    {
        return $this->quickCreateResource;
    }

    public function whenModuleActive(string $key): ?static
    {
        return Module::has($key) ? $this : null;
    }

    public function condition(Closure $condition): self
    {
        $this->condition = $condition;

        return $this;
    }

    // --- Getters ---

    public function getName(): string
    {
        return $this->name;
    }

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

    public function getComponent(): string
    {
        return $this->component;
    }

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

    public function getDefault(): mixed
    {
        return $this->default;
    }

    public function getHint(): ?string
    {
        return $this->hint;
    }

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

    /** @return ?Closure(mixed, mixed): bool */
    public function getCondition(): ?Closure
    {
        return $this->condition;
    }

    /** @return array<string, mixed> */
    public function toArray(): array
    {
        return [
            'name' => $this->name,
            'label' => $this->label,
            'component' => $this->component,
            'rules' => $this->rules,
            'default' => $this->default,
            'hint' => $this->hint,
            'readonly' => $this->readonly,
            'options' => $this->getOptions(),
            'multiple' => $this->multiple,
            'viewPermission' => $this->viewPermission,
            'updatePermission' => $this->updatePermission,
            'translatable' => $this->translatable,
            'generateFrom' => $this->generateFrom,
            'quickCreate' => $this->quickCreateResource !== null
                ? ['resource' => $this->quickCreateResource, 'labelKey' => $this->quickCreateLabelKey]
                : null,
            ...$this->meta,
        ];
    }
}
