<?php

namespace App\Services\Configurator;

use Mtc\MercuryDataModels\CarConfiguratorColour;
use Mtc\MercuryDataModels\CarConfiguratorCustomSection;
use Mtc\MercuryDataModels\CarConfiguratorEdition;
use Mtc\MercuryDataModels\CarConfiguratorEngine;
use Mtc\MercuryDataModels\CarConfiguratorExtra;
use Mtc\MercuryDataModels\CarConfiguratorInterior;
use Mtc\MercuryDataModels\CarConfiguratorModel;
use Mtc\MercuryDataModels\CarConfiguratorPackage;
use Mtc\MercuryDataModels\CarConfiguratorSection;
use Mtc\MercuryDataModels\CarConfiguratorTrim;
use Mtc\MercuryDataModels\CarConfiguratorWheel;
use RuntimeException;

/**
 * Old-id -> new-id bookkeeping for a cross-tenant configurator copy.
 *
 * Ids are grouped into "buckets" keyed by the configurator relationship they
 * belong to (model, colours, trims, ...). Restrictions and media_uses record the
 * entity they point at using a type string that, in real data, can take THREE
 * shapes for the same class:
 *
 *  - a morphMap alias (car-config-colour, ...) from {@see \Mtc\MercuryDataModels\DataProvider};
 *  - the fully-qualified class name (rows written before the class was added to
 *    the morphMap, and always for Package/Extra which are unmapped);
 *  - a relationship-name string (colours, packages, custom, ...) used by
 *    restriction type columns.
 *
 * {@see resolveType()} collapses all three onto the same bucket so an id can be
 * remapped regardless of how it was stored. Unknown type strings throw so a
 * schema change surfaces loudly rather than silently dropping a row.
 */
class ConfiguratorIdMap
{
    public const BUCKET_MODEL = 'model';
    public const BUCKET_SECTIONS = 'sections';
    public const BUCKET_CUSTOM = 'custom';
    public const BUCKET_EDITIONS = 'editions';
    public const BUCKET_TRIMS = 'trims';
    public const BUCKET_ENGINES = 'engines';
    public const BUCKET_COLOURS = 'colours';
    public const BUCKET_WHEELS = 'wheels';
    public const BUCKET_INTERIORS = 'interiors';
    public const BUCKET_PACKAGES = 'packages';
    public const BUCKET_EXTRAS = 'extras';

    /** bucket => model class (its FQCN is a valid type string). */
    private const CLASSES = [
        self::BUCKET_MODEL => CarConfiguratorModel::class,
        self::BUCKET_SECTIONS => CarConfiguratorSection::class,
        self::BUCKET_CUSTOM => CarConfiguratorCustomSection::class,
        self::BUCKET_EDITIONS => CarConfiguratorEdition::class,
        self::BUCKET_TRIMS => CarConfiguratorTrim::class,
        self::BUCKET_ENGINES => CarConfiguratorEngine::class,
        self::BUCKET_COLOURS => CarConfiguratorColour::class,
        self::BUCKET_WHEELS => CarConfiguratorWheel::class,
        self::BUCKET_INTERIORS => CarConfiguratorInterior::class,
        self::BUCKET_PACKAGES => CarConfiguratorPackage::class,
        self::BUCKET_EXTRAS => CarConfiguratorExtra::class,
    ];

    /** bucket => morphMap alias (Package/Extra are unmapped, so omitted). */
    private const ALIASES = [
        self::BUCKET_MODEL => 'car-config-model',
        self::BUCKET_SECTIONS => 'car-config-section',
        self::BUCKET_CUSTOM => 'car-config-custom-section',
        self::BUCKET_EDITIONS => 'car-config-edition',
        self::BUCKET_TRIMS => 'car-config-trim',
        self::BUCKET_ENGINES => 'car-config-engine',
        self::BUCKET_COLOURS => 'car-config-colour',
        self::BUCKET_WHEELS => 'car-config-wheel',
        self::BUCKET_INTERIORS => 'car-config-interior',
    ];

    /** @var array<string, array<int, int>> bucket => [oldId => newId] */
    private array $map = [];

    public function set(string $bucket, int $oldId, int $newId): void
    {
        $this->map[$bucket][$oldId] = $newId;
    }

    public function get(string $bucket, int $oldId): ?int
    {
        return $this->map[$bucket][$oldId] ?? null;
    }

    /**
     * Every bucket that can own media, in a stable order.
     *
     * @return string[]
     */
    public function mediaOwnerBuckets(): array
    {
        return array_keys(self::CLASSES);
    }

    /**
     * All type strings a bucket's rows can be stored under (morph alias, if any,
     * plus the FQCN). Used to match owner_type both ways when reading/deleting.
     *
     * @return string[]
     */
    public function ownerTypes(string $bucket): array
    {
        return array_values(array_filter([
            self::ALIASES[$bucket] ?? null,
            self::CLASSES[$bucket] ?? null,
        ]));
    }

    /**
     * Resolve any type-string shape (morph alias, FQCN or relationship-name
     * string) to the bucket that holds its ids.
     */
    public function resolveType(string $type): string
    {
        $byAlias = array_flip(self::ALIASES);
        if (isset($byAlias[$type])) {
            return $byAlias[$type];
        }

        $byClass = array_flip(self::CLASSES);
        if (isset($byClass[$type])) {
            return $byClass[$type];
        }

        // A relationship-name string is itself a bucket key.
        if (array_key_exists($type, self::CLASSES)) {
            return $type;
        }

        throw new RuntimeException("Unrecognised configurator type string [{$type}] while remapping ids.");
    }
}
