<?php

declare(strict_types=1);

namespace apexl\Io\services;

use apexl\dto\Raw;
use apexl\Io\collections\LoaderConditionCollection;
use apexl\Io\collections\LoaderSortCollection;
use apexl\Io\exceptions\LoaderArgumentException;
use apexl\Io\includes\Entity;
use apexl\Io\modules\tabulator\Services\TabulatorFieldRenamer;
use apexl\Vault\interfaces\driver;
use Arrayy\Arrayy;
use Arrayy\Type\StringCollection;

final readonly class LoaderArgumentResolver
{
    public StringCollection $rawFields;
    private StringCollection $fields;

    /**
     * @throws LoaderArgumentException
     */
    public function __construct(
        string|array|StringCollection $fields,
        private ?string $table,
        private Entity $entity,
        private LoaderSortResolver $loaderSortResolver,
        private LoaderDataFieldsResolver $loaderDataFieldsResolver,
        private TabulatorFieldRenamer $tabulatorFieldRenamer,
    ) {
        $this->fields = $this->resolveFields($fields);
        $this->rawFields = $this->resolveRawFields($fields);
    }

    private function resolveFields(string|array|StringCollection $fields): StringCollection
    {
        if (is_string($fields)) {
            $fields = [$fields];
        }

        if (is_array($fields)) {
            $fields = StringCollection::createFromArray(
                array_filter(array_filter($fields, fn(string|Raw $field): bool => is_string($field)))
            );
        }

        return $fields->map($this->tabulatorFieldRenamer->rename(...));
    }

    /**
     * @throws LoaderArgumentException
     */
    private function resolveRawFields(string|array|StringCollection $fields): StringCollection
    {
        if ($fields instanceof StringCollection) {
            return new StringCollection();
        }

        if (is_string($fields)) {
            $fields = [$fields];
        }

        if (is_array($fields)) {
            $out = [];
            /** @var Raw[] $fields */
            $fields = array_filter($fields, fn(string|Raw $field): bool => $field instanceof Raw);
            foreach ($fields as $field) {
                if (!preg_match('/^(.*) as (\w+)$/i', $field->value, $matches)) {
                    throw new LoaderArgumentException('Raw fields must be in the format of `<expr> as <alias>`');
                }

                [, $expression, $alias] = $matches;
                $out[$expression] = $alias;
            }

            return StringCollection::createFromArray($out);
        }

        return StringCollection::createFromObject($fields->map(fn(Raw $field): string => $field->value));
    }

    public function resolveSorts(array|string|LoaderSortCollection $sorts): LoaderSortCollection
    {
        return $this->loaderSortResolver->resolve($sorts);
    }

    public function resolveDataFields(bool $autoIncludePrimaryKey = true): StringCollection
    {
        return $this->loaderDataFieldsResolver
            ->resolve($this->table, $this->fields, $this->entity, $autoIncludePrimaryKey);
    }

    public function resolveVirtualFields(): StringCollection
    {
        $output = [];
        $this->fields
            ->filter($this->entity->isVirtualField(...))
            ->each(function (string $field) use (&$output): void {
                $_field = $this->entity->virtualFields()->get($field);
                $output[$_field->field] = $_field->toSelect($this->table);
            });

        return StringCollection::createFromArray($output)
            ->flip()
            ->map(fn(string $field): string => sprintf('`%s`', $field));
    }

    public function resolveRelations(): Arrayy
    {
        $filtered = $this->fields
            ->filter($this->entity->isRelationField(...))
            ->unique();

        $grouped = $filtered->group(
            function (string $field): string {
                if (str_contains($field, '__')) {
                    return substr($field, 0, strpos($field, '__') ?: 0);
                }

                return $field;
            }
        )->map(
            fn(array $fields): array => array_map(
                fn(string $field): string => str_contains($field, '__') ?
                    substr($field, strpos($field, '__') + 2) :
                    $field,
                $fields
            )
        );

        return $grouped->map(
            function (array $fields, $key): driver {
                $relation = $this->entity->relation($key);

                return $relation->subSelect($fields, $this->table);
            },
            true,
        );
    }

    public function resolveConditions(array|LoaderConditionCollection $conditions): LoaderConditionCollection
    {
        if (is_array($conditions)) {
            $conditions = LoaderConditionCollection::createFromArray($conditions);
        }

        return $conditions;
    }

    public function resolveLimit(?int $limit, int $offset): ?int
    {
        if ($limit === null) {
            return $offset ? PHP_INT_MAX : $limit;
        }

        return $limit;
    }
}