import { csrfHeaders } from '@/admin/settings/types';
import { Button, Card, Checkbox, Popover } from '@/Components/ui';
import { BLOCK_REGISTRY, getBlockMeta, pickOrderForPage } from '@/marketing/block-registry';
import type { PageConfig, PageSeoOverride, Section } from '@/marketing/cms-types';
import SeoCard from './SeoCard';
import {
    AlertCircle,
    AlertTriangle,
    ArrowDown,
    ArrowUp,
    CheckCircle2,
    ChevronDown,
    HelpCircle,
    Loader2,
    Pencil,
    Plus,
    Trash2,
} from 'lucide-react';
import { useEffect, useRef, useState, type ComponentType, type SubmitEvent as ReactSubmitEvent } from 'react';

export type SectionsEditorPageKey = 'welcome' | 'pricing' | 'templates' | 'about' | 'contact' | 'privacy' | 'terms' | 'refund';

type Props = {
    value: PageConfig;
    onSaved: (next: PageConfig) => void;
    /** Drives the save endpoint, the response key read back, and which
     *  pickOrder is rendered in the Add-Section menu. */
    pageKey?: SectionsEditorPageKey;
    /** Spread into each section's editor — lets per-section editors
     *  (e.g. KitGridEditor) receive page-level context like
     *  `installedKits` without threading it through every section type. */
    extraEditorProps?: Record<string, unknown>;
    /** Custom save handler — when set, replaces the built-in fetch. Used
     *  by `CustomPagesManager` since custom pages' save endpoint takes a
     *  slug + returns the whole pages list, not a single PageConfig. The
     *  parent does the network call, normalizes the response back into a
     *  PageConfig for one page, and resolves the promise. Errors thrown
     *  surface as a generic save-failed message. */
    saveOverride?: (body: { sections: Section[]; enabled: boolean; seo: PageSeoOverride }) => Promise<PageConfig>;
    /** Override the page-header copy from PAGE_HEADER_COPY. Custom pages
     *  pass their own title here. */
    headerCopyOverride?: { heading: string; description: string };
};

type SectionType = Section['type'];
type SectionErrors = Record<number, Record<string, string>>;

// Per-pageKey wire metadata. Keeping this colocated so adding a 4th
// marketing page is a one-line registry entry — no scattered conditionals.
const PAGE_WIRING: Record<
    SectionsEditorPageKey,
    { endpoint: string; responseKey: SectionsEditorPageKey }
> = {
    welcome: { endpoint: '/admin/marketing/welcome', responseKey: 'welcome' },
    pricing: { endpoint: '/admin/marketing/pricing', responseKey: 'pricing' },
    templates: { endpoint: '/admin/marketing/templates', responseKey: 'templates' },
    about: { endpoint: '/admin/marketing/about', responseKey: 'about' },
    contact: { endpoint: '/admin/marketing/contact', responseKey: 'contact' },
    privacy: { endpoint: '/admin/marketing/privacy', responseKey: 'privacy' },
    terms: { endpoint: '/admin/marketing/terms', responseKey: 'terms' },
    refund: { endpoint: '/admin/marketing/refund', responseKey: 'refund' },
};

export default function WelcomeEditor({
    value,
    onSaved,
    pageKey = 'welcome',
    extraEditorProps,
    saveOverride,
    headerCopyOverride,
}: Props) {
    const { endpoint, responseKey } = PAGE_WIRING[pageKey];
    const pickOrder = pickOrderForPage(pageKey);
    // Only optional content pages get the publish toggle. Welcome is the
    // landing (controlled by the site-level landing-mode toggle instead);
    // Pricing is billing-critical (change-plan flow must keep working).
    // Legal pages (privacy/terms/refund) ship with enabled=false in
    // defaults — admin must explicitly publish after replacing the
    // placeholder copy.
    const hasPublishToggle =
        pageKey === 'about' ||
        pageKey === 'contact' ||
        pageKey === 'templates' ||
        pageKey === 'privacy' ||
        pageKey === 'terms' ||
        pageKey === 'refund' ||
        // Custom pages (via saveOverride) always have publish state too.
        Boolean(saveOverride);
    const [sections, setSections] = useState<Section[]>(value.sections);
    const [pageEnabled, setPageEnabled] = useState<boolean>(value.enabled !== false);
    const [pageSeo, setPageSeo] = useState<PageSeoOverride>(value.seo ?? {});
    const [expandedId, setExpandedId] = useState<string | null>(null);
    const [errors, setErrors] = useState<SectionErrors>({});
    const [submitError, setSubmitError] = useState<string | null>(null);
    const [saving, setSaving] = useState(false);
    const [savedAt, setSavedAt] = useState<number | null>(null);

    // Per-section row refs for scroll-into-view when admin clicks Edit on a
    // section that's offscreen (or near the bottom — the editor below the
    // row would otherwise extend past the fold). Keyed by section id so
    // reorder/add/delete don't break the mapping.
    const sectionRefs = useRef<Record<string, HTMLDivElement | null>>({});

    useEffect(() => {
        if (!expandedId) return;
        const el = sectionRefs.current[expandedId];
        if (!el) return;
        // requestAnimationFrame waits for React to paint the expanded
        // editor below the row. Without it, the scroll fires before the
        // editor is in the DOM and the calculated position is wrong on
        // dense pages where the page height changes during the same tick.
        requestAnimationFrame(() => {
            el.scrollIntoView({ behavior: 'smooth', block: 'start' });
        });
    }, [expandedId]);

    useEffect(() => {
        setSections(value.sections);
    }, [value.sections]);

    useEffect(() => {
        setPageEnabled(value.enabled !== false);
    }, [value.enabled]);

    useEffect(() => {
        setPageSeo(value.seo ?? {});
    }, [value.seo]);

    useEffect(() => {
        if (savedAt === null) return;
        const t = window.setTimeout(() => setSavedAt(null), 2400);
        return () => window.clearTimeout(t);
    }, [savedAt]);

    const updateSection = (idx: number, next: Section) => {
        setSections((prev) => prev.map((s, i) => (i === idx ? next : s)));
        if (errors[idx]) {
            setErrors((prev) => {
                const copy = { ...prev };
                delete copy[idx];
                return copy;
            });
        }
    };

    const toggleEnabled = (idx: number, enabled: boolean) => {
        setSections((prev) =>
            prev.map((s, i) => (i === idx ? ({ ...s, enabled } as Section) : s)),
        );
    };

    const moveSection = (idx: number, dir: -1 | 1) => {
        const target = idx + dir;
        if (target < 0 || target >= sections.length) return;
        setSections((prev) => {
            const next = prev.slice();
            const [item] = next.splice(idx, 1);
            next.splice(target, 0, item);
            return next;
        });
        // Clear errors after reorder — indices no longer match server's
        // last-known shape, so stale errors would attach to wrong rows.
        setErrors({});
    };

    const removeSection = (idx: number) => {
        const sec = sections[idx];
        const label = getBlockMeta(sec.type)?.label ?? sec.type;
        if (!window.confirm(`Delete the ${label} section?`)) {
            return;
        }
        if (expandedId === sec.id) setExpandedId(null);
        setSections((prev) => prev.filter((_, i) => i !== idx));
        setErrors({});
    };

    const addSection = (type: SectionType) => {
        const fresh = BLOCK_REGISTRY[type].defaultData();
        setSections((prev) => [...prev, fresh]);
        setExpandedId(fresh.id);
    };

    const submit = async (e: ReactSubmitEvent) => {
        e.preventDefault();
        setSaving(true);
        setSubmitError(null);
        setErrors({});

        // saveOverride path: parent owns the network call (custom pages
        // need slug-based endpoint + response-shape extraction). Errors
        // propagate as generic save-failed messages — per-field validation
        // would need a shared error shape; deferred.
        if (saveOverride) {
            try {
                const saved = await saveOverride({ sections, enabled: pageEnabled, seo: pageSeo });
                setSections(saved.sections);
                if (saved.enabled !== undefined) setPageEnabled(saved.enabled);
                onSaved(saved);
                setSavedAt(Date.now());
            } catch (err) {
                setSubmitError(err instanceof Error ? err.message : 'Save failed.');
            } finally {
                setSaving(false);
            }
            return;
        }

        try {
            const res = await fetch(endpoint, {
                method: 'PATCH',
                credentials: 'same-origin',
                headers: csrfHeaders(),
                body: JSON.stringify({
                    sections,
                    ...(hasPublishToggle ? { enabled: pageEnabled } : {}),
                    // Always include seo — server strips it on save when
                    // empty/absent (extractPageSeoBlock returns null) so
                    // a no-override page doesn't accidentally persist
                    // empty SEO fields into Settings.
                    seo: pageSeo,
                }),
            });

            if (res.status === 422) {
                const body = await res.json().catch(() => null);
                const next: SectionErrors = {};
                for (const [k, msgs] of Object.entries(body?.errors ?? {})) {
                    if (!Array.isArray(msgs) || msgs.length === 0) continue;
                    // Match `sections.{idx}.{...rest}` and stash the rest as
                    // the field path on that section's error map. Per-section
                    // editors look up by the rest path (e.g. "primaryCta.label"
                    // or "items.0.title") to render the inline error.
                    const m = k.match(/^sections\.(\d+)\.(.+)$/);
                    if (!m) continue;
                    const idx = Number.parseInt(m[1], 10);
                    const field = m[2];
                    if (!next[idx]) next[idx] = {};
                    next[idx][field] = msgs[0] as string;
                }
                setErrors(next);
                // Auto-expand the first section with errors so the admin
                // sees the offending field on submit.
                const firstBadIdx = Object.keys(next)
                    .map((s) => Number.parseInt(s, 10))
                    .sort((a, b) => a - b)[0];
                if (firstBadIdx !== undefined && sections[firstBadIdx]) {
                    setExpandedId(sections[firstBadIdx].id);
                }
                return;
            }

            if (!res.ok) {
                const body = await res.json().catch(() => null);
                setSubmitError(body?.message ?? `Save failed (${res.status}).`);
                return;
            }

            const body = (await res.json()) as Partial<Record<SectionsEditorPageKey, PageConfig>>;
            const saved = body?.[responseKey];
            if (saved) {
                setSections(saved.sections);
                onSaved(saved);
            }
            setSavedAt(Date.now());
        } catch {
            setSubmitError('Network error. Try again.');
        } finally {
            setSaving(false);
        }
    };

    return (
        <form
            onSubmit={submit}
            className="space-y-6"
            data-testid={`admin-marketing-${pageKey}-form`}
        >
            <Card padding="lg">
                <header className="mb-5 flex items-start justify-between gap-4">
                    <div>
                        <h2 className="text-base font-semibold tracking-tight text-foreground">
                            {headerCopyOverride?.heading ?? PAGE_HEADER_COPY[pageKey].heading}
                        </h2>
                        <p className="mt-1 text-[12px] text-muted-foreground">
                            {headerCopyOverride?.description ?? PAGE_HEADER_COPY[pageKey].description}
                        </p>
                    </div>
                    {hasPublishToggle && (
                        <label
                            className="inline-flex shrink-0 cursor-pointer items-center gap-2 rounded-lg border border-border bg-background px-3 py-2 text-[12px] font-medium"
                            data-testid={`admin-marketing-${pageKey}-publish-toggle`}
                        >
                            <Checkbox
                                checked={pageEnabled}
                                onChange={(ev) => setPageEnabled(ev.target.checked)}
                            />
                            <span className={pageEnabled ? 'text-foreground' : 'text-muted-foreground'}>
                                {pageEnabled ? 'Published' : 'Unpublished (404)'}
                            </span>
                        </label>
                    )}
                </header>
                {hasPublishToggle && !pageEnabled && (
                    <div className="mb-5 rounded-lg border border-warning/30 bg-warning/10 px-3 py-2 text-[12px] text-foreground">
                        This page is unpublished — visitors hitting /
                        {pageKey} get a 404. Re-enable above and save to
                        publish.
                    </div>
                )}

                {/* Per-page SEO override card. Collapsed by default; expand
                    to override the site-wide template/description/og:image
                    for this page only. The headerCopyOverride heading is
                    the natural label for the {page} placeholder preview. */}
                <div className="mb-5">
                    <SeoCard
                        value={pageSeo}
                        onChange={setPageSeo}
                        pageLabel={
                            headerCopyOverride?.heading ?? PAGE_HEADER_COPY[pageKey].heading
                        }
                    />
                </div>

                <div className="space-y-3">
                    {sections.length === 0 && (
                        <p className="rounded-lg border border-dashed border-border bg-muted/20 px-3 py-6 text-center text-[12px] text-muted-foreground">
                            No sections yet. Add one below.
                        </p>
                    )}
                    {sections.map((sec, idx) => {
                        const expanded = expandedId === sec.id;
                        const sectionErrors = errors[idx];
                        const hasErrors = sectionErrors && Object.keys(sectionErrors).length > 0;
                        const meta = getBlockMeta(sec.type);
                        const label = meta?.label ?? sec.type;
                        const isUnknown = meta === null;
                        const isMandatory = meta?.mandatory === true;
                        return (
                            <div
                                key={sec.id}
                                ref={(el) => {
                                    sectionRefs.current[sec.id] = el;
                                }}
                                className={
                                    'scroll-mt-6 rounded-xl border bg-muted/10 ' +
                                    (hasErrors ? 'border-destructive/40' : 'border-border')
                                }
                                data-testid={`admin-marketing-${pageKey}-section-${sec.id}`}
                            >
                                <div className="flex items-center gap-3 p-3">
                                    <span className="inline-flex h-6 w-6 items-center justify-center rounded-md bg-background text-[11px] font-medium text-muted-foreground">
                                        {idx + 1}
                                    </span>
                                    <div className="min-w-0 flex-1">
                                        <p className="truncate text-[13px] font-medium text-foreground">
                                            {label}
                                        </p>
                                        <p className="truncate text-[11px] text-muted-foreground">
                                            {sec.type}
                                            {hasErrors ? ' · check fields below' : ''}
                                            {isUnknown ? ' · unknown block type' : ''}
                                        </p>
                                    </div>

                                    {isMandatory ? (
                                        <span
                                            className="rounded-md border border-border bg-muted/40 px-2 py-1 text-[11px] font-medium text-muted-foreground"
                                            title="This section is required for the page and can't be disabled or removed."
                                        >
                                            Required
                                        </span>
                                    ) : (
                                        <label
                                            className="flex items-center gap-2 text-[12px] font-medium text-foreground"
                                            title={sec.enabled ? 'Section is rendering on the live page' : 'Section is hidden from the live page'}
                                        >
                                            <Checkbox
                                                checked={sec.enabled}
                                                onChange={(e) => toggleEnabled(idx, e.target.checked)}
                                            />
                                            <span>Enabled</span>
                                        </label>
                                    )}

                                    <div className="flex items-center gap-1">
                                        <button
                                            type="button"
                                            onClick={() => moveSection(idx, -1)}
                                            disabled={idx === 0}
                                            aria-label="Move section up"
                                            className="rounded-md border border-border bg-background p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-40"
                                        >
                                            <ArrowUp className="h-3.5 w-3.5" strokeWidth={2} />
                                        </button>
                                        <button
                                            type="button"
                                            onClick={() => moveSection(idx, 1)}
                                            disabled={idx === sections.length - 1}
                                            aria-label="Move section down"
                                            className="rounded-md border border-border bg-background p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-40"
                                        >
                                            <ArrowDown className="h-3.5 w-3.5" strokeWidth={2} />
                                        </button>
                                        <button
                                            type="button"
                                            onClick={() =>
                                                setExpandedId(expanded ? null : sec.id)
                                            }
                                            aria-label={expanded ? 'Collapse editor' : 'Edit section'}
                                            aria-expanded={expanded}
                                            className={
                                                'rounded-md border border-border p-2 transition-colors ' +
                                                (expanded
                                                    ? 'bg-accent/10 text-accent'
                                                    : 'bg-background text-muted-foreground hover:bg-muted hover:text-foreground')
                                            }
                                        >
                                            <Pencil className="h-3.5 w-3.5" strokeWidth={2} />
                                        </button>
                                        {!isMandatory && (
                                            <button
                                                type="button"
                                                onClick={() => removeSection(idx)}
                                                aria-label="Delete section"
                                                className="rounded-md border border-border bg-background p-2 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive"
                                            >
                                                <Trash2 className="h-3.5 w-3.5" strokeWidth={2} />
                                            </button>
                                        )}
                                    </div>
                                </div>

                                {expanded && (
                                    <div className="border-t border-border bg-background/40 p-4">
                                        <SectionBody
                                            pageKey={pageKey}
                                            section={sec}
                                            errors={sectionErrors}
                                            onChange={(next) => updateSection(idx, next)}
                                            extraEditorProps={extraEditorProps}
                                        />
                                    </div>
                                )}
                            </div>
                        );
                    })}
                </div>

                <div className="mt-5">
                    <Popover
                        align="start"
                        trigger={({ open, toggle }) => (
                            <Button
                                type="button"
                                variant="secondary"
                                size="sm"
                                onClick={toggle}
                                aria-expanded={open}
                                data-testid={`admin-marketing-${pageKey}-add-section`}
                            >
                                <Plus className="h-3.5 w-3.5" strokeWidth={2} />
                                Add section
                                <ChevronDown className="h-3.5 w-3.5" strokeWidth={2} />
                            </Button>
                        )}
                    >
                        <ul className="flex w-72 flex-col gap-0.5">
                            {pickOrder.map((type) => {
                                const meta = BLOCK_REGISTRY[type];
                                const Icon = meta.icon ?? HelpCircle;
                                return (
                                    <li key={type}>
                                        <button
                                            type="button"
                                            onClick={() => addSection(type)}
                                            data-testid={`admin-marketing-${pageKey}-add-section-${type}`}
                                            className="flex w-full items-start gap-2.5 rounded-md px-2 py-1.5 text-left hover:bg-muted"
                                        >
                                            <Icon className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
                                            <div className="min-w-0 flex-1">
                                                <p className="text-[13px] font-medium text-foreground">
                                                    {meta.label}
                                                </p>
                                                <p className="truncate text-[11px] text-muted-foreground">
                                                    {meta.description}
                                                </p>
                                            </div>
                                        </button>
                                    </li>
                                );
                            })}
                        </ul>
                    </Popover>
                </div>
            </Card>

            {submitError && (
                <div
                    className="flex items-start gap-2 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2 text-[12px] font-medium text-destructive"
                    role="alert"
                >
                    <AlertCircle className="mt-0.5 h-3.5 w-3.5" strokeWidth={2.5} />
                    <span>{submitError}</span>
                </div>
            )}

            <div className="flex items-center gap-3">
                <Button
                    type="submit"
                    variant="primary"
                    size="md"
                    disabled={saving}
                    data-testid={`admin-marketing-${pageKey}-save`}
                >
                    {saving ? <Loader2 className="h-3.5 w-3.5 animate-spin" strokeWidth={2} /> : null}
                    {saving ? 'Saving…' : 'Save changes'}
                </Button>
                {savedAt !== null && (
                    <span className="inline-flex items-center gap-1.5 text-[12px] font-medium text-success">
                        <CheckCircle2 className="h-3.5 w-3.5" strokeWidth={2.5} />
                        Saved
                    </span>
                )}
            </div>
        </form>
    );
}

type SectionBodyProps = {
    pageKey: SectionsEditorPageKey;
    section: Section;
    errors?: Record<string, string>;
    onChange: (next: Section) => void;
    extraEditorProps?: Record<string, unknown>;
};

// Registry-driven dispatch. Unknown types render a non-blocking warning so
// admin's saved data round-trips cleanly even when the renderer doesn't
// recognize the block type (e.g. downgrade from a future schema version).
function SectionBody({ pageKey, section, errors, onChange, extraEditorProps }: SectionBodyProps) {
    const meta = getBlockMeta(section.type);
    if (!meta) {
        return (
            <div
                role="status"
                className="flex items-start gap-2 rounded-lg border border-warning/30 bg-warning/10 px-3 py-2 text-[12px] text-foreground"
                data-testid={`admin-marketing-${pageKey}-unknown-${section.type}`}
            >
                <AlertTriangle className="mt-0.5 h-3.5 w-3.5 shrink-0" strokeWidth={2.5} />
                <span>
                    Unknown block type: <code className="font-mono">{section.type}</code> — likely from a newer version. Save will preserve this section.
                </span>
            </div>
        );
    }
    // Editor signature is strictly typed per-section in BLOCK_REGISTRY (value
    // narrows on `type`). Spreading page-level context like `installedKits`
    // through requires loosening to `unknown` here — per-editor signatures
    // still enforce their own optional prop shape.
    const Editor = meta.editor as unknown as ComponentType<Record<string, unknown>>;
    return (
        <Editor
            value={section}
            onChange={onChange}
            errors={errors}
            {...(extraEditorProps ?? {})}
        />
    );
}

const PAGE_HEADER_COPY: Record<
    SectionsEditorPageKey,
    { heading: string; description: string }
> = {
    welcome: {
        heading: 'Welcome page sections',
        description:
            "Reorder, toggle, and edit every section that renders on the public Welcome page. Disabled sections stay in storage but don't render.",
    },
    pricing: {
        heading: 'Pricing page sections',
        description:
            "Reorder, toggle, and edit the framing sections on /pricing. The plan cards are required and always render.",
    },
    templates: {
        heading: 'Templates page sections',
        description:
            "Reorder, toggle, and edit the framing sections on /templates. The kit grid is required and always renders.",
    },
    about: {
        heading: 'About page sections',
        description:
            'Reorder, toggle, and edit every section on the public About page.',
    },
    contact: {
        heading: 'Contact page sections',
        description:
            'Reorder, toggle, and edit every section on the public Contact page.',
    },
    privacy: {
        heading: 'Privacy policy',
        description:
            'Defaults ship with a placeholder outline — review + replace each block with your reviewed legal copy, then publish.',
    },
    terms: {
        heading: 'Terms of use',
        description:
            'Defaults ship with a placeholder outline — review + replace each block with your reviewed legal copy, then publish.',
    },
    refund: {
        heading: 'Refund policy',
        description:
            'Defaults ship with a placeholder outline — replace with your actual refund window + process, then publish.',
    },
};
