<?php
// components/Form/Toggle.php
namespace Components\Form;

class Toggle extends BaseField {
    /**
     * Render a toggle (switch) field.
     * Options:
     * - help: string help text
     * - errors: array of error messages
     * - input_attrs: raw attributes to put on the input element
     * - active_color: short Tailwind color token (e.g., 'indigo', 'emerald', 'sky'). Used for active (checked) state and focus outline.
     * - active_classes: full class string override for active state+outline (takes precedence over active_color).
     * - size: 'xs' | 'sm' | 'md' explicit size override. If not set, can be derived from `variant`.
     * - variant: 'main' | 'default' — blueprint-level semantic variant mapped to size. 'main' => md (current), 'default' => sm.
     * - icon: optional SVG icon. Accepted forms:
     *   - Raw SVG markup (string containing <svg ...>)
     *   - Fully-qualified PHP constant reference to an SVG, e.g. "\\Components\\Base::SVG_CALENDAR"
     *   - If omitted, will try to resolve via mh_lng(STRtoupper(name) . '_ICON'), which may return either of the above
     * - disabled: bool|string — when truthy, renders the control as disabled (grayed out and not clickable)
     */
    public static function render(string $name, string $label, $value = '', array $opts = []): string {
        $help = $opts['help'] ?? '';
        $errors = $opts['errors'] ?? [];
        $extra = $opts['input_attrs'] ?? '';
        $trueValue = array_key_exists('true_value', $opts) ? (string)$opts['true_value'] : '';
        // Determine checked state robustly across legacy and modern representations
        // Accept: boolean true, numeric 1, and common truthy strings (case-insensitive)
        $isChecked = false;
        if (is_bool($value)) {
            $isChecked = $value === true;
        } elseif (is_int($value) || is_float($value)) {
            $isChecked = ((int)$value) !== 0;
        } elseif (is_string($value)) {
            $s = strtolower(trim($value));
            // include legacy 'True'/'False' as well as typical HTML/Alpine values
            $isChecked = in_array($s, ['on', '1', 'true', 'yes', 'y', 't', 'enabled', 'active', 'checked'], true);
        }

        // Also check against custom true_value if provided
        if (!$isChecked && $trueValue !== '') {
            $isChecked = ((string)$value === $trueValue);
        }
        $checked = $isChecked ? 'checked' : '';

        $nameEsc = self::h($name);
        $labelEsc = self::h($label);
        $helpEsc = self::h((string)$help);

        // Build accessible ids based on name
        $idBase = preg_replace('/[^a-zA-Z0-9_-]+/', '-', $name);
        $idBase = trim($idBase ?? '', '-');
        if ($idBase === '') { $idBase = 'toggle'; }
        $id = self::h($idBase);
        $labelId = self::h($idBase . '-label');
        $descId = self::h($idBase . '-description');

        $describedByAttr = '';
        $descHtml = '';
        if ($help !== '' && $help !== null) {
            $describedByAttr = " aria-describedby=\"{$descId}\"";
            $descHtml = self::helpHtml((string)$help, $descId);
        }

        $errorHtml = self::errorsHtml($errors);
        $clientErr = self::clientErrorHtml($name);
        $badge = $opts['label_badge_html'] ?? '';

        // Disabled handling (grayed out + not clickable)
        $disabledOpt = $opts['disabled'] ?? false;
        $isDisabled = false;
        if (is_bool($disabledOpt)) {
            $isDisabled = $disabledOpt === true;
        } elseif (is_int($disabledOpt) || is_float($disabledOpt)) {
            $isDisabled = ((int)$disabledOpt) !== 0;
        } elseif (is_string($disabledOpt)) {
            $ds = strtolower(trim($disabledOpt));
            $isDisabled = in_array($ds, ['1','true','yes','y','on','disabled','readonly','blocked'], true);
        }

        // Determine active color classes
        $activeColorToken = isset($opts['active_color']) ? (string)$opts['active_color'] : '';
        $activeClassesOpt = isset($opts['active_classes']) ? (string)$opts['active_classes'] : '';
        if ($activeClassesOpt !== '') {
            $activeClasses = $activeClassesOpt;
        } else {
            $tok = strtolower((string)preg_replace('/[^a-z0-9_-]+/i', '', $activeColorToken));
            if ($tok === '' || $tok === null) { $tok = 'brand'; }
            // Explicit Tailwind utility classes map for safelisting in build (no dynamic color concatenation)
            // Include a small range of supported tokens: brand, indigo, green, emerald, sky, blue, violet, purple, rose, pink, orange
            $map = [
                // brand uses theme color without shade suffix
                'brand'   => 'outline-brand/70 hover:outline-brand dark:outline-brand has-checked:bg-brand/70 has-checked:hover:bg-brand dark:has-checked:bg-brand',
                // Standard Tailwind palette colors with explicit shades
                'indigo'  => 'outline-indigo-600 dark:outline-indigo-500 has-checked:bg-indigo-600 dark:has-checked:bg-indigo-500',
                'green'   => 'outline-green-600 dark:outline-green-500 has-checked:bg-green-600 dark:has-checked:bg-green-500',
                'emerald' => 'outline-emerald-600 dark:outline-emerald-500 has-checked:bg-emerald-600 dark:has-checked:bg-emerald-500',
                'sky'     => 'outline-sky-600 dark:outline-sky-500 has-checked:bg-sky-600 dark:has-checked:bg-sky-500',
                'blue'    => 'outline-blue-600 dark:outline-blue-500 has-checked:bg-blue-600 dark:has-checked:bg-blue-500',
                'violet'  => 'outline-violet-600 dark:outline-violet-500 has-checked:bg-violet-600 dark:has-checked:bg-violet-500',
                'purple'  => 'outline-purple-600 dark:outline-purple-500 has-checked:bg-purple-600 dark:has-checked:bg-purple-500',
                'rose'    => 'outline-rose-600 dark:outline-rose-500 has-checked:bg-rose-600 dark:has-checked:bg-rose-500',
                'pink'    => 'outline-pink-600 dark:outline-pink-500 has-checked:bg-pink-600 dark:has-checked:bg-pink-500',
                'orange'  => 'outline-orange-600 dark:outline-orange-500 has-checked:bg-orange-600 dark:has-checked:bg-orange-500',
            ];
            $activeClasses = $map[$tok] ?? $map['brand'];
        }
        // If disabled, suppress active coloring so the switch stays visually muted
        if ($isDisabled) {
            $activeClasses = '';
        }
        $activeClassesEsc = self::h(trim($activeClasses));

        // Size classes (explicit maps for Tailwind safelist)
        // Support semantic variant -> size mapping for blueprints.
        $sizeTok = strtolower(trim((string)($opts['size'] ?? '')));
        $variantTok = strtolower(trim((string)($opts['variant'] ?? '')));
        if ($sizeTok === '' && $variantTok !== '') {
            // Map variants to sizes: main => md, default => sm
            if ($variantTok === 'default') {
                $sizeTok = 'sm';
            } elseif ($variantTok === 'main') {
                $sizeTok = 'md';
            }
        }
        $size = in_array($sizeTok, ['xs', 'sm', 'md'], true) ? $sizeTok : 'sm';
        $sizeMap = [
            'md' => [
                'track' => 'w-9 p-0.5',
                'thumb' => 'size-4 group-has-checked:translate-x-4',
            ],
            'sm' => [
                'track' => 'w-7 p-0.5',
                'thumb' => 'size-3 group-has-checked:translate-x-3',
            ],
            'xs' => [
                'track' => 'w-5 p-0.5',
                'thumb' => 'size-2 group-has-checked:translate-x-2',
            ],
        ];
        $trackSize = $sizeMap[$size]['track'];
        $thumbSize = $sizeMap[$size]['thumb'];

        $trackSizeEsc = self::h($trackSize);
        $thumbSizeEsc = self::h($thumbSize);

        // Optional icon resolution
        $iconSpec = trim((string)($opts['icon'] ?? ''));
        if ($iconSpec === '') {
            // Try to resolve from language: e.g. KEY_ICON
            $lngKey = strtoupper((string)$name) . '_ICON';
            try {
                $maybe = (string)\mh_lng($lngKey, '');
                $iconSpec = trim($maybe);
            } catch (\Throwable $e) {
                // ignore i18n issues, no icon
            }
        }

        $iconHtml = '';
        if ($iconSpec !== '') {
            // If it's a constant reference like \Components\Base::SVG_CALENDAR
            if (preg_match('/^\\\\?[A-Za-z_\\\\][A-Za-z0-9_\\\\]*::[A-Z0-9_]+$/', $iconSpec)) {
                $constName = $iconSpec[0] === '\\' ? $iconSpec : ('\\' . $iconSpec);
                if (defined($constName)) {
                    $val = constant($constName);
                    if (is_string($val)) { $iconHtml = $val; }
                } else {
                    // Fallback without leading backslash
                    $alt = ltrim($constName, '\\');
                    if (defined($alt)) {
                        $val = constant($alt);
                        if (is_string($val)) { $iconHtml = $val; }
                    }
                }
            } elseif (stripos($iconSpec, '<svg') !== false) {
                $iconHtml = $iconSpec; // assume raw svg
            }
        }
        $iconContainer = '';
        if ($iconHtml !== '') {
            $iconContainer = <<<HTML
<div class="rounded bg-secondary/50 text-white p-1 size-6"><div class="size-4">{$iconHtml}</div></div>
HTML;
        }

        // Compose disabled-related classes/attributes
        $trackDisabledClasses = $isDisabled ? 'opacity-50 cursor-not-allowed' : '';
        $trackDisabledClassesEsc = self::h(trim($trackDisabledClasses));
        $inputDisabledAttr = $isDisabled ? ' disabled aria-disabled="true" tabindex="-1"' : '';
        $inputDisabledClass = $isDisabled ? ' pointer-events-none cursor-not-allowed' : '';
        $inputDisabledClassEsc = self::h(trim($inputDisabledClass));

        $containerClass = (string)($opts['container_class'] ?? '');
        $containerClassEsc = self::h($containerClass);
        $labelHtml = self::renderLabel($id, $label, $badge, array_merge($opts, ['skip_label_pt' => true]));
        return <<<HTML
<div class="{$containerClassEsc} flex items-start gap-x-3 min-w-0">
    {$iconContainer}
    <div class="flex grow flex-col min-w-0">
        <div class="flex items-center justify-between min-w-0">
            {$labelHtml}
            <div class="group relative inline-flex {$trackSizeEsc} shrink-0 rounded-full bg-gray-200 inset-ring inset-ring-gray-900/5 outline-offset-2 has-focus-visible:outline-2 transition-colors duration-200 ease-in-out dark:bg-white/5 dark:inset-ring-white/10 {$activeClassesEsc} {$trackDisabledClassesEsc}">
                <span class="{$thumbSizeEsc} rounded-full bg-white ring-1 ring-gray-900/5 transition-transform duration-200 ease-in-out"></span>
                <input id="{$id}" type="checkbox" name="{$nameEsc}" aria-labelledby="{$labelId}"{$describedByAttr} class="absolute inset-0 appearance-none focus:outline-hidden{$inputDisabledClassEsc}" {$checked}{$inputDisabledAttr} {$extra}/>
            </div>
        </div>
        {$descHtml}
    </div>
</div>
{$errorHtml}
{$clientErr}
HTML;
    }
}
