<?php

declare(strict_types=1);

namespace KupShop\GraphQLBundle\ApiAdmin\Changes\ChangeExtension;

use KupShop\GraphQLBundle\ApiAdmin\Types\Changes\ChangeInterface;
use KupShop\GraphQLBundle\ApiAdmin\Types\Changes\ChangeMetadata;
use KupShop\GraphQLBundle\ApiAdmin\Types\Changes\Enum\ChangeActionType;
use KupShop\KafkaBundle\DBChanges\DBChangeTypeEnum;
use KupShop\KafkaBundle\DBChanges\MessageTypes\DBGenericMessage;
use KupShop\KupShopBundle\Util\Functional\Mapping;

abstract class AbstractChangeExtension implements ChangeExtensionInterface
{
    public function addChange(array $changes, ChangeInterface $change): array
    {
        $changes[] = $change;

        return $changes;
    }

    public function didChange(DBGenericMessage $message): bool
    {
        if (!($fields = $this->getFieldsMapForTable($message->getTableName()))) {
            return false;
        }

        return $message->didAnyOfFieldChange(array_keys($fields));
    }

    /**
     * Returns a mapping of table fields to GraphQL fields that should be watched by ChangesAPI.
     *
     * Format:
     * [
     *     'tableName' => [
     *         'db_field' => 'graphqlField',
     *         ...
     *     ]
     * ]
     *
     * @return array<string, array<string, string>> Table name => [DB field => GraphQL field]
     */
    abstract protected function getFieldsMap(): array;

    protected function getFieldsMapForTable(string $table): array
    {
        return $this->getFieldsMap()[$table] ?? [];
    }

    protected function createMetadata(DBGenericMessage $message, ?ChangeActionType $actionType = null): ChangeMetadata
    {
        $date = new \DateTimeImmutable();
        $date = $date->setTimestamp($message->getTimestamp());

        return new ChangeMetadata(
            $message->getOffset(),
            $date,
            $actionType ?: ChangeActionType::from($message->getEventType()->value),
        );
    }

    protected function getChangedFields(DBGenericMessage $message): array
    {
        $fields = $this->getFieldsMapForTable($message->getTableName());

        return array_unique(array_map(
            function (string $key) use ($fields) {
                return $fields[$key];
            },
            array_filter(
                $message->getChangedValuesKeys(),
                fn (string $key) => isset($fields[$key])
            )
        ));
    }

    protected function getCurrentAndPreviousValue(DBGenericMessage $message, string $key, mixed $onDeleteValue = null): array
    {
        $original = $current = $message->getValue($key);
        $previous = $message->getPreviousValue($key);

        if ($message->getEventType() === DBChangeTypeEnum::DELETE) {
            $current = $onDeleteValue;
            $previous = $original;
        }

        return [$current, $previous];
    }

    protected function createSubfieldMap(string $targetField, array $watchedFields): array
    {
        return Mapping::mapKeys($watchedFields, fn ($k, $v) => [$v, $targetField]);
    }
}
