<?php

namespace App\Form\Fields;

use App\Form\Data\ColumnData;
use App\Form\Enums\NamiType;
use App\Form\Enums\SpecialType;
use App\Form\Matchers\Matcher;
use App\Form\Matchers\SingleValueMatcher;
use App\Form\Models\Form;
use App\Form\Models\Participant;
use App\Form\Presenters\DefaultPresenter;
use App\Form\Presenters\Presenter;
use App\Lib\Editor\Comparator;
use Faker\Generator;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Attributes\MapInputName;
use Spatie\LaravelData\Attributes\MapOutputName;
use Spatie\LaravelData\Mappers\SnakeCaseMapper;

#[MapInputName(SnakeCaseMapper::class)]
#[MapOutputName(SnakeCaseMapper::class)]
abstract class Field extends Data
{

    public string $key;
    public string $name;
    public ?NamiType $namiType = null;
    public ColumnData $columns;
    public bool $forMembers;
    public ?SpecialType $specialType = null;
    public ?string $hint;
    public ?string $intro;

    /** @var mixed */
    public $value;

    /**
     * @param array<array-key, mixed> $input
     */
    abstract public function afterRegistration(Form $form, Participant $participant, array $input): void;

    abstract public static function name(): string;

    /** @return array<int, array{key: string, default: mixed, label: string, rules: array<string, mixed>}> */
    abstract public static function meta(): array;

    /** @return mixed */
    abstract public static function default();

    /** @return array<string, mixed> */
    abstract public function getRegistrationRules(Form $form): array;

    /** @return array<string, mixed> */
    abstract public function getRegistrationAttributes(Form $form): array;

    /** @return array<string, mixed> */
    abstract public function getRegistrationMessages(Form $form): array;

    /** @return array<string, mixed> */
    abstract public static function fake(Generator $faker): array;

    /**
     * @return array<int, array<string, mixed>>
     */
    public static function asMeta(): array
    {
        return array_map(fn ($class) => $class::allMeta(), self::classNames());
    }

    /**
     * @return array<int, class-string<self>>
     */
    private static function classNames(): array
    {
        return collect(glob(base_path('app/Form/Fields/*.php')))
            ->filter(fn ($fieldClass) => preg_match('/[A-Za-z]Field\.php$/', $fieldClass) === 1)
            ->map(fn ($fieldClass) => str($fieldClass)->replaceFirst(base_path(''), '')->replace('/app', '/App')->replace('.php', '')->replace('/', '\\')->toString())
            ->values()
            ->toArray();
    }

    /**
     * @return class-string<Field>
     */
    public static function classFromType(string $type): ?string
    {
        /** @var class-string<Field> */
        $fieldClass = '\\App\\Form\\Fields\\' . $type;
        if (!class_exists($fieldClass)) {
            return null;
        }

        return $fieldClass;
    }

    /**
     * @return mixed
     */
    public function present()
    {
        return [
            $this->key => $this->value,
            $this->getDisplayAttribute() => $this->presentRaw(),
        ];
    }

    public function presentRaw(): string
    {
        return $this->getPresenter()->present($this->value);
    }

    /**
     * @return array<string, string>
     */
    public static function metaAttributes(): array
    {
        return collect(static::meta())->mapWithKeys(fn ($meta) => [$meta['key'] => $meta['label']])->toArray();
    }

    /**
     * @return array<string, mixed>
     **/
    public static function metaRules(): array
    {
        $result = [];
        foreach (static::meta() as $meta) {
            foreach ($meta['rules'] as $fieldName => $rules) {
                $result[$fieldName] = $rules;
            }
        }

        return $result;
    }

    public static function type(): string
    {
        return class_basename(static::class);
    }

    /**
     * @return array<string, mixed>
     */
    public static function allMeta(): array
    {
        return [
            'id' => static::type(),
            'name' => static::name(),
            'default' => [
                'name' => '',
                'type' => static::type(),
                'columns' => ['mobile' => 2, 'tablet' => 4, 'desktop' => 6],
                'value' => static::default(),
                'nami_type' => null,
                'for_members' => true,
                'special_type' => null,
                'hint' => '',
                'intro' => '',
                ...collect(static::meta())->mapWithKeys(fn ($meta) => [$meta['key'] => $meta['default']])->toArray(),
            ],
        ];
    }

    public function getPresenter(): Presenter
    {
        return app(DefaultPresenter::class);
    }

    public function getDisplayAttribute(): string
    {
        return $this->key . '_display';
    }

    public function matches(Comparator $comparator, mixed $value): bool
    {
        return $this->getMatcher()->setValue($this->value)->matches($comparator, $value);
    }

    public function getMatcher(): Matcher
    {
        return app(SingleValueMatcher::class);
    }
}