<?php

namespace apexl\Io\modules\component\entities;

use apexl\Io\includes\Entity;
use apexl\Io\modules\component\entities\operators\componentOperator;

class componentEntity extends Entity
{
    protected $props;
    protected $components;
    protected $supportComponents;
    protected $componentId;

    protected $classArray = [];
    protected $columns = [];

    protected $name;

    public function __construct()
    {
        parent::__construct('components');
        $this->setOperator(new componentOperator('components'));
        $this->setName($this->getComponentName());
        $this->setRequiredData([
            "type"
        ]);
        $this->setType();
        $this->id = $this->getComponentName().'_'.substr(sha1(microtime()), 0, 16);
        $this->setComponentId($this->id);
        $this->props = (object)[];
    }

    /**
     * Type is required so we know what entity class to use when loading. Default is component (the standard entity)
     * @param string $type
     * @return $this
     */
    public function setType($type = "component")
    {
        $this->data["type"] = $type;
        return $this;
    }

    public function setComponentId($id)
    {
        $this->data['componentId'] = $id;
        return $this;
    }

    public function setName($name)
    {
        $this->data["name"] = $name;
        return $this;
    }

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

    public function setID($id)
    {
        $this->data["id"] = $id;
        return $this;
    }

    public function setDataSource($src)
    {
        $this->addProperty("dataSrc", $src);
        return $this;
    }

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

    public function resetComponents()
    {
        $this->components = null;
        return $this;
    }

    public function resetClasses()
    {
        $this->classArray = [];
        $this->classes = '';
        return $this;
    }

    public function resetColumns()
    {
        $this->columns = [];
        return $this;
    }

    /**
     * As overrides are annoying, separate columns into their own overrideable storage before converting to classes.
     * @param $size
     * @param $cols
     * @return $this
     */
    public function setColWidth($size, $cols)
    {
        $this->columns[$size] = $cols;
        $this->addClassInternal();
        return $this;
    }

    /**
     * At some point we managed to mess up the class creation and management, so add an override here to be processed
     * when the component render is called.
     * @param $class
     * @return componentEntity
     */
    public function addClass(string $class)
    {
        //check for multiples, split them out if we have them.
        if (str_contains(trim($class), ' ')) {
            $classes = explode(' ', $class);
            foreach ($classes as $class) {
                $this->addClassInternal($class);
            }
        } else {
            $this->addClassInternal($class);
        }
        return $this;
    }

    protected function addClassInternal($class = '')
    {
        $this->classes = '';
        if (!empty($class) && !in_array(trim((string) $class), $this->classArray)) {
            $this->classArray[] = trim((string) $class);
        }
        if (!empty($this->columns)) {
            //always do this when a class changes, so we dont have to scan
            // all components and manually trigger
            $cols = [];
            foreach ($this->columns as $col => $number) {
                $cols[] = 'col-' . $col . '-' . $number;
            }
            $this->classes = implode(' ', $cols);
        }
        //clean any blanks.
        array_filter($this->classArray);
        $this->classes .= empty(trim($this->classes)) ? implode(' ', $this->classArray) : ' '.implode(' ', $this->classArray);
    }

    /**
     * @throws \Exception
     */
    public function addProperties(array $props)
    {
        if ($props !== []) {
            foreach ($props as $prop) {
                //validation for property names and values
                if (($prop['name'] ?? true) || ($prop['value'] ?? true)) {
                    throw new \Exception("Property Name and Value must be set.");
                }
                $this->addProperty($prop['name'], $prop['value'], ($prop['classes'] ?? null), ($prop['metadata'] ?? null));
            }
        } else {
            //empty? assume we want to reset props
            //@NOTE this will not trigger a delete method. It will simply remove any properties in the loaded entity, until it is loaded again
            $this->props = (object)[];
        }
    }

    /**
     * @param $name
     * @param $value
     * @param null $classes
     * @param null $metaData
     */
    public function addProperty($name, $value, $classes = null, $metaData = null)
    {
        $this->props->$name = (new propertyEntity())->setName($name)->setValue($value)->setClasses($classes)->setMetadata($metaData);
        return $this;
    }

    public function removeProperty($name)
    {
        if (isset($this->props->$name)) {
            unset($this->props->$name);
        }
        return $this;
    }

    public function addComponent(componentEntity $componentEntity)
    {
        $this->components[$componentEntity->id] = $componentEntity;
        return $this;
    }

    //bulk add components - note, for now this overwrites any components already assigned.
    public function addComponents(array $components)
    {
        $this->components = $components;
        return $this;
    }

    public function addSupportComponent(componentEntity $componentEntity)
    {
        $this->supportComponents[$componentEntity->id] = $componentEntity;
        return $this;
    }

    public function load($id, $skipAccessCheck = false)
    {
        return parent::__call('load', [$id, $skipAccessCheck]);
    }

    /**
     * Override base entity setData method as we need to set props and components as well.
     * @param $entity
     */
    public function setData($entity): static
    {
        $this->data = empty($entity["data"]) ? array() : json_decode(json_encode($entity["data"]), true);
        $this->props = $entity["props"] ?? (object)[];
        $this->components = $entity["components"] ?? [];
        $this->supportComponents = $entity["supportComponents"] ?? [];
        return $this;
    }

    /**
     * Override @see \apexl\entityCore\Entity
     * Magic PHP __set function to set provided field value
     * @param $name string
     * @param $value string|array|object
     * @return $this
     */
    public function __set($name, $value)
    {
        switch(strtolower((string) $name)) {
            //Allow us to shortcut the addProp method.
            case 'props':
                $property = (new propertyEntity())->setName($name);
                $value = is_array($value) ? (object)$value : $value;
                if (is_object($value)) {
                    $property->setValue($value->value);
                    $property->setClasses(($value->classes ?? null));
                    $property->setMetadata($value->metadata ?? null);
                } else {
                    $property->setValue($value);
                }
                $this->props->$name = $property;
                break;
            default:
                return $this->setField($name, $value);
        }
        return $this;
    }

    /**
     * Allow us to store with base entity data, prevents the need o call a method while passing the result of another method to the operator
     */
    public function store(): Entity
    {
        $this->operator->store(['entity' => $this->data, 'props' => $this->props, 'components' => $this->components]);
        return $this;
    }

    /**
     * Override the base toString method as we need to do some processing here.
     * Allows us to recursively process nested component entities.
     * @return false|string
     */
    public function __toString()
    {
        return json_encode($this->prepareString()); //return generated json
    }

    protected function prepareString()
    {
        //get data and build json Structure.
        $component = (object)$this->getData();
        $component->props = (object)[]; //always pass at least an empty object
        //handle Properties
        if (!empty($this->props)) {
            foreach ($this->props as $name => $prop) {
                $component->props->$name = $prop->value;
            }
        }

        //Handle Nested Components
        if (!empty($this->components)) {
            foreach ($this->components as $nestedComponent) {
                $component->components[] = $nestedComponent->prepareString();
            }
        }

        //Handle Nested Support Components (Secondary page components related to the parent)
        if (!empty($this->supportComponents)) {
            foreach ($this->supportComponents as $nestedComponent) {
                $component->supportComponents[] = $nestedComponent->prepareString();
            }
        }

        return $component;
    }

    /**
     * Easy get name of component method.
     * @return string
     */
    public function getComponentName()
    {
        return (new \ReflectionClass($this))->getShortName();
    }

    /**
     * Clone the component entity.
     * @return componentEntity
     */
    public function clone()
    {
        return clone $this;
    }
}
