<?php

namespace Storeplugin\PDFInvoice\Framework\Rest;

defined('ABSPATH') || exit;

/**
 * Build REST JSON schemas from framework field definitions.
 *
 * The schema intentionally matches persisted values rather than UI widget
 * semantics so REST writes align with the framework's storage model.
 */
final class FieldSchemaFactory
{
    /**
     * Build a REST schema for a field definition.
     *
     * @param array $field
     * @return array<string, mixed>
     */
    public static function buildForField(array $field): array
    {
        $type = (string) ($field['type'] ?? 'text');

        if ($type === 'repeater') {
            return self::buildRepeaterSchema($field);
        }

        if (self::fieldStoresArrayValues($field)) {
            return self::buildArrayFieldSchema($field);
        }

        return self::buildScalarFieldSchema($field);
    }

    /**
     * Build the REST schema for a scalar field.
     *
     * @param array $field
     * @return array<string, mixed>
     */
    private static function buildScalarFieldSchema(array $field): array
    {
        $type = (string) ($field['type'] ?? 'text');
        $schema = [
            'type' => 'string',
        ];

        switch ($type) {
            case 'checkbox':
            case 'switch':
                $schema['enum'] = ['0', '1'];
                break;

            case 'number':
            case 'range':
                $schema['pattern'] = '^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$';
                break;

            case 'email':
                $schema['format'] = 'email';
                break;

            case 'url':
            case 'file_upload':
                $schema['format'] = 'uri';
                break;

            case 'color':
                $schema['format'] = 'hex-color';
                break;

            case 'tel':
                $schema['pattern'] = '^[\d\s\-\(\)\+]+$';
                break;

            case 'select':
            case 'select2':
            case 'disabled_select':
            case 'radio':
                $enumValues = self::resolveFieldOptionKeys($field);
                if ($enumValues !== []) {
                    $schema['enum'] = $enumValues;
                }
                break;

            case 'text':
            case 'textarea':
            case 'icon_picker':
            case 'wysiwyg':
                $schema = self::applyStringLengthConstraints($schema, $field);
                break;
        }

        return $schema;
    }

    /**
     * Build the REST schema for a field that stores multiple values.
     *
     * @param array $field
     * @return array<string, mixed>
     */
    private static function buildArrayFieldSchema(array $field): array
    {
        $itemsSchema = [
            'type' => 'string',
        ];

        $enumValues = self::resolveFieldOptionKeys($field);
        if ($enumValues !== []) {
            $itemsSchema['enum'] = $enumValues;
        }

        return [
            'type' => 'array',
            'items' => $itemsSchema,
        ];
    }

    /**
     * Build the REST schema for a repeater field.
     *
     * @param array $field
     * @return array<string, mixed>
     */
    private static function buildRepeaterSchema(array $field): array
    {
        $properties = [];
        $subFields = isset($field['fields']) && is_array($field['fields']) ? $field['fields'] : [];

        foreach ($subFields as $subField) {
            if (!is_array($subField)) {
                continue;
            }

            $subId = \sanitize_key((string) ($subField['id'] ?? ''));
            if ($subId === '') {
                continue;
            }

            $properties[$subId] = self::buildForField($subField);
        }

        return [
            'type' => 'array',
            'items' => [
                'type' => 'object',
                'properties' => $properties,
                'additionalProperties' => false,
            ],
        ];
    }

    /**
     * Determine whether a field stores an array of values.
     *
     * @param array $field
     * @return bool
     */
    private static function fieldStoresArrayValues(array $field): bool
    {
        $type = (string) ($field['type'] ?? 'text');

        if ($type === 'checkbox') {
            return isset($field['options']) && is_array($field['options']) && $field['options'] !== [];
        }

        if ($type !== 'select2') {
            return false;
        }

        return self::select2SupportsMultiple($field['support'] ?? ['single', 'search']);
    }

    /**
     * Determine whether a Select2 field persists multiple selected values.
     *
     * @param mixed $support
     * @return bool
     */
    private static function select2SupportsMultiple($support): bool
    {
        if (is_string($support)) {
            $support = [$support];
        }

        if (!is_array($support)) {
            return false;
        }

        foreach ($support as $featureName => $featureValue) {
            if (is_string($featureName) && strtolower($featureName) === 'multi' && $featureValue) {
                return true;
            }

            if (is_string($featureValue) && strtolower($featureValue) === 'multi') {
                return true;
            }
        }

        return false;
    }

    /**
     * Apply min/max length constraints for text-like fields.
     *
     * @param array<string, mixed> $schema
     * @param array                $field
     * @return array<string, mixed>
     */
    private static function applyStringLengthConstraints(array $schema, array $field): array
    {
        if (isset($field['minlength']) && is_numeric($field['minlength'])) {
            $schema['minLength'] = max(0, (int) $field['minlength']);
        }

        if (isset($field['maxlength']) && is_numeric($field['maxlength'])) {
            $schema['maxLength'] = max(0, (int) $field['maxlength']);
        }

        return $schema;
    }

    /**
     * Resolve all possible option keys for enum-constrained schemas.
     *
     * @param array $field
     * @return array<int, string>
     */
    private static function resolveFieldOptionKeys(array $field): array
    {
        $keys = [];

        if (isset($field['options']) && is_array($field['options'])) {
            self::appendOptionKeys($keys, $field['options']);
        }

        if (isset($field['options_when']['map']) && is_array($field['options_when']['map'])) {
            foreach ($field['options_when']['map'] as $mappedOptions) {
                if (is_array($mappedOptions)) {
                    self::appendOptionKeys($keys, $mappedOptions);
                }
            }
        }

        return array_keys($keys);
    }

    /**
     * Add option keys from a flat or rich option definition array.
     *
     * @param array<string, bool> $keys
     * @param array               $options
     * @return void
     */
    private static function appendOptionKeys(array &$keys, array $options): void
    {
        foreach ($options as $optionValue => $_) {
            $keys[(string) $optionValue] = true;
        }
    }
}
