<?php

namespace CliqThemes\ORM\Form;

use InvalidArgumentException;

/**
 * Immutable value object describing how a form field should be persisted.
 */
class FieldStorageDescriptor
{
    public function __construct(
        public readonly string $field,
        public readonly string $target,
        public readonly array $options = [],
        public readonly array $definition = [],
        public readonly array $path = []
    ) {
        if ($field === '') {
            throw new InvalidArgumentException(esc_html('Field name cannot be empty.'));
        }

        if ($target === '') {
            throw new InvalidArgumentException(esc_html(sprintf('Storage target for field "%s" cannot be empty.', $field)));
        }
    }

    /**
     * Build a descriptor from a raw field definition.
     *
     * @param array<string, mixed> $definition
     * @param string[] $path
     */
    public static function fromDefinition(array $definition, array $path = []): self
    {
        if (!isset($definition['name']) || !is_string($definition['name'])) {
            throw new InvalidArgumentException(esc_html('Field definitions must include a string "name" key.'));
        }

        $storage = $definition['storage'] ?? null;
        $target = 'passthrough';
        $options = [];

        if (is_string($storage)) {
            $target = $storage;
        } elseif (is_array($storage)) {
            $targetGuess = $storage['target'] ?? $storage['strategy'] ?? null;

            if ($targetGuess === null && array_key_exists('table', $storage)) {
                $targetGuess = 'table';
            }

            $target = $targetGuess ?? $target;
            $options = $storage['options'] ?? [];

            $reserved = ['target', 'strategy', 'options'];
            foreach ($storage as $key => $value) {
                if (!in_array($key, $reserved, true)) {
                    $options[$key] = $value;
                }
            }
        }

        return new self($definition['name'], $target, $options, $definition, $path);
    }

    /**
     * Fetch an option from the descriptor, returning the provided default when missing.
     */
    public function option(string $key, mixed $default = null): mixed
    {
        return $this->options[$key] ?? $default;
    }
}
