<?php

namespace apexl\Io\modules\display\includes;

use apexl\Io\includes\System;
use apexl\Io\modules\component\entities\componentEntity;
use apexl\Io\modules\component\services\ComponentBucket;
use apexl\Io\modules\component\services\regionBucket;
use apexl\Io\modules\display\components\RowWrapper;
use apexl\Utils\Arrays\Merge;
use ReflectionClass;

/**
 * Class region. This is a container wrapper for a general component - makes adhoc component grouping and injection much easier.
 * Base region class to allow others to extend. Provides generic page structures to allow for easy page building.
 * @package apexl\Io\modules\display\includes
 */
abstract class region
{
    protected string $name;
    protected componentEntity $regionComponent;
    protected regionBucket $regions;
    protected ComponentBucket $components;

    /**
     * @param (componentEntity|region)[] $components
     */
    public function __construct(array $components = [])
    {
        $this->components = System::makeRegisteredService(ComponentBucket::class);
        $this->regions = System::makeRegisteredService(regionBucket::class);

        $this->setRegionWrapper();
        $this->initComponents($components);
    }

    /**
     * Overload this method to change the wrapper.
     */
    public function setRegionWrapper(): void
    {
        $this->regionComponent = new RowWrapper();
    }

    private function initComponents(array $components): void
    {
        foreach ($components as $component) {
            /** Check if we're adding a region or a component */
            if (
                $component instanceof region &&
                $component->isWrapperType('region')
            ) {
                $this->addRegion($component);

                return;
            }
            $this->addComponent($component);

        }
    }

    public function isWrapperType(string $type): bool
    {
        return $this->getWrapperType() === $type;
    }

    public function getWrapperType(): string
    {
        return 'region';
    }

    /**
     * Add a subregion.
     */
    public function addRegion(region $region): region
    {
        $name = $region->getRegionName();

        if (!$this->regions->has($name)) {
            $this->regions->set($name, $region);
        }

        return $this->regions->get($name);
    }

    /**
     * Easy get name of region method.
     */
    public function getRegionName(): string
    {
        return (new ReflectionClass($this))->getShortName();
    }

    /**
     * Add a component. Allow the specification of a subregion and the auto inclusion if it does not currently exist
     */
    public function addComponent(componentEntity $component, ?int $weight = null, ?string $region = null): static
    {
        if ($this->isRegion($region)) {
            $region = new $region();
            $this->addRegion($region)->addComponent($component, $weight);
        } elseif ($weight !== null && $this->components->has($weight)) {
            /**
             * Check the weight and insert accordingly.
             * If the key position exists already, insert after and increment everything else.
             */
            $components = $this->components->all();
            Merge::arrayByWeight($weight, $component, $components);
            $this->components = System::makeRegisteredService(ComponentBucket::class, [
                'data' => $components,
            ]);
        } else {
            $this->components->set($component->id, $component);
        }

        return $this;
    }

    private function isRegion(?string $region = null): bool
    {
        return $region && class_exists($region) && is_a($region, region::class, true);
    }

    public function getRegion($region): ?region
    {
        return $this->regions[$region] ?? null;
    }

    public function addWrapperClass($class): static
    {
        $this->regionComponent->addClass($class);

        return $this;
    }

    public function addSecondaryComponent(componentEntity $component): static
    {
        $this->regionComponent->addSupportComponent($component);

        return $this;
    }

    public function clearWrapperClasses(): static
    {
        $this->regionComponent->resetClasses();
        $this->regionComponent->resetColumns();

        return $this;
    }

    /**
     * Allows us to add multiple regions at once to construct basic "pages"
     * @param region[] $regions
     */
    public function addRegions(array $regions): static
    {
        foreach ($regions as $region) {
            $this->addRegion($region);
        }

        return $this;
    }

    /**
     * Build the region as components for output to the API.
     */
    public function buildRegions(): componentEntity
    {
        //loop over all subregions, components etc. and run the builder methods.
        //we have to assume that regions are rendered before components.
        foreach ($this->regions as $region) {
            $this->regionComponent->addComponent($region->buildRegions());
        }

        foreach ($this->components as $component) {
            $this->regionComponent->addComponent($component);
        }

        return $this->regionComponent;
    }
}
