<?php

declare(strict_types=1);

namespace Apexl\Io\Tabulator\Controllers;

use Apexl\Io\Controller;
use Apexl\Io\Dtos\LoaderCondition;
use Apexl\Io\Interfaces\OutputManagerInterface;
use Apexl\Io\Tabulator\Interfaces\HasTabulatorViewInterface;
use Apexl\Io\Tabulator\Requests\TabulatorAjaxRequest;
use Arrayy\Type\StringCollection;
use JsonException;
use Psr\Http\Message\ResponseInterface;

final readonly class TabulatorAjaxController extends Controller
{
    /**
     * @param class-string<HasTabulatorViewInterface> $entityClass
     */
    public function __construct(
        private string $entityClass,
        OutputManagerInterface $output,
    ) {
        \Apexl\Io\assertInstanceOf($entityClass, HasTabulatorViewInterface::class);
        parent::__construct($output);
    }

    /**
     * @throws JsonException
     */
    public function __invoke(
        TabulatorAjaxRequest $request,
        ResponseInterface $response,
    ): ResponseInterface {
        $entity = new $this->entityClass();
        $field = $request->field();
        $requestedFields = new StringCollection([$field]);

        $params = $request->data();
        $primaryKey = $entity->getPrimaryKey();

        // The caller names the rows it wants a value for:
        // ?field=X&data={"id":[1,2,3]}. With no ids there is nothing to look
        // up, so answer with an empty map rather than passing null into
        // LoaderCondition::in(), which is typed `array` — a request that
        // simply carries no filter is not a server error.
        $ids = $params[$primaryKey] ?? null;
        if (!is_array($ids) || $ids === []) {
            return $this->json($response, ['data' => []]);
        }

        $conditions = [
            LoaderCondition::in(
                sprintf('%s.%s', $entity->getTableName(), $primaryKey),
                array_values($ids),
            ),
        ];

        $rows = $entity::findMultiple(
            conditions: $conditions,
            // findMultiple() defaults to 20; the id list is already the bound,
            // so a page bigger than 20 rows must not be silently truncated.
            limit: count($ids),
            fields: $requestedFields,
        );

        // Key off each row's OWN primary key rather than zipping the result
        // against the requested id list — findMultiple() always selects the
        // primary key (autoIncludePrimaryKey), and an id that no longer
        // exists would otherwise misalign every subsequent value (or throw,
        // when the two lists end up different lengths).
        $data = [];
        foreach ($rows->toArray() as $row) {
            /** @var HasTabulatorViewInterface $row */
            $tabulated = $row->tabulate($requestedFields, false);
            $data[$row->{$primaryKey}] = $tabulated[$field] ?? null;
        }

        return $this->json(
            $response,
            ['data' => $data]
        );
    }
}
