<?php
// components/Form/Validation.php
// Centralized validation rules normalization and helpers for server and client
namespace Components\Form;

class Validation
{
    /**
     * Parse a date-like value to a Unix timestamp.
     * Accepts formats like YYYY-MM-DD, YYYY-MM-DDTHH:MM, or any strtotime parseable string.
     */
    public static function parseDateToTs($val): ?int
    {
        if ($val === '' || $val === null) return null;
        if (is_int($val)) return $val;
        $s = is_string($val) ? trim($val) : '';
        if ($s === '') return null;
        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $s)) {
            $ts = strtotime($s . ' 00:00:00');
            if ($ts !== false) return $ts;
        }
        // Support HTML datetime-local format
        if (preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/', $s)) {
            $s2 = str_replace('T', ' ', $s);
            if (strlen($s2) === 16) {
                $s2 .= ':00';
            }
            $ts = strtotime($s2);
            if ($ts !== false) return $ts;
        }
        $ts = strtotime($s);
        if ($ts !== false) return $ts;
        return null;
    }
    public static function getFieldLabel(string $name, array $fields): string
    {
        $def = $fields[$name] ?? [];
        $cfgKey = strtoupper($name);
        $fallback = method_exists(__NAMESPACE__ . '\Engine', 'labelFromKey')
            ? Engine::labelFromKey($name)
            : $name;

        $label = \mh_lng($cfgKey . '_TITLE', $def['label'] ?? $fallback);
        return html_entity_decode($label, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    }
    public static function translateCompareMessage(string $msg, $offset, string $otherFieldName, array $fields): string
    {
        $translated = \mh_lng($msg, $msg);
        if (strpos($translated, '%') !== false) {
            $otherLabel = self::getFieldLabel($otherFieldName, $fields);
            // Support both single %s and multiple %s (using numbered placeholders recommended)
            try {
                // If it has numbered placeholders like %1$s, use them.
                // Otherwise sprintf will just assign them in order.
                $args = [abs($offset), $otherLabel];
                $translated = sprintf($translated, ...$args);
            } catch (\Throwable $e) {
                // fallback
            }
        }

        // Ensure we have a plain UTF-8 string without HTML entities for use in JS or escaped PHP output
        $translated = html_entity_decode($translated, ENT_QUOTES | ENT_HTML5, 'UTF-8');

        if (function_exists('mh_force_utf8')) {
            $translated = mh_force_utf8($translated);
        }
        return $translated;
    }
    /**
     * Normalize YAML field validate blocks to a compact rule set per field for client consumption.
     * - Keeps: type, required, min, max, compare (normalized to array of rules)
     * - Drops: unknown keys
     * @param array $fields Blueprint form fields
     * @return array<string, array>
     */
    public static function toClientSchema(array $fields): array
    {
        $out = [];
        foreach ($fields as $name => $def) {
            if (!is_array($def)) continue;
            $v = isset($def['validate']) && is_array($def['validate']) ? $def['validate'] : [];
            $type = strtolower((string)($v['type'] ?? 'string'));
            $defType = strtolower((string)($def['type'] ?? ''));
            if ($defType === 'i18n_text' || $defType === 'i18n_textarea') {
                $type = 'i18n';
            }
            // Prefer field type when it is a specialized date type and validate.type is not explicitly set
            if (($type === '' || $type === 'string') && ($defType === 'date' || $defType === 'date_time')) {
                $type = $defType;
            }
            $rule = [
                'type' => $type,
            ];
            if (!empty($v['required'])) { $rule['required'] = true; }
            if (!empty($v['required_all'])) { $rule['required_all'] = true; }
            if (isset($v['min']) && $v['min'] !== '') { $rule['min'] = is_numeric($v['min']) ? 0 + $v['min'] : (string)$v['min']; }
            if (isset($v['max']) && $v['max'] !== '') { $rule['max'] = is_numeric($v['max']) ? 0 + $v['max'] : (string)$v['max']; }

            // compare: allow single or list
            $cmp = $v['compare'] ?? null;
            if ($cmp) {
                $rule['compare'] = [];
                $arr = (array)$cmp;
                $items = (isset($arr['field']) && is_string($arr['field'])) ? [$arr] : array_values($arr);
                foreach ($items as $c) {
                    if (!is_array($c)) continue;
                    $cf = (string)($c['field'] ?? '');
                    if ($cf === '') continue;
                    $op = strtolower((string)($c['op'] ?? 'gte'));
                    $offset = $c['offset'] ?? 0;
                    $msg = (string)($c['message'] ?? '');
                    $item = ['field' => $cf, 'op' => $op];
                    if ($offset !== null) { $item['offset'] = $offset; }
                    if ($msg !== '') {
                        $item['message'] = self::translateCompareMessage($msg, $offset, $cf, $fields);
                    }
                    $rule['compare'][] = $item;
                }
                if (empty($rule['compare'])) unset($rule['compare']);
            }

            $out[$name] = $rule;
        }
        return $out;
    }

    /**
     * Provide the default server-side validators as callables.
     * Mirrors the logic used historically by the Engine.
     * @return array<string, callable>
     */
    public static function defaultValidators(): array
    {
        return [
            'string' => function ($value, array $v) {
                $value = (string)$value;
                $errors = [];
                if (($v['required'] ?? false) && trim($value) === '') {
                    $errors[] = \mh_lng('UI_VALIDATION_REQUIRED');
                }
                $min = isset($v['min']) && $v['min'] !== '' ? (int)$v['min'] : null;
                $max = isset($v['max']) && $v['max'] !== '' ? (int)$v['max'] : null;
                $len = strlen($value);
                if ($min !== null && $len < $min) $errors[] = sprintf(\mh_lng('UI_VALIDATION_MIN_LENGTH'), $min);
                if ($max !== null && $len > $max) $errors[] = sprintf(\mh_lng('UI_VALIDATION_MAX_LENGTH'), $max);

                if (isset($v['pattern']) && $v['pattern'] !== '' && $value !== '') {
                    $pattern = $v['pattern'];
                    // Automatically wrap with delimiters if missing
                    if (strpos($pattern, '/') !== 0) {
                        $pattern = '/' . str_replace('/', '\/', $pattern) . '/';
                    }
                    if (!preg_match($pattern, $value)) {
                        $msg = $v['message'] ?? 'UI_VALIDATION_PATTERN';
                        $errors[] = \mh_lng($msg, $msg);
                    }
                }
                return $errors;
            },
            'email' => function ($value, array $v) {
                $value = (string)$value;
                $errors = [];
                if (($v['required'] ?? false) && trim($value) === '') {
                    $errors[] = \mh_lng('UI_VALIDATION_REQUIRED');
                    return $errors;
                }
                if ($value === '') { return $errors; }
                // Use PHP's filter for RFC-compliant emails
                if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
                    $errors[] = \mh_lng('UI_VALIDATION_EMAIL');
                    return $errors;
                }
                $min = isset($v['min']) && $v['min'] !== '' ? (int)$v['min'] : null;
                $max = isset($v['max']) && $v['max'] !== '' ? (int)$v['max'] : null;
                $len = strlen($value);
                if ($min !== null && $len < $min) $errors[] = sprintf(\mh_lng('UI_VALIDATION_MIN_LENGTH'), $min);
                if ($max !== null && $len > $max) $errors[] = sprintf(\mh_lng('UI_VALIDATION_MAX_LENGTH'), $max);
                return $errors;
            },
            'integer' => function ($value, array $v) {
                $errors = [];
                if (($v['required'] ?? false) && ($value === '' || $value === null)) {
                    $errors[] = \mh_lng('UI_VALIDATION_REQUIRED');
                    return $errors;
                }
                if ($value === '' || $value === null) return $errors; // not required and empty
                if (!is_numeric($value) || (string)(int)$value !== (string)$value) {
                    $errors[] = \mh_lng('UI_VALIDATION_INTEGER');
                    return $errors;
                }
                $int = (int)$value;
                $min = isset($v['min']) && $v['min'] !== '' ? (int)$v['min'] : null;
                $max = isset($v['max']) && $v['max'] !== '' ? (int)$v['max'] : null;
                if ($min !== null && $int < $min) $errors[] = sprintf(\mh_lng('UI_VALIDATION_MIN_VALUE'), $min);
                if ($max !== null && $int > $max) $errors[] = sprintf(\mh_lng('UI_VALIDATION_MAX_VALUE'), $max);
                return $errors;
            },
            'number' => function ($value, array $v) {
                $errors = [];
                if (($v['required'] ?? false) && ($value === '' || $value === null)) {
                    $errors[] = \mh_lng('UI_VALIDATION_REQUIRED');
                    return $errors;
                }
                if ($value === '' || $value === null) return $errors;
                if (!is_numeric($value)) {
                    $errors[] = \mh_lng('UI_VALIDATION_NUMBER');
                    return $errors;
                }
                $num = (float)$value;
                $min = isset($v['min']) && $v['min'] !== '' ? (float)$v['min'] : null;
                $max = isset($v['max']) && $v['max'] !== '' ? (float)$v['max'] : null;
                if ($min !== null && $num < $min) $errors[] = sprintf(\mh_lng('UI_VALIDATION_MIN_VALUE'), $min);
                if ($max !== null && $num > $max) $errors[] = sprintf(\mh_lng('UI_VALIDATION_MAX_VALUE'), $max);
                return $errors;
            },
            'float' => function ($value, array $v) {
                $errors = [];
                if (($v['required'] ?? false) && ($value === '' || $value === null)) {
                    $errors[] = \mh_lng('UI_VALIDATION_REQUIRED');
                    return $errors;
                }
                if ($value === '' || $value === null) return $errors;
                if (!is_numeric($value)) {
                    $errors[] = \mh_lng('UI_VALIDATION_NUMBER');
                    return $errors;
                }
                $num = (float)$value;
                $min = isset($v['min']) && $v['min'] !== '' ? (float)$v['min'] : null;
                $max = isset($v['max']) && $v['max'] !== '' ? (float)$v['max'] : null;
                if ($min !== null && $num < $min) $errors[] = sprintf(\mh_lng('UI_VALIDATION_MIN_VALUE'), $min);
                if ($max !== null && $num > $max) $errors[] = sprintf(\mh_lng('UI_VALIDATION_MAX_VALUE'), $max);
                return $errors;
            },
            'boolean' => function ($value, array $v) {
                $errors = [];
                if (($v['required'] ?? false) && $value !== 'on' && $value !== '1' && $value !== 'true') {
                    $errors[] = \mh_lng('UI_VALIDATION_REQUIRED');
                }
                return $errors;
            },
            'select' => function ($value, array $v, array $def = []) {
                $errors = [];
                $allowed = [];
                if (isset($def['options']) && is_array($def['options'])) {
                    $opts = $def['options'];
                    $isAssoc = array_keys($opts) !== range(0, count($opts) - 1);
                    if ($isAssoc) {
                        $allowed = array_map('strval', array_keys($opts));
                    } else {
                        foreach ($opts as $opt) {
                            if (is_array($opt)) {
                                $allowed[] = (string)($opt['value'] ?? $opt['id'] ?? $opt['key'] ?? '');
                            } else {
                                $allowed[] = (string)$opt;
                            }
                        }
                    }
                } elseif (isset($def['values']) && is_array($def['values'])) {
                    $vals = $def['values'];
                    $isAssoc = array_keys($vals) !== range(0, count($vals) - 1);
                    if ($isAssoc) {
                        $allowed = array_map('strval', array_keys($vals));
                    } else {
                        foreach ($vals as $val) {
                            if (is_array($val)) {
                                $allowed[] = (string)($val['value'] ?? $val['id'] ?? $val['key'] ?? '');
                            } else {
                                $allowed[] = (string)$val;
                            }
                        }
                    }
                }

                if (is_array($value)) {
                    $values = [];
                    foreach ($value as $item) {
                        if ($item === '' || $item === null) {
                            continue;
                        }
                        if (is_array($item)) {
                            $id = $item['id'] ?? ($item['value'] ?? ($item['key'] ?? null));
                            if ($id === null || $id === '') {
                                continue;
                            }
                            $values[] = (string)$id;
                        } else {
                            $values[] = (string)$item;
                        }
                    }

                    if (($v['required'] ?? false) && count($values) < 1) {
                        $errors[] = \mh_lng('UI_VALIDATION_SELECT_VALUE');
                        return $errors;
                    }

                    if (!empty($allowed)) {
                        foreach ($values as $itemValue) {
                            if (!in_array($itemValue, $allowed, true)) {
                                $errors[] = \mh_lng('UI_VALIDATION_INVALID_SELECTION');
                                break;
                            }
                        }
                    }

                    return $errors;
                }

                if (($v['required'] ?? false) && ($value === '' || $value === null)) {
                    $errors[] = \mh_lng('UI_VALIDATION_SELECT_VALUE');
                    return $errors;
                }

                if ($value !== '' && $value !== null && !empty($allowed)) {
                    if (!in_array((string)$value, $allowed, true)) {
                        $errors[] = \mh_lng('UI_VALIDATION_INVALID_SELECTION');
                    }
                }
                return $errors;
            },
            'checkbox' => function ($value, array $v) {
                $errors = [];
                // Support both single checkbox (boolean) and checkbox group (array)
                if (is_array($value)) {
                    if (($v['required'] ?? false) && count($value) < 1) {
                        $errors[] = \mh_lng('UI_VALIDATION_SELECT_AT_LEAST_ONE');
                    }
                    return $errors;
                }
                if (($v['required'] ?? false) && $value !== 'on' && $value !== '1' && $value !== 'true') {
                    $errors[] = \mh_lng('UI_VALIDATION_REQUIRED');
                }
                return $errors;
            },
            'date' => function($value, array $v) {
                $errors = [];
                $s = is_string($value) ? trim($value) : '';
                if (($v['required'] ?? false) && $s === '') { $errors[] = \mh_lng('UI_VALIDATION_REQUIRED'); return $errors; }
                if ($s === '') { return $errors; }
                $ts = Validation::parseDateToTs($s);
                if ($ts === null) { $errors[] = \mh_lng('UI_VALIDATION_DATE'); return $errors; }
                if (isset($v['min']) && $v['min'] !== '') {
                    $minTs = Validation::parseDateToTs($v['min']);
                    if ($minTs !== null && $ts < $minTs) { $errors[] = sprintf(\mh_lng('UI_VALIDATION_DATE_AFTER'), (string)$v['min']); }
                }
                if (isset($v['max']) && $v['max'] !== '') {
                    $maxTs = Validation::parseDateToTs($v['max']);
                    if ($maxTs !== null && $ts > $maxTs) { $errors[] = sprintf(\mh_lng('UI_VALIDATION_DATE_BEFORE'), (string)$v['max']); }
                }
                return $errors;
            },
            'date_time' => function($value, array $v) {
                $errors = [];
                $s = is_string($value) ? trim($value) : '';
                if (($v['required'] ?? false) && $s === '') { $errors[] = \mh_lng('UI_VALIDATION_REQUIRED'); return $errors; }
                if ($s === '') { return $errors; }
                $ts = Validation::parseDateToTs($s);
                if ($ts === null) { $errors[] = \mh_lng('UI_VALIDATION_DATE_TIME'); return $errors; }
                if (isset($v['min']) && $v['min'] !== '') {
                    $minTs = Validation::parseDateToTs($v['min']);
                    if ($minTs !== null && $ts < $minTs) { $errors[] = sprintf(\mh_lng('UI_VALIDATION_DATE_AFTER'), (string)$v['min']); }
                }
                if (isset($v['max']) && $v['max'] !== '') {
                    $maxTs = Validation::parseDateToTs($v['max']);
                    if ($maxTs !== null && $ts > $maxTs) { $errors[] = sprintf(\mh_lng('UI_VALIDATION_DATE_BEFORE'), (string)$v['max']); }
                }
                return $errors;
            },
            'i18n' => function($value, array $v) {
                $errors = [];
                $arr = is_array($value) ? $value : [];
                // trim values
                $trimmed = [];
                foreach ($arr as $k => $val) { $trimmed[$k] = is_string($val) ? trim($val) : ''; }
                $requiredAll = !empty($v['required_all']);
                $requiredAny = !empty($v['required']) && !$requiredAll;

                if ($requiredAll) {
                    // Determine languages to require from live env if available
                    $langs = [];
                    if (function_exists('mh_get_language_list')) {
                        try { $langs = (array) \mh_get_language_list(); } catch (\Throwable $e) { $langs = []; }
                    }
                    $ids = [];
                    foreach ($langs as $lang) { if (is_array($lang) && !empty($lang['id'])) { $ids[] = (int)$lang['id']; } }
                    if (empty($ids)) { $ids = array_map('intval', array_keys($trimmed)); }
                    foreach ($ids as $lid) {
                        $lidStr = (string)$lid;
                        $val = $trimmed[$lid] ?? $trimmed[$lidStr] ?? '';
                        if ($val === '') { $errors[] = \mh_lng('UI_VALIDATION_I18N_ALL_REQUIRED'); break; }
                    }
                } elseif ($requiredAny) {
                    $hasAny = false; foreach ($trimmed as $val) { if ($val !== '') { $hasAny = true; break; } }
                    if (!$hasAny) { $errors[] = \mh_lng('UI_VALIDATION_I18N_ANY_REQUIRED'); }
                }

                $min = isset($v['min']) && $v['min'] !== '' ? (int)$v['min'] : null;
                $max = isset($v['max']) && $v['max'] !== '' ? (int)$v['max'] : null;
                if ($min !== null || $max !== null) {
                    foreach ($trimmed as $val) {
                        if ($val === '') continue;
                        $len = strlen($val);
                        if ($min !== null && $len < $min) { $errors[] = sprintf(\mh_lng('UI_VALIDATION_I18N_MIN_LENGTH'), $min); break; }
                        if ($max !== null && $len > $max) { $errors[] = sprintf(\mh_lng('UI_VALIDATION_I18N_MAX_LENGTH'), $max); break; }
                    }
                }
                return $errors;
            },
        ];
    }
}
