<?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', ' ', substr($s, 0, 16)) . ':00';
            $ts = strtotime($s2);
            if ($ts !== false) return $ts;
        }
        $ts = strtotime($s);
        if ($ts !== false) return $ts;
        return null;
    }
    /**
     * 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'] = $msg; }
                    $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[] = 'This field is 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[] = "Minimum length is $min.";
                if ($max !== null && $len > $max) $errors[] = "Maximum length is $max.";
                return $errors;
            },
            'email' => function ($value, array $v) {
                $value = (string)$value;
                $errors = [];
                if (($v['required'] ?? false) && trim($value) === '') {
                    $errors[] = 'This field is required.';
                    return $errors;
                }
                if ($value === '') { return $errors; }
                // Use PHP's filter for RFC-compliant emails
                if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
                    $errors[] = 'Please enter a valid email address.';
                    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[] = "Minimum length is $min.";
                if ($max !== null && $len > $max) $errors[] = "Maximum length is $max.";
                return $errors;
            },
            'integer' => function ($value, array $v) {
                $errors = [];
                if (($v['required'] ?? false) && ($value === '' || $value === null)) {
                    $errors[] = 'This field is required.';
                    return $errors;
                }
                if ($value === '' || $value === null) return $errors; // not required and empty
                if (!is_numeric($value) || (string)(int)$value !== (string)$value) {
                    $errors[] = 'Please enter a valid 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[] = "Minimum value is $min.";
                if ($max !== null && $int > $max) $errors[] = "Maximum value is $max.";
                return $errors;
            },
            'boolean' => function ($value, array $v) {
                $errors = [];
                if (($v['required'] ?? false) && $value !== 'on' && $value !== '1' && $value !== 'true') {
                    $errors[] = 'This field is required.';
                }
                return $errors;
            },
            'select' => function ($value, array $v, array $def = []) {
                $errors = [];
                if (($v['required'] ?? false) && ($value === '' || $value === null)) {
                    $errors[] = 'Please select a value.';
                }
                // optional: validate option exists (support options or values)
                if ($value !== '' && $value !== null && (isset($def['options']) || isset($def['values']))) {
                    $allowed = [];
                    if (isset($def['options']) && is_array($def['options'])) {
                        $opts = $def['options'];
                        $isAssoc = array_keys($opts) !== range(0, count($opts) - 1);
                        if ($isAssoc) {
                            // key=>label pairs
                            $allowed = array_map('strval', array_keys($opts));
                        } else {
                            // list of labels or list of {value,label}
                            foreach ($opts as $opt) {
                                if (is_array($opt) && isset($opt['value'])) {
                                    $allowed[] = (string)$opt['value'];
                                } else {
                                    $allowed[] = (string)$opt;
                                }
                            }
                        }
                    } elseif (isset($def['values']) && is_array($def['values'])) {
                        $allowed = array_map('strval', $def['values']);
                    }
                    if (!in_array((string)$value, $allowed, true)) {
                        $errors[] = '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[] = 'Please select at least one option.';
                    }
                    return $errors;
                }
                if (($v['required'] ?? false) && $value !== 'on' && $value !== '1' && $value !== 'true') {
                    $errors[] = 'This field is required.';
                }
                return $errors;
            },
            'date' => function($value, array $v) {
                $errors = [];
                $s = is_string($value) ? trim($value) : '';
                if (($v['required'] ?? false) && $s === '') { $errors[] = 'This field is required.'; return $errors; }
                if ($s === '') { return $errors; }
                $ts = Validation::parseDateToTs($s);
                if ($ts === null) { $errors[] = 'Please enter a valid date.'; return $errors; }
                if (isset($v['min']) && $v['min'] !== '') {
                    $minTs = Validation::parseDateToTs($v['min']);
                    if ($minTs !== null && $ts < $minTs) { $errors[] = 'Date must be on or after ' . (string)$v['min'] . '.'; }
                }
                if (isset($v['max']) && $v['max'] !== '') {
                    $maxTs = Validation::parseDateToTs($v['max']);
                    if ($maxTs !== null && $ts > $maxTs) { $errors[] = 'Date must be on or 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[] = 'This field is required.'; return $errors; }
                if ($s === '') { return $errors; }
                $ts = Validation::parseDateToTs($s);
                if ($ts === null) { $errors[] = 'Please enter a valid date and time.'; return $errors; }
                if (isset($v['min']) && $v['min'] !== '') {
                    $minTs = Validation::parseDateToTs($v['min']);
                    if ($minTs !== null && $ts < $minTs) { $errors[] = 'Date must be on or after ' . (string)$v['min'] . '.'; }
                }
                if (isset($v['max']) && $v['max'] !== '') {
                    $maxTs = Validation::parseDateToTs($v['max']);
                    if ($maxTs !== null && $ts > $maxTs) { $errors[] = 'Date must be on or 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[] = 'All languages are required.'; break; }
                    }
                } elseif ($requiredAny) {
                    $hasAny = false; foreach ($trimmed as $val) { if ($val !== '') { $hasAny = true; break; } }
                    if (!$hasAny) { $errors[] = 'At least one language must be provided.'; }
                }

                $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[] = "Each translation must be at least $min characters."; break; }
                        if ($max !== null && $len > $max) { $errors[] = "Each translation must be at most $max characters."; break; }
                    }
                }
                return $errors;
            },
        ];
    }
}
