<?php

namespace CliqThemes\ORM\Form;

use CliqThemes\ORM\Form\Storage\JsonColumnStrategy;
use CliqThemes\ORM\Form\Storage\PassthroughStrategy;
use CliqThemes\ORM\Form\Storage\PivotStorageStrategy;
use CliqThemes\ORM\Form\Storage\TableStorageStrategy;
use CliqThemes\ORM\Model;
use InvalidArgumentException;

/**
 * Service locator for storage strategies referenced within form schemas.
 */
class StorageStrategyRegistry
{
    /** @var array<string, callable> */
    protected array $factories = [];

    /**
     * @param bool $registerDefaults When false the registry starts empty, allowing consumers to register strategies manually.
     */
    public function __construct(bool $registerDefaults = true)
    {
        if ($registerDefaults) {
            $this->registerDefaults();
        }
    }

    /**
     * Register a strategy factory for the provided target identifier.
     */
    public function register(string $target, callable $factory): void
    {
        $this->factories[$target] = $factory;
    }

    /**
     * Determine if a factory has been registered for the target.
     */
    public function has(string $target): bool
    {
        return isset($this->factories[$target]);
    }

    /**
     * Resolve a storage strategy using the registered factory.
     *
     * @param array<string, mixed> $context
     */
    public function resolve(string $target, FieldStorageDescriptor $descriptor, array $context = []): StorageStrategy
    {
        if (!$this->has($target)) {
            throw new InvalidArgumentException(esc_html(sprintf('No storage strategy registered for target "%s".', $target)));
        }

        $factory = $this->factories[$target];

        $strategy = $factory($descriptor, $context);

        if (!$strategy instanceof StorageStrategy) {
            throw new InvalidArgumentException(esc_html(sprintf('Factory for target "%s" must return an instance of %s.', $target, StorageStrategy::class)));
        }

        return $strategy;
    }

    /**
     * Register the built-in storage strategies.
     */
    public function registerDefaults(): void
    {
        $this->register('passthrough', static function (FieldStorageDescriptor $descriptor, array $context): StorageStrategy {
            $model = $context['model'] ?? null;

            if (!$model instanceof Model) {
                throw new InvalidArgumentException(esc_html('The passthrough storage strategy requires a model instance in the resolution context.'));
            }

            return new PassthroughStrategy($model);
        });

        $this->register('table', static function (FieldStorageDescriptor $descriptor, array $context): StorageStrategy {
            return new TableStorageStrategy($descriptor->options);
        });

        $this->register('pivot', static function (FieldStorageDescriptor $descriptor, array $context): StorageStrategy {
            return new PivotStorageStrategy($descriptor->options);
        });

        $this->register('json', static function (FieldStorageDescriptor $descriptor, array $context): StorageStrategy {
            return new JsonColumnStrategy($descriptor->options);
        });
    }
}
