<?php

namespace apexl\Io\modules\display\components;

use apexl\Io\includes\Entity;
use apexl\Io\modules\component\Collections\ComponentCollection;
use apexl\Io\modules\component\Collections\ComponentPropertyCollection;
use apexl\Io\modules\component\Component\Component;
use apexl\Io\modules\component\Factories\ComponentPropertyFactory;
use apexl\Io\modules\component\Factories\GenericComponentFactory;
use Arrayy\Type\StringCollection;
use Closure;

/**
 * @deprecated Builder-pattern component incompatible with the readonly Component contract — mutating methods (setEntity()/setFilters()/hideDisabled()) fatal at runtime on write-once properties. Superseded by tabulator-based components: io-tabulator (Tabulator / EntityTabulator / AggregateTabulator); user/role admin screens via apexl/io-user-tabulator. Do not use in new code; removal planned once the legacy io-user userDisplay routes are gone.
 */
readonly class EntityDisplayTable extends Component
{
    // setName() is part of the io-component 3.1.7 compatibility shim (slated
    // for reversal) and doesn't exist on the strict base. Both the strict and
    // shimmed Component constructors resolve the component name from a `NAME`
    // class constant when present, so declare it instead of calling setName().
    public const string NAME = 'DataTable';

    // Untyped properties / property defaults are illegal on readonly classes
    // (pre-existing parse error). Only the parse errors are fixed here —
    // several of these properties are reassigned post-construction (see the
    // setters below), which is incompatible with readonly write-once
    // semantics and is flagged for design review:
    // - $dataSrc: written by setEntity() AND again by the src() call it makes
    //   (a double write — fatal even during construction with an entity)
    // - $filters: reassigned by setFilters()
    // - $entity: reassigned by setEntity()
    // - $hideDisabled: reassigned by the hideDisabled() setter
    // - $dropMenu: assigned lazily by addDropmenuComponent()/setDropmenuItems()
    // $dataSrc is deliberately NOT constructor-initialised so a consumer's
    // first src() call remains a legal lazy first write; its only read uses
    // `?? ''`, which is safe on an uninitialised typed property.
    protected mixed $dataSrc;
    protected mixed $filters;
    protected Entity $entity;
    protected bool $hideDisabled;
    protected ?DropMenu $dropMenu;

    public function __construct(
        protected readonly GenericComponentFactory $componentFactory,
        ComponentPropertyFactory $propertyFactory,
        ComponentPropertyCollection $props,
        ComponentCollection $components,
        StringCollection $classes,
        ?string $componentId = null,
        ?Entity $entity = null,
        $replacements = []
    ) {
        parent::__construct($propertyFactory, $props, $components, $classes, $componentId);
        $this->filters = null;
        $this->hideDisabled = false;
        $this->dropMenu = null;
        if ($entity !== null) {
            $this->setEntity($entity, $replacements);

            $hideCols = array_filter(
                $entity->getFieldConfig(),
                fn($config) => $config['table'] ?? null === 'hide'
            );

            if ($hideCols) {
                $this->hideCols(array_keys($hideCols));
            }
        }
        $this->displayPagination();
    }

    public function setEntity(Entity $entity, $replacements = [])
    {
        $this->entity = $entity;
        $this->dataSrc = $this->routeManager->getRoutePattern($entity->getEntityName() . '.get', $replacements);
        $this->src($this->dataSrc);

        return $this;
    }

    public function src($dataSrc)
    {
        $this->dataSrc = $dataSrc;
        $src = $this->dataSrc ?? '';
        if ($this->filters) {
            $src .= '?' . ltrim((string) $this->filters, '?') . '&asTable=1';
        } else {
            $src .= '?asTable=1';
        }
        $this->setProperty('dataSrc', $src);

        return $this;
    }

    public function hideCols(array $cols): EntityDisplayTable
    {
        $this->setProperty('hideCols', $cols);

        return $this;
    }

    public function displayPagination($display = true)
    {
        $this->setProperty('showPagination', $display);

        return $this;
    }

    public function setFilters($filters)
    {
        $this->filters = $filters;
        $this->src($this->dataSrc);

        return $this;
    }

    public function showCols(array $cols): EntityDisplayTable
    {
        $this->setProperty('showCols', $cols);

        return $this;
    }

    public function displayShortTotals($display = true)
    {
        $this->setProperty('displayShortTotals', $display);

        return $this;
    }

    public function colspans($colspans = [])
    {
        $this->setProperty('colspans', $colspans);

        return $this;
    }

    public function hideIdColumn($display = true)
    {
        $this->setProperty('hideIdColumn', $display);

        return $this;
    }

    public function addCommonButtons(): EntityDisplayTable
    {
        $buttons = [
            'get' => Closure::fromCallable(function ($routeName): self {
                return $this->addViewButton($routeName);
            }),
            'put' => Closure::fromCallable(function ($routeName): self {
                return $this->addEditButton($routeName);
            }),
            'delete' => Closure::fromCallable(function ($routeName): self {
                return $this->addDeleteButton($routeName);
            }),
        ];

        $entityName = $this->entity->getEntityName();

        foreach ($buttons as $routeAppend => $callable) {
            $routeName = sprintf('%s.display.%s', $entityName, $routeAppend);
            if ($this->routeManager->getRoutePattern($routeName . '.override')) {
                $callable($routeName . '.override');
            } elseif ($this->routeManager->getRoutePattern($routeName)) {
                $callable($routeName);
            }
        }

        return $this;
    }

    /**
     * @param $routeName
     */
    public function addViewButton($routeName): EntityDisplayTable
    {
        $this->addButton($routeName, 'eye', $this->getAccessFromRoute($routeName));

        return $this;
    }

    /**
     * @param $routeName
     */
    public function addButton(
        $routeName,
        string $icon = '',
        bool $disabled = false,
        bool $openInNewWindow = false
    ): EntityDisplayTable {
        $entityTrueName = isset($this->entity) && $this->entity ? $this->entity->getEntityTrueName() : '';

        $button = $this->componentFactory->make(TableButton::class)
            ->addRoute($this->routeManager->getRoutePattern($routeName, [], false))
            ->addIcon($icon)
            ->addClass($entityTrueName . '_table__btn')
            ->addClass($entityTrueName . '_table__btn-' . $icon)
            ->addClass('table__btn')
            ->addClass('table__btn-' . $icon);
        if ($openInNewWindow) {
            $button->openInNewWindow();
        }
        if ($disabled) {
            $button->addRoute('#');
            $button->addClass($entityTrueName . '_table__btn-disabled');
            $button->addClass('table__btn-disabled');
            $button->disabled($disabled);
            if ($this->hideDisabled) {
                //do not add the button to the component
                return $this;
            }
        }
        $this->addComponent($button);

        return $this;
    }

    /**
     * @param $routeName
     */
    protected function getAccessFromRoute($routeName): bool
    {
        $route = $this->routeManagerRouteManager::getRoute($routeName);
        if (($route ?? false) && $permission = $route->getPermission()) {
            //we need to pass the opposite here as disabled is false if access it true.
            return !$this->currentUser->isAllowed($permission);
        }

        //no permission? just return true.
        return (bool) $route; //no route? return false.
    }

    /**
     * @param $routeName
     */
    public function addEditButton($routeName): EntityDisplayTable
    {
        $this->addButton($routeName, 'edit', $this->getAccessFromRoute($routeName));

        return $this;
    }

    /**
     * @param $routeName
     */
    public function addDeleteButton($routeName): EntityDisplayTable
    {
        $this->addButton($routeName, 'trash', $this->getAccessFromRoute($routeName));

        return $this;
    }

    public function addDropMenuModal($title, $modalTitle, $modalComponents, $contentSrc = '', $conditional = null)
    {
        $this->addDropmenuComponent(
            $this->componentFactory->make(Modal::class)
                ->setTitle($modalTitle)
                ->setButtonContent($title)
                ->setButtonClass('')
                ->setContentSrc($contentSrc)
                ->addComponents($modalComponents)
                ->setRowConditional($conditional)
        );

        return $this;
    }

    public function addDropmenuComponent($component)
    {
        if (!$this->dropMenu) {
            $this->dropMenu = $this->componentFactory->make(DropMenu::class);
            $this->addComponent($this->dropMenu);
        }
        $this->dropMenu->addComponent($component);

        return $this;
    }

    public function addDropMenuItem($routeName, $title, $cssClass = '', $method = 'get', $target = null, $isApiRoute = false, $dynamicPdf = false, $replaceParameters = [])
    {
        $menuItem = (object) [
            'route' => ($isApiRoute ? rtrim(
                    $this->config->app->site->backend_domain,
                    "/"
                ) : '') . $this->routeManager->getRoutePattern($routeName, [], false),
            'title' => $title,
            'class' => $cssClass
        ];
        foreach ($replaceParameters as $find => $replace) {
            $menuItem->route = str_replace("{" . $find . "}", "{" . $replace . "}", $menuItem->route);
        }
        if ($method !== 'get') $menuItem->method = $method;
        if ($target) $menuItem->target = $target;
        if ($dynamicPdf) $menuItem->dynamicPdf = true;

        $this->setDropmenuItems([$menuItem]);

        return $this;
    }

    public function setDropmenuItems($menuItems)
    {
        if (!$this->dropMenu) {
            $this->dropMenu = $this->componentFactory->make(DropMenu::class);
            $this->addComponent($this->dropMenu);
        }
        $this->dropMenu->addMenuItems($menuItems);

        return $this;
    }

    /**
     * @param $routeName
     */
    public function addPostButton(
        $routeName,
        array $replacements = [],
        string $icon = '',
        bool $disabled = null
    ): EntityDisplayTable {
        if (is_null($disabled)) {
            $disabled = $this->getAccessFromRoute($routeName);
        }
        $button = $this->componentFactory->make(TableButton::class)
            ->addRoute($this->routeManager->getRoutePattern($routeName, $replacements, false))
            ->addIcon($icon)
            ->isPost()
            ->addClass($this->entity->getEntityTrueName() . '_table__btn')
            ->addClass($this->entity->getEntityTrueName() . '_table__btn-' . $icon)
            ->addClass('table__btn')
            ->addClass('table__btn-' . $icon);
        if ($disabled) {
            $button->addRoute('#');
            $button->addClass($this->entity->getEntityTrueName() . '_table__btn-disabled');
            $button->addClass('table__btn-disabled');
            $button->disabled($disabled);
            if ($this->hideDisabled) {
                //do not add the button to the component
                return $this;
            }
        }
        $this->addComponent($button);

        return $this;
    }

    public function hideDisabled(bool $value = true): EntityDisplayTable
    {
        $this->hideDisabled = $value;

        return $this;
    }
}
