<?php

namespace apexl\Io\modules\component\entities\operators;

use apexl\Io\modules\component\entities\componentEntity;
use apexl\Io\modules\component\entities\componentTypeEntity;
use apexl\Io\modules\component\entities\propertyEntity;
use apexl\Io\operators\entityDatabaseOperator;
use apexl\Vault\exceptions\ExecutionException;
use Exception;

class componentOperator extends entityDatabaseOperator
{
    public function __construct($table, $primaryKey = 'id')
    {
        parent::__construct($table, $primaryKey);
    }

    /**
     * Override apexl\Io\operators\entityDatabaseOperator::store;
     * @throws Exception
     */
    public function store(array $data): array|bool
    {
        //first, are we inserting or updating
        if (empty($data)) {
            throw new Exception("Cannot store empty entity data");
        }
        $entity = [];
        if (isset($data['entity'][$this->primaryKey]) && !empty($data['entity'][$this->primaryKey])) {
            $entity = $this->load($data['entity'][$this->primaryKey]);
        }
        if (isset($entity['data']) && !empty($entity['data'])) {
            //not empty? we have a record, so update.
            $this->vault->update($this->dbTable)->fields($data['entity'])->where(
                $this->primaryKey,
                $data['entity'][$this->primaryKey]
            )->execute();
            $this->upSertProperties($this->primaryKey, $data['props'])->upsertComponents(
                $this->primaryKey,
                $data['components']
            );

            return true;
        } else {
            //we're inserting to force an entity update in case we have a new primary key.
            $this->vault->insert($this->dbTable)->fields($data['entity'])->execute();
            $lastStored = $this->getLastNewRecord();
            $this->upsertProperties($lastStored[$this->primaryKey], $data['props'])->upsertComponents(
                $lastStored[$this->primaryKey],
                $data['components']
            );

            //force the local entity to be updated with the new data (id etc)
            return ['updateEntityData' => true, 'data' => $this->load($lastStored[$this->primaryKey])];
        }
    }

    /**
     * @throws ExecutionException
     */
    public function load(int|string $id, $skipAccessCheck = false): array
    {
        //first we need to load everything for the component
        $entity = $this->vault->select($this->dbTable)
            ->fields()
            ->where($this->primaryKey, $id)
            ->execute()->fetchAssoc();

        //next, we get all properties
        $props = (new propertyEntity())->getByCid($entity['id']);

        //get any components, we need to load the appropriate namespace to be able to do this.
        //Allows for base component entity to be extended and not have to extend the base operator every time.
        $type = new componentTypeEntity();
        $type->load($entity['type'], $skipAccessCheck);
        $componentType = $type->namespace.'\\'.$entity['type']."Entity";
        $components = (new $componentType())->getByPid($entity['id']);

        //then we need to take any ids for props and components and load those entities
        return [
            'updateEntityData' => true,
            'data' => ["data" => $entity, "props" => $props, "components" => $components],
        ];
    }

    /**
     * @throws ExecutionException
     */
    public function getByPid($pid): array
    {
        $components = $this->vault->select($this->dbTable)
            ->fields('id')
            ->where('pid', $pid)
            ->execute()->fetchAll();

        //return a loaded array
        $loadedComponents = [];
        if (!empty($components)) {
            foreach ($components as $component) {
                $loadedComponents[$component->id] = (new componentEntity())->load($component->id);
            }
        }

        return $loadedComponents;
    }

    /**
     * Store Child components (This will be recursive).
     * @noinspection PhpReturnValueOfMethodIsNeverUsedInspection
     */
    private function upsertComponents(int $componentID, array $components): static
    {
        foreach ($components as $component) {
            //Force the component ID to be sure
            $component->pid = $componentID;
            $component->store();
        }

        return $this;
    }

    /**
     * Store Component Properties
     */
    private function upsertProperties(int $componentID, array $properties): static
    {
        foreach ($properties as $property) {
            //Force the component ID to be sure
            $property->cid = $componentID;
            $property->store();
        }

        return $this;
    }
}
