import { Button, Card, Input, Modal } from '@/Components/ui';
import { cn } from '@/lib/cn';
import type { ThemeKnobs } from '@/lib/themePalette';
import { router } from '@inertiajs/react';
import { Download, Save, Send, Upload } from 'lucide-react';
import { useRef, useState, type SubmitEvent as ReactSubmitEvent } from 'react';

type Props = {
    knobs: ThemeKnobs;
    activeKnobs: ThemeKnobs;
    activeId: number | null;
    /** Parent uses this to suppress its beforeunload prompt during the
     *  publish-and-reload flow — admin clicked Publish, the reload is
     *  intentional, no "unsaved changes?" dialog needed. */
    onSaveStart?: () => void;
};

function defaultName(): string {
    const now = new Date();
    const y = now.getFullYear();
    const m = String(now.getMonth() + 1).padStart(2, '0');
    const d = String(now.getDate()).padStart(2, '0');
    const hh = String(now.getHours()).padStart(2, '0');
    const mm = String(now.getMinutes()).padStart(2, '0');
    return `Custom — ${y}-${m}-${d} ${hh}:${mm}`;
}

function knobsEqual(a: ThemeKnobs, b: ThemeKnobs): boolean {
    return JSON.stringify(a) === JSON.stringify(b);
}

type SaveMode = 'save-only' | 'save-and-apply';

export default function ActionsBar({
    knobs,
    activeKnobs,
    activeId,
    onSaveStart,
}: Props) {
    const [saveOpen, setSaveOpen] = useState<SaveMode | null>(null);
    const fileInputRef = useRef<HTMLInputElement>(null);
    const isClean = knobsEqual(knobs, activeKnobs);

    const onImportPicked = (e: React.ChangeEvent<HTMLInputElement>) => {
        const file = e.target.files?.[0];
        if (!file) return;
        const formData = new FormData();
        formData.append('file', file);
        router.post('/admin/appearance/import', formData, {
            forceFormData: true,
            preserveScroll: true,
            onFinish: () => {
                if (fileInputRef.current) fileInputRef.current.value = '';
            },
        });
    };

    return (
        <Card padding="md" data-testid="appearance-actions-bar">
            <h3 className="mb-3 text-[11px] font-medium text-muted-foreground">
                Actions
            </h3>
            <div className="flex flex-col gap-2">
                <Button
                    type="button"
                    variant="primary"
                    size="md"
                    disabled={isClean}
                    onClick={() => setSaveOpen('save-only')}
                    data-testid="appearance-save-version"
                    className="justify-start"
                >
                    <Save className="h-4 w-4" strokeWidth={2} />
                    Save as new version
                </Button>
                <Button
                    type="button"
                    variant="secondary"
                    size="md"
                    disabled={isClean}
                    onClick={() => setSaveOpen('save-and-apply')}
                    data-testid="appearance-save-and-apply"
                    className="justify-start"
                >
                    <Send className="h-4 w-4" strokeWidth={2} />
                    Save and apply to live
                </Button>
                <div className="my-1 h-px bg-border" />
                <Button
                    type="button"
                    variant="ghost"
                    size="md"
                    onClick={() => fileInputRef.current?.click()}
                    data-testid="appearance-import-trigger"
                    className="justify-start"
                >
                    <Upload className="h-4 w-4" strokeWidth={2} />
                    Import JSON
                </Button>
                <input
                    ref={fileInputRef}
                    type="file"
                    accept="application/json,.json"
                    onChange={onImportPicked}
                    className="hidden"
                    data-testid="appearance-import-input"
                />
                {activeId !== null && (
                    <a
                        href={`/admin/appearance/${activeId}/export`}
                        data-testid="appearance-export-active"
                        className={cn(
                            'inline-flex h-10 items-center justify-start gap-2 rounded-xl px-4 text-sm font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground',
                        )}
                    >
                        <Download className="h-4 w-4" strokeWidth={2} />
                        Export active theme
                    </a>
                )}
            </div>

            {saveOpen && (
                <SaveModal
                    mode={saveOpen}
                    knobs={knobs}
                    onCancel={() => setSaveOpen(null)}
                    onClose={() => setSaveOpen(null)}
                    onSaveStart={onSaveStart}
                />
            )}
        </Card>
    );
}

type SaveModalProps = {
    mode: SaveMode;
    knobs: ThemeKnobs;
    onCancel: () => void;
    onClose: () => void;
    onSaveStart?: () => void;
};

function SaveModal({ mode, knobs, onCancel, onClose, onSaveStart }: SaveModalProps) {
    const [name, setName] = useState(() => defaultName());
    const [submitting, setSubmitting] = useState(false);
    const [error, setError] = useState<string | null>(null);
    const [contrastFailures, setContrastFailures] = useState<string[] | null>(
        null,
    );

    const runSave = (force: boolean) => {
        setSubmitting(true);
        setError(null);
        // Signal parent to suppress its beforeunload prompt — the
        // upcoming reload (in save-and-apply mode) is an intentional
        // navigation, not an unsaved-changes leave.
        onSaveStart?.();
        // POST is JSON, not Inertia — backend returns `{id, name}`. Use fetch
        // so we can chain the apply call when mode is save-and-apply.
        fetch('/admin/appearance', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                Accept: 'application/json',
                'X-Requested-With': 'XMLHttpRequest',
                'X-CSRF-TOKEN':
                    document
                        .querySelector<HTMLMetaElement>('meta[name="csrf-token"]')
                        ?.content ?? '',
            },
            credentials: 'same-origin',
            body: JSON.stringify({
                name: name.trim(),
                knobs,
                ...(force ? { force: 1 } : {}),
            }),
        })
            .then(async (res) => {
                if (!res.ok) {
                    const body = (await res.json().catch(() => null)) as
                        | {
                              error?: string;
                              message?: string;
                              failures?: string[];
                          }
                        | null;
                    // Contrast guard — surface the failures inline + offer
                    // a "Save anyway" CTA. The save handler resubmits with
                    // force=1 when the admin confirms.
                    if (
                        res.status === 422 &&
                        body?.error === 'contrast_failures'
                    ) {
                        setContrastFailures(body.failures ?? []);
                        setSubmitting(false);
                        return null;
                    }
                    throw new Error(
                        (body && (body.message as string)) ||
                            `Save failed (${res.status}).`,
                    );
                }
                return res.json() as Promise<{ id: number; name: string }>;
            })
            .then((data) => {
                if (!data) return; // contrast-failures branch already handled
                if (mode === 'save-and-apply') {
                    // Apply via fetch (not router) so we can force a full
                    // browser reload after — Inertia's XHR navigation swaps
                    // the React tree but never re-renders Blade's <head>,
                    // so the Google Fonts <link> and <style nonce> block
                    // stay frozen on the old theme. Full reload is needed.
                    //
                    // Pass `force` through if the save bypassed contrast
                    // checks. The apply endpoint has its own contrast guard
                    // that 422s without it, leaving the saved version
                    // inactive — silently strands the admin's "Save anyway"
                    // intent if not forwarded.
                    const applyUrl = `/admin/appearance/${data.id}/apply${force ? '?force=1' : ''}`;
                    fetch(applyUrl, {
                        method: 'PATCH',
                        headers: {
                            Accept: 'application/json',
                            'X-Requested-With': 'XMLHttpRequest',
                            'X-CSRF-TOKEN':
                                document
                                    .querySelector<HTMLMetaElement>(
                                        'meta[name="csrf-token"]',
                                    )
                                    ?.content ?? '',
                        },
                        credentials: 'same-origin',
                    }).finally(() => {
                        setSubmitting(false);
                        onClose();
                        window.location.reload();
                    });
                } else {
                    // Refresh the versions list / active id from the server.
                    router.reload({
                        only: ['versions', 'activeId'],
                        onFinish: () => {
                            setSubmitting(false);
                            onClose();
                        },
                    });
                }
            })
            .catch((err: Error) => {
                setError(err.message);
                setSubmitting(false);
            });
    };

    const submit = (e: ReactSubmitEvent) => {
        e.preventDefault();
        if (!name.trim()) {
            setError('Name is required.');
            return;
        }
        setContrastFailures(null);
        runSave(false);
    };

    return (
        <Modal
            open
            onClose={onCancel}
            size="md"
            onSubmit={submit}
            closeOnBackdrop={false}
            closeOnEsc={!submitting}
            data-testid="appearance-save-modal"
        >
            <Modal.Header
                title={
                    mode === 'save-and-apply'
                        ? 'Save and apply'
                        : 'Save as new version'
                }
                subtitle={
                    mode === 'save-and-apply'
                        ? 'Saves a new version and applies it to the live site.'
                        : 'Snapshots the current knobs. Apply later from the versions list.'
                }
            />
            <Modal.Body fade={false}>
                <div>
                    <label
                        htmlFor="appearance-save-name"
                        className="mb-1 block text-[11px] font-medium text-muted-foreground"
                    >
                        Name
                    </label>
                    <Input
                        id="appearance-save-name"
                        value={name}
                        onChange={(e) => setName(e.target.value)}
                        autoFocus
                        data-testid="appearance-save-name-input"
                        disabled={submitting}
                    />
                </div>
                {error && (
                    <p
                        className="text-xs text-destructive"
                        data-testid="appearance-save-error"
                    >
                        {error}
                    </p>
                )}
                {contrastFailures && (
                    <div
                        className="rounded-md border border-warning/30 bg-warning/10 p-3"
                        data-testid="appearance-save-contrast-failures"
                    >
                        <p className="text-[12px] font-semibold text-foreground">
                            {contrastFailures.length} contrast{' '}
                            {contrastFailures.length === 1
                                ? 'pair fails'
                                : 'pairs fail'}{' '}
                            WCAG AA
                        </p>
                        <p className="mt-1 text-[11px] text-muted-foreground">
                            Body text on these surfaces will be hard to
                            read. Adjust the knobs to fix, or save anyway
                            if the brand requires it.
                        </p>
                        <ul className="mt-2 space-y-1 text-[11px] font-mono">
                            {contrastFailures.map((line, i) => (
                                <li
                                    key={i}
                                    className="text-foreground/80"
                                >
                                    {line}
                                </li>
                            ))}
                        </ul>
                    </div>
                )}
            </Modal.Body>
            <Modal.Footer>
                <Button
                    type="button"
                    variant="ghost"
                    onClick={onCancel}
                    disabled={submitting}
                    data-testid="appearance-save-cancel"
                >
                    Cancel
                </Button>
                {contrastFailures ? (
                    <Button
                        type="button"
                        variant="danger"
                        disabled={submitting}
                        onClick={() => runSave(true)}
                        data-testid="appearance-save-force"
                    >
                        {submitting ? 'Saving…' : 'Save anyway'}
                    </Button>
                ) : (
                    <Button
                        type="submit"
                        variant="primary"
                        disabled={submitting}
                        data-testid="appearance-save-confirm"
                    >
                        {submitting
                            ? 'Saving…'
                            : mode === 'save-and-apply'
                              ? 'Save and apply'
                              : 'Save'}
                    </Button>
                )}
            </Modal.Footer>
        </Modal>
    );
}
