<?php

namespace Shm\ShmAddons\EventTicketAddon\Types\HallType;

use Shm\Shm;
use Shm\ShmDB\mDB;
use Shm\ShmRPC\ShmRPCCodeGen\TSType;
use Shm\ShmTypes\BaseType;
use Traversable;

class HallType extends BaseType
{
    public string $type = 'hall';


    public bool $compositeType = true;




    private static function isTempClientId(mixed $id): bool
    {
        return is_string($id) && !mDB::isMongoId($id);
    }

    /**
     * @return \MongoDB\BSON\ObjectId|null
     */
    private static function toMongoObjectId(mixed $id): ?\MongoDB\BSON\ObjectId
    {
        if ($id instanceof \MongoDB\BSON\ObjectId) {
            return $id;
        }

        $converted = mDB::id($id);

        return $converted instanceof \MongoDB\BSON\ObjectId ? $converted : null;
    }

    /**
     * @param array<string, mixed> $document
     * @return array{updateOne: array{0: array, 1: array}}
     */
    private static function bulkUpdateOneOp(array $document): array
    {
        $filter = mDB::replaceStringToObjectIds(['_id' => $document['_id']]);
        $set = $document;
        unset($set['_id']);

        return [
            'updateOne' => [
                $filter,
                ['$set' => $set],
            ],
        ];
    }

    /**
     * @param list<array{updateOne: array{0: array, 1: array}}> $operations
     */
    private static function flushBulkUpdates(array $operations): void
    {
        if ($operations === []) {
            return;
        }

        HallElements::_collection()->bulkWrite($operations);
    }


    public function tsType(): TSType
    {



        $TSType = new TSType('string');
        $TSType->comment($this->title);

        return $TSType;
    }



    public function normalize(mixed $values, $addDefaultValues = false, string | null $processId = null): mixed
    {



        // Пустое значение — зал не задан, сохранять нечего.
        if (!$values) return null;


        $isArray = is_array($values) || $values instanceof Traversable;

        // Если пришла уже сохранённая ссылка на зал (UUID-строка), нормализация не нужна —
        // элементы уже лежат в HallElements, просто пробрасываем идентификатор дальше.
        if (!$isArray && is_string($values)) {
            return $values;
        }




        // --- Дальше ожидаем массив элементов схемы зала (места, секторы, стены и т.д.) ---
        // Клиент шлёт полный снимок схемы: новые элементы с временными _id (tml_*),
        // существующие — с реальными Mongo ObjectId. Задача normalize:
        // 1) вставить новые, 
        // 2) обновить существующие, 
        // 3) удалить то, чего больше нет в снимке,
        // 4) вернуть hallUUID как значение поля (вместо массива элементов).

        $hallUUID = null;
        // Все элементы одного зала должны нести один и тот же hallUUID —
        // без него нельзя понять, к какому залу относятся документы и что чистить при sync.
        foreach ($values as $value) {
            if (!isset($value['hallUUID'])) {
                Shm::error('У элементов зала отсутствует идентификатор зала');
            }
            $hallUUID = $value['hallUUID'];
        }



        // Карта временных клиентских id → реальных Mongo ObjectId после insert.
        // Нужна, чтобы:
        // - связать места/столы с только что созданным сектором (поле sector),
        // - собрать итоговый список _id для удаления «лишних» документов.
        $tmpIdToMongoId = [];

        // ========== ШАГ 1: секторы первыми ==========
        // Секторы обрабатываем отдельно и раньше остальных, потому что другие элементы
        // (места и т.п.) ссылаются на сектор через поле sector. Если сектор новый
        // (id вида tml_...), его Mongo _id ещё неизвестен — сначала создаём сектор,
        // запоминаем соответствие tmp→mongo, и только потом пишем зависимые элементы.
        $sectorDocsToInsert = [];
        $sectorTmpIds = [];
        $sectorBulkUpdates = [];

        foreach ($values as $value) {
            $valueType =  ($value['type'] ?? null);
            if (!is_array($value) || !in_array($valueType, ['sector', 'sector_polygon'])) {
                continue;
            }

            if (self::isTempClientId($value['_id'] ?? null)) {
                // Новый сектор: убираем временный _id — Mongo сам сгенерирует ObjectId.
                $doc = $value;
                unset($doc['_id']);
                $sectorDocsToInsert[] = $doc;
                $sectorTmpIds[] = $value['_id'];
            } else {
                // Уже существующий сектор — готовим updateOne в bulk.
                $sectorBulkUpdates[] = self::bulkUpdateOneOp($value);
            }
        }

        // Массовая вставка новых секторов и фиксация tmpId → inserted ObjectId
        // (порядок insertMany совпадает с порядком sectorTmpIds).
        if ($sectorDocsToInsert !== []) {
            $insertResult = HallElements::_collection()->insertMany($sectorDocsToInsert);
            $insertedIds = $insertResult->getInsertedIds();
            foreach ($sectorTmpIds as $i => $tmpId) {
                $tmpIdToMongoId[$tmpId] = $insertedIds[$i];
            }
        }

        // Обновление уже существующих секторов одним bulkWrite.
        self::flushBulkUpdates($sectorBulkUpdates);

        // ========== ШАГ 2: остальные элементы (кроме sector и sector_polygon) ==========
        // sector_polygon пропускаем здесь намеренно (не пишем/не обновляем в этом проходе).
        // Для элементов с привязкой к сектору подменяем временный sector id на реальный ObjectId.
        $otherDocsToInsert = [];
        $otherTmpIds = [];
        $otherBulkUpdates = [];

        foreach ($values as $value) {

            $valueType =  ($value['type'] ?? null);
            if (!is_array($value) || in_array($valueType, ['sector', 'sector_polygon'])) {
                continue;
            }

            $doc = $value;
            // Место/стол могло ссылаться на сектор по временному id с клиента —
            // после шага 1 у нас уже есть настоящий Mongo _id сектора.
            if (isset($doc['sector']) && self::isTempClientId($doc['sector'])) {
                $doc['sector'] = $tmpIdToMongoId[$doc['sector']];
            }

            if (self::isTempClientId($doc['_id'] ?? null)) {
                // Новый элемент схемы — insert без временного _id.
                $tmpKey = $doc['_id'];
                unset($doc['_id']);
                $otherDocsToInsert[] = $doc;
                $otherTmpIds[] = $tmpKey;
            } else {
                // Существующий элемент — update по реальному _id.
                $otherBulkUpdates[] = self::bulkUpdateOneOp($doc);
            }
        }


        // Вставка новых элементов + расширение карты tmp→mongo (для финального sync).
        if ($otherDocsToInsert !== []) {
            $insertResult = HallElements::_collection()->insertMany($otherDocsToInsert);
            $insertedIds = $insertResult->getInsertedIds();
            foreach ($otherTmpIds as $i => $tmpId) {
                $tmpIdToMongoId[$tmpId] = $insertedIds[$i];
            }
        }

        self::flushBulkUpdates($otherBulkUpdates);

        // ========== ШАГ 3: полный список актуальных _id схемы ==========
        // Собираем Mongo ObjectId всех элементов из входящего снимка
        // (для новых — из карты tmpIdToMongoId, для старых — из самого _id).
        // Этот список — «истина» текущего состояния зала на клиенте.
        $allMongoIds = [];
        foreach ($values as $value) {
            if (!is_array($value) || !isset($value['_id'])) {
                continue;
            }

            $clientId = $value['_id'];
            if (self::isTempClientId($clientId)) {
                // Элемент был новым: если по какой-то причине не попал в insert — пропускаем.
                if (!isset($tmpIdToMongoId[$clientId])) {
                    continue;
                }
                $mongoId = self::toMongoObjectId($tmpIdToMongoId[$clientId]);
            } else {
                $mongoId = self::toMongoObjectId($clientId);
            }

            if ($mongoId !== null) {
                $allMongoIds[] = $mongoId;
            }
        }



        // ========== ШАГ 4: sync-удаление ==========
        // Всё, что есть в БД у этого hallUUID, но отсутствует во входящем снимке —
        // считается удалённым на клиенте и вычищается из HallElements.
        // $nin = «не из списка актуальных id».
        HallElements::_collection()->deleteMany(['hallUUID' => $hallUUID, '_id' => ['$nin' => $allMongoIds]]);


        // В поле схемы сохраняем не массив элементов, а идентификатор зала.
        return  $hallUUID;
    }
}
