<?php declare(strict_types = 1);

namespace idoit\Module\SyneticsFlows;

use idoit\Module\SyneticsFlows\Automation\Action\ActionType\ActionType;
use idoit\Module\SyneticsFlows\Automation\Automation;
use idoit\Module\SyneticsFlows\Automation\Trigger\Condition\AndCondition;
use idoit\Module\SyneticsFlows\Automation\Trigger\Condition\Condition;
use idoit\Module\SyneticsFlows\Automation\Trigger\Condition\OrCondition;
use idoit\Module\SyneticsFlows\Automation\Trigger\TriggerType\TriggerType;
use idoit\Module\SyneticsFlows\Serialization\Discriminator;
use idoit\Module\SyneticsFlows\Serialization\Serializer;
use isys_module_synetics_flows;

class PermissionService
{
    private const NOT_LICENSED_TRIGGER_TYPES = [
        'schedule',
    ];
    private const NOT_LICENSED_CONDITIONS = [];
    private const NOT_LICENSED_ACTIONS = [
        'call-command',
    ];

    /**
     * @return string[]
     */
    public static function getAvailableTriggerTypes(): array
    {
        if (isys_module_synetics_flows::isLicensed()) {
            return self::getTypes(TriggerType::class);
        }

        return self::NOT_LICENSED_TRIGGER_TYPES;
    }

    /**
     * @return string[]
     */
    public static function getAvailableConditions(): array
    {
        if (isys_module_synetics_flows::isLicensed()) {
            return self::getTypes(Condition::class);
        }

        return self::NOT_LICENSED_CONDITIONS;
    }

    /**
     * @return string[]
     */
    public static function getAvailableActions(): array
    {
        if (isys_module_synetics_flows::isLicensed()) {
            return self::getTypes(ActionType::class);
        }

        return self::NOT_LICENSED_ACTIONS;
    }

    /**
     * @param Automation $automation
     *
     * @return bool
     */
    public static function hasAccess(Automation $automation): bool
    {
        $trigger = $automation->getTrigger();
        if ($trigger && !in_array(self::getType(TriggerType::class, $trigger), self::getAvailableTriggerTypes())) {
            return false;
        }
        if ($automation->getCondition()) {
            $conditions = self::extractConditions([$automation->getCondition()]);
            foreach ($conditions as $condition) {
                if (!in_array(self::getType(Condition::class, $condition), self::getAvailableConditions())) {
                    return false;
                }
            }
        }
        if ($automation->getActions()) {
            foreach ($automation->getActions() as $action) {
                if (!in_array(self::getType(ActionType::class, $action), self::getAvailableActions())) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * @param array $conditions
     * @return array
     */
    private static function extractConditions(array $conditions): array
    {
        $items = [];

        foreach ($conditions as $condition) {
            if ($condition instanceof AndCondition || $condition instanceof OrCondition) {
                $items = [
                    ...$items,
                    ...self::extractConditions($condition->getItems())
                ];
                continue;
            }

            $items[] = $condition;
        }

        return $items;
    }

    /**
     * @param string $classWithMap
     * @param object $object
     *
     * @return string|null
     *
     * @throws \ReflectionException
     */
    private static function getType(string $classWithMap, object $object): ?string
    {
        $discriminators = Serializer::getClassDiscriminators($classWithMap);

        foreach ($discriminators as $discriminator) {
            if (!$discriminator instanceof Discriminator) {
                continue;
            }
            $type = $discriminator->getType($object);
            if ($type) {
                return $type;
            }
        }

        return null;
    }

    /**
     * @param string $classname
     *
     * @return array
     *
     * @throws \ReflectionException
     */
    private static function getTypes(string $classname): array
    {
        $discriminators = Serializer::getClassDiscriminators($classname);
        $types = [];
        foreach ($discriminators as $discriminator) {
            if (!$discriminator instanceof Discriminator) {
                continue;
            }
            $types = [
                ...$types,
                ...array_keys($discriminator->getClassmap())
            ];
        }

        return $types;
    }
}