<?php

declare (strict_types=1);
namespace PoPSchema\DirectiveCommons\StateServices;

use PoP\ComponentModel\App;
use PoP\ComponentModel\Engine\EngineIterationFieldSet;
use PoP\ComponentModel\Feedback\EngineIterationFeedbackStore;
use PoP\ComponentModel\Feedback\ObjectResolutionFeedback;
use PoP\ComponentModel\TypeResolvers\ConcreteTypeResolverInterface;
use PoP\ComponentModel\TypeResolvers\LeafOutputTypeResolverInterface;
use PoP\ComponentModel\TypeResolvers\ObjectType\ObjectTypeResolverInterface;
use PoP\ComponentModel\TypeResolvers\RelationalTypeResolverInterface;
use PoP\ComponentModel\TypeResolvers\UnionType\UnionTypeResolverInterface;
use PoP\ComponentModel\TypeSerialization\TypeSerializationServiceInterface;
use PoP\GraphQLParser\ASTNodes\ASTNodesFactory;
use PoP\GraphQLParser\Spec\Parser\Ast\AstInterface;
use PoP\GraphQLParser\Spec\Parser\Ast\Directive;
use PoP\GraphQLParser\Spec\Parser\Ast\FieldInterface;
use PoP\ComponentModel\Feedback\FeedbackItemResolution;
use PoP\Root\Services\AbstractBasicService;
use PoPSchema\DirectiveCommons\FeedbackItemProviders\FeedbackItemProvider;
use SplObjectStorage;
/** @internal */
class ObjectResolvedDynamicVariablesService extends AbstractBasicService implements \PoPSchema\DirectiveCommons\StateServices\ObjectResolvedDynamicVariablesServiceInterface
{
    private ?TypeSerializationServiceInterface $typeSerializationService = null;
    protected final function getTypeSerializationService() : TypeSerializationServiceInterface
    {
        if ($this->typeSerializationService === null) {
            /** @var TypeSerializationServiceInterface */
            $typeSerializationService = $this->instanceManager->getInstance(TypeSerializationServiceInterface::class);
            $this->typeSerializationService = $typeSerializationService;
        }
        return $this->typeSerializationService;
    }
    /**
     * Export the value of a field, assigning it to a dynamic variable,
     * to make it available to all subsequent directives.
     *
     * Can store the value either for an objectID, or a combination
     * of objectID + field.
     *
     * @param null|FieldInterface[] $dynamicVariableTargetFields
     */
    public function setObjectResolvedDynamicVariableInAppState(RelationalTypeResolverInterface $relationalTypeResolver, FieldInterface $field, object $object, string|int $id, mixed $value, bool $serializeValue, string $dynamicVariableName, ?array $dynamicVariableTargetFields, AstInterface $astNode, Directive $directive, EngineIterationFeedbackStore $engineIterationFeedbackStore) : void
    {
        $targetObjectTypeResolver = null;
        $isUnionTypeResolver = $relationalTypeResolver instanceof UnionTypeResolverInterface;
        if ($isUnionTypeResolver) {
            /** @var UnionTypeResolverInterface */
            $unionTypeResolver = $relationalTypeResolver;
            $targetObjectTypeResolver = $unionTypeResolver->getTargetObjectTypeResolver($object);
        } else {
            $targetObjectTypeResolver = $relationalTypeResolver;
        }
        /** @var ObjectTypeResolverInterface $targetObjectTypeResolver */
        if ($value !== null && $serializeValue) {
            /** @var ConcreteTypeResolverInterface */
            $fieldTypeResolver = $targetObjectTypeResolver->getFieldTypeResolver($field);
            if ($fieldTypeResolver instanceof LeafOutputTypeResolverInterface) {
                $value = $this->getTypeSerializationService()->serializeLeafOutputTypeValue($value, $fieldTypeResolver, $targetObjectTypeResolver, $field);
            }
        }
        /** @var SplObjectStorage<FieldInterface,array<string|int,array<string,mixed>>> SplObjectStorage<Field, [objectID => [dynamicVariableName => value]]> */
        $objectResolvedDynamicVariables = App::getState('object-resolved-dynamic-variables');
        /** @var bool */
        $showWarningsOnExportingDuplicateDynamicVariableName = App::getState('show-warnings-on-exporting-duplicate-dynamic-variable-name');
        /**
         * If passing null, execute on objectID for all fields
         */
        $wildcardField = ASTNodesFactory::getWildcardField();
        $dynamicVariableTargetFields = $dynamicVariableTargetFields ?? [$wildcardField];
        foreach ($dynamicVariableTargetFields as $targetField) {
            /**
             * If the variable already exists, then show a warning.
             *
             * When setting the dynamic variable on both a targeted Field
             * and the "wildcard field", the latter one may continuously
             * receive many values, so disable setting the warning in that
             * situation or the response will be inundated with
             * unwarranted warnings.
             */
            if ($showWarningsOnExportingDuplicateDynamicVariableName) {
                $addDynamicVariableAlreadySetWarningFeedback = \count($dynamicVariableTargetFields) === 1 || $targetField !== $wildcardField;
                if ($addDynamicVariableAlreadySetWarningFeedback && $objectResolvedDynamicVariables->offsetExists($targetField) && isset($objectResolvedDynamicVariables[$targetField][$id]) && \array_key_exists($dynamicVariableName, $objectResolvedDynamicVariables[$targetField][$id])) {
                    $this->addDynamicVariableAlreadySetWarningFeedback($targetObjectTypeResolver, $astNode, $directive, $dynamicVariableName, $id, $field, $engineIterationFeedbackStore);
                }
            }
            /** @var array<string|int,array<string,mixed>> */
            $targetFieldObjectResolvedDynamicVariables = $objectResolvedDynamicVariables[$targetField] ?? [];
            $targetFieldObjectResolvedDynamicVariables[$id][$dynamicVariableName] = $value;
            $objectResolvedDynamicVariables[$targetField] = $targetFieldObjectResolvedDynamicVariables;
        }
        /**
         * No `override(...)` call needed: `$objectResolvedDynamicVariables`
         * IS the SplObjectStorage instance already stored in AppState
         * (retrieved by reference above), so the in-place mutation is
         * already visible.
         */
    }
    protected function addDynamicVariableAlreadySetWarningFeedback(ObjectTypeResolverInterface $objectTypeResolver, AstInterface $astNode, Directive $directive, string $dynamicVariableName, string|int $id, FieldInterface $field, EngineIterationFeedbackStore $engineIterationFeedbackStore) : void
    {
        $engineIterationFeedbackStore->objectResolutionFeedbackStore->addWarning(new ObjectResolutionFeedback(new FeedbackItemResolution(FeedbackItemProvider::class, FeedbackItemProvider::W2, [$dynamicVariableName, $id]), $astNode, $objectTypeResolver, $directive, [$id => new EngineIterationFieldSet([$field])]));
    }
    /**
     * Duplicate all the dynamic variables for one Field into
     * another Field. It is used by @underJSONObjectProperty
     * to have the "advanced" Field have access to all state
     * set by the underlying Fieldl
     */
    public function copyObjectResolvedDynamicVariablesFromFieldToFieldInAppState(FieldInterface $fromField, FieldInterface $toField) : void
    {
        $this->copyObjectResolvedDynamicVariablesFromFieldToFieldsInAppState($fromField, [$toField]);
    }
    /**
     * @param FieldInterface[] $toFields
     */
    public function copyObjectResolvedDynamicVariablesFromFieldToFieldsInAppState(FieldInterface $fromField, array $toFields) : void
    {
        if ($toFields === []) {
            return;
        }
        /** @var SplObjectStorage<FieldInterface,array<string|int,array<string,mixed>>> SplObjectStorage<Field, [objectID => [dynamicVariableName => value]]> */
        $objectResolvedDynamicVariables = App::getState('object-resolved-dynamic-variables');
        if (!$objectResolvedDynamicVariables->offsetExists($fromField)) {
            return;
        }
        /** @var array<string|int,array<string,mixed>> */
        $fromObjectDynamicVariableNameValues = $objectResolvedDynamicVariables[$fromField];
        foreach ($toFields as $toField) {
            /**
             * Watch out! Do not override any state set in the toField!
             */
            if (!$objectResolvedDynamicVariables->offsetExists($toField)) {
                $objectResolvedDynamicVariables[$toField] = $fromObjectDynamicVariableNameValues;
                continue;
            }
            /** @var array<string|int,array<string,mixed>> */
            $toObjectDynamicVariableNameValues = $objectResolvedDynamicVariables[$toField];
            /** @var string|int $objectID */
            foreach ($fromObjectDynamicVariableNameValues as $objectID => $dynamicVariableNameValues) {
                /**
                 * Avoid array_merge: assign in place to skip the
                 * intermediate-array allocation per object ID.
                 */
                if (!isset($toObjectDynamicVariableNameValues[$objectID])) {
                    $toObjectDynamicVariableNameValues[$objectID] = $dynamicVariableNameValues;
                    continue;
                }
                foreach ($dynamicVariableNameValues as $dynamicVariableName => $dynamicVariableValue) {
                    $toObjectDynamicVariableNameValues[$objectID][$dynamicVariableName] = $dynamicVariableValue;
                }
            }
            $objectResolvedDynamicVariables[$toField] = $toObjectDynamicVariableNameValues;
        }
        /**
         * No `override(...)` call needed: `$objectResolvedDynamicVariables`
         * IS the SplObjectStorage instance already stored in AppState
         * (retrieved by reference above), so the in-place mutation is
         * already visible.
         */
    }
}
