<?php

namespace apexl\Io\modules\display\includes;

use apexl\Io\modules\component\Collections\ComponentCollection;
use apexl\Io\modules\component\Component\Component;
use apexl\Io\modules\component\Factories\GenericComponentFactory;
use apexl\Io\modules\component\Interfaces\ComponentInterface;
use apexl\Io\modules\display\components\TitleTile;
use apexl\Io\modules\display\regions\ContentRegion;
use apexl\Io\modules\display\regions\PageContentRegion;
use apexl\Io\modules\menu\services\MenuManager;
use apexl\Io\modules\user\entities\userEntity;
use apexl\Io\modules\user\services\CurrentUserFactory;
use apexl\Io\services\HttpPaths;
use Exception;
use Stringable;
use function apexl\Io\config;
use function apexl\Io\container;

;

abstract class Page implements Stringable
{
    public string $pageTitle = '';
    protected array $data = [];
    protected object $metadata;
    protected ComponentCollection $components;
    protected MenuManager $menuManager;
    protected ?userEntity $currentUser;
    protected HttpPaths $path;
    protected TitleTile $titleTile;

    /** While the page has its own "components" array, we're working in regions so need a holder. */
    protected region $globalRegion;
    protected GenericComponentFactory $componentFactory;

    /**
     * @throws Exception
     * @todo DI
     */
    public function __construct()
    {
        $this->metadata = (object) [];
        $this->currentUser = container()->get(CurrentUserFactory::class)->get();
        $this->menuManager = container()->get(MenuManager::class);
        $this->path = container()->get(HttpPaths::class);
        $this->components = container()->make(ComponentCollection::class);
        $this->componentFactory = container()->get(GenericComponentFactory::class);

        //do this in a method so extending classes can override.
        $route = $this->menuManager->getMenuItem($this->path->getCurrentRouteName());
        if ($title = $route->title ?? $this->path->getCurrentRouteName()) {
            $this->setPageTitle($title);
        }
        $this->buildRegions();
    }

    /**
     * Default must have content!
     */
    protected function buildRegions()
    {
        $this->globalRegion = new PageContentRegion();
        $this->globalRegion->addRegions([new ContentRegion()]);
    }

    public function __get($name)
    {
        return $this->data[$name] ?? null;
    }

    public function __set($name, $value)
    {
        $this->data[$name] = $value;
    }

    public function __isset($name)
    {
        return isset($this->data[$name]);
    }

    public function __unset($name)
    {
        unset($this->data[$name]);
    }

    public function setMetaDescription($desc): static
    {
        $this->metadata->description = $desc;

        return $this;
    }

    public function setMetadata($metaKey, $metaValue): static
    {
        $this->metadata->$metaKey = $metaValue;

        return $this;
    }

    public function registerTitleComponent($component): void
    {
        $this->titleTile = $component;
    }

    public function getPageTitle()
    {
        return $this->getPageTitle();
    }

    public function setPageTitle(string $title): static
    {
        $this->pageTitle = $title;
        if (isset($this->titleTile) && method_exists($this->titleTile, 'text')) {
            $this->titleTile->text($this->pageTitle);
        }

        return $this;
    }

    /**
     * Get the top level component by name.
     * @TODO change how components are registered maybe?
     */
    public function getComponent(string $name): Component|false
    {
        return $this->components->get($name);
    }

    /**
     * Allow top level components to be replaced
     */
    public function replaceComponent(string $name, Component $component): bool
    {
        if ($replace = $this->components->has($name)) {
            $this->components->set($name, $component);
        }

        return $replace;
    }

    public function addToContent(array $components, ?int $weight = null): static
    {
        $this->addToRegion($components, ContentRegion::class, $weight);

        return $this;
    }

    protected function addToRegion(array $components, string $regionName, ?int $weight): static
    {
        foreach ($components as $component) {
            $this->globalRegion->addComponent($component, $weight, $regionName);
        }

        return $this;
    }

    public function addComponent(ComponentInterface $component): static
    {
        $this->components->set($component->id, $component);

        return $this;
    }

    public function __toString(): string
    {
        return json_encode($this->getPage());
    }

    /**
     * Get the page as an MD array
     * @return array
     */
    public function getPage(): array
    {
        $this->addComponent($this->globalRegion->buildRegions());
        $this->setMetaTitle(
            sprintf("%s %s %s", $this->pageTitle, config('app.meta.default_divider'), config('app.meta.default_title'))
        );
        $data = [];
        $data['metadata'] = $this->metadata;
        foreach ($this->components as $component) {
            //we need to decode the cast components as they will be json encoded.
            //We do this to ensure we have all nested data.
            $data['components'][] = $component;
        }

        return $data;
    }

    public function setMetaTitle($title): static
    {
        $this->metadata->title = $title;

        return $this;
    }
}
