'use client';
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
/**
 * Admin UI for editing the site-wide + per-collection SEO defaults that are
 * normally defined in `actuate.config.ts`. The CMS merges code-level config
 * with these DB-stored overrides, so any field left blank here falls back to
 * the static config (shown as the placeholder).
 *
 * Wired in `Settings.tsx` as the "SEO" tab. Talks to `/seo/config` (GET + PUT).
 */
import { Loader2, RefreshCw, Save, Globe, FileText } from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
import { toast } from 'sonner';
import { cmsApi } from '../lib/api.js';
const SCHEMA_TYPES = [
    '',
    'WebPage',
    'Article',
    'BlogPosting',
    'NewsArticle',
    'Product',
    'Service',
    'LocalBusiness',
    'FAQPage',
    'JobPosting',
    'Event',
    'Recipe',
    'Person',
    'Organization',
];
const CHANGE_FREQS = ['', 'always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'];
export function SEOConfigPanel() {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [saving, setSaving] = useState(false);
    const [error, setError] = useState(null);
    const [site, setSite] = useState({});
    const [collections, setCollections] = useState({});
    async function load() {
        setLoading(true);
        setError(null);
        const res = await cmsApi('/seo/config', { method: 'GET' });
        if (res.error || !res.data) {
            setError(res.error ?? 'Failed to load SEO config');
            setLoading(false);
            return;
        }
        setData(res.data);
        setSite(res.data.overrides?.site ?? {});
        setCollections(res.data.overrides?.collections ?? {});
        setLoading(false);
    }
    useEffect(() => {
        void load();
    }, []);
    // Field-level placeholder = the static value from actuate.config.ts. The
    // input value is whatever the admin has typed (or the saved override). Empty
    // input + empty override means "fall back to static", which is the desired
    // UX for an additive overrides panel.
    const staticSite = data?.static?.site ?? {};
    async function handleSave() {
        setSaving(true);
        // Strip empty strings so they unset back to the static default rather
        // than persisting "" — the store treats "" as "no override".
        const cleanSite = stripEmpty(site);
        const cleanCollections = {};
        for (const [slug, c] of Object.entries(collections)) {
            const cleaned = stripEmpty(c);
            if (Object.keys(cleaned).length > 0)
                cleanCollections[slug] = cleaned;
        }
        const res = await cmsApi('/seo/config', {
            method: 'PUT',
            body: JSON.stringify({ site: cleanSite, collections: cleanCollections }),
        });
        if (res.error) {
            toast.error(res.error);
        }
        else {
            toast.success('SEO defaults saved');
            await load();
        }
        setSaving(false);
    }
    function patchSite(key, value) {
        setSite((prev) => ({ ...prev, [key]: value }));
    }
    function patchSiteRobots(key, value) {
        setSite((prev) => ({ ...prev, robots: { ...(prev.robots ?? {}), [key]: value } }));
    }
    function patchSiteSitemap(key, value) {
        setSite((prev) => ({ ...prev, sitemap: { ...(prev.sitemap ?? {}), [key]: value } }));
    }
    function patchSiteOg(key, value) {
        setSite((prev) => ({ ...prev, ogImage: { ...(prev.ogImage ?? {}), [key]: value } }));
    }
    function patchCollection(slug, key, value) {
        setCollections((prev) => ({
            ...prev,
            [slug]: { ...(prev[slug] ?? {}), [key]: value },
        }));
    }
    function patchCollectionRobots(slug, key, value) {
        setCollections((prev) => ({
            ...prev,
            [slug]: {
                ...(prev[slug] ?? {}),
                defaultRobots: { ...(prev[slug]?.defaultRobots ?? {}), [key]: value },
            },
        }));
    }
    const updatedAt = useMemo(() => {
        // `updatedAt` is set by the server when overrides are persisted but isn't
        // part of the declared `SeoConfigOverrides` shape. Cast through `unknown`
        // so we can surface it in the UI without widening the type contract.
        const ts = data?.overrides?.updatedAt;
        if (!ts)
            return null;
        try {
            return new Date(ts).toLocaleString();
        }
        catch {
            return ts;
        }
    }, [data]);
    if (loading) {
        return (_jsxs("div", { className: "text-muted-foreground flex items-center gap-2 py-8 text-sm", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Loading SEO defaults\u2026"] }));
    }
    if (error) {
        return (_jsxs("div", { className: "border-destructive/30 bg-destructive/10 text-destructive rounded-lg border p-4 text-sm", children: [_jsx("div", { className: "mb-2 font-medium", children: "Failed to load SEO config" }), _jsx("div", { className: "font-mono text-xs", children: error }), _jsxs("button", { onClick: () => void load(), className: "border-destructive bg-card text-destructive hover:bg-destructive/10 mt-3 inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-xs font-medium", children: [_jsx(RefreshCw, { className: "h-3.5 w-3.5" }), " Retry"] })] }));
    }
    return (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsxs("p", { className: "text-muted-foreground text-sm", children: ["Edit the SEO defaults that flow into", ' ', _jsx("code", { className: "bg-muted rounded px-1 py-0.5 text-[11px]", children: "/sitemap.xml" }), ",", ' ', _jsx("code", { className: "bg-muted rounded px-1 py-0.5 text-[11px]", children: "/robots.txt" }), ",", ' ', _jsx("code", { className: "bg-muted rounded px-1 py-0.5 text-[11px]", children: "/og.png" }), ", and the", ' ', _jsx("code", { className: "bg-muted rounded px-1 py-0.5 text-[11px]", children: "/resolve" }), " meta + JSON-LD response. Empty fields fall back to", ' ', _jsx("code", { className: "bg-muted rounded px-1 py-0.5 text-[11px]", children: "actuate.config.ts" }), "."] }), updatedAt && (_jsxs("p", { className: "text-muted-foreground mt-1 text-xs", children: ["Last saved: ", updatedAt] }))] }), _jsxs("button", { onClick: handleSave, disabled: saving, className: "bg-primary hover:bg-primary/90 inline-flex shrink-0 items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium text-white shadow-sm disabled:opacity-50", children: [saving ? _jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : _jsx(Save, { className: "h-4 w-4" }), "Save SEO defaults"] })] }), _jsxs("section", { className: "border-border bg-card rounded-lg border", children: [_jsxs("header", { className: "border-border flex items-center gap-2 border-b px-4 py-3", children: [_jsx(Globe, { className: "text-primary h-4 w-4" }), _jsx("h3", { className: "text-foreground text-sm font-medium", children: "Site-wide defaults" })] }), _jsxs("div", { className: "grid grid-cols-1 gap-4 p-4 md:grid-cols-2", children: [_jsx(Field, { label: "Site URL", placeholder: staticSite.siteUrl ?? 'https://example.com', value: site.siteUrl ?? '', onChange: (v) => patchSite('siteUrl', v), hint: "Canonical origin used in sitemap URLs and JSON-LD." }), _jsx(Field, { label: "Site name", placeholder: staticSite.siteName ?? 'My Site', value: site.siteName ?? '', onChange: (v) => patchSite('siteName', v), hint: "Brand name in OG cards and Schema.org Organization." }), _jsx(Field, { label: "Default OG image", placeholder: staticSite.defaultOgImage ?? '/og-default.png', value: site.defaultOgImage ?? '', onChange: (v) => patchSite('defaultOgImage', v), hint: "Used when a document doesn't supply its own." }), _jsx(Field, { label: "Twitter handle", placeholder: staticSite.twitterHandle ?? '@yourbrand', value: site.twitterHandle ?? '', onChange: (v) => patchSite('twitterHandle', v), hint: "Including the leading @." })] }), _jsxs("div", { className: "grid grid-cols-1 gap-4 border-t border-gray-100 p-4 md:grid-cols-3", children: [_jsx(Select, { label: "Default change frequency", value: site.sitemap?.defaultChangeFreq ?? '', options: CHANGE_FREQS, onChange: (v) => patchSiteSitemap('defaultChangeFreq', v), placeholderLabel: `default: ${staticSite.sitemap?.defaultChangeFreq ?? 'weekly'}`, hint: "Applied per-URL in sitemap.xml when a collection doesn't override." }), _jsx(NumberField, { label: "Default priority", value: site.sitemap?.defaultPriority, placeholder: `default: ${staticSite.sitemap?.defaultPriority ?? 0.6}`, onChange: (v) => patchSiteSitemap('defaultPriority', v), min: 0, max: 1, step: 0.1, hint: "Between 0.0 and 1.0." }), _jsx(Select, { label: "OG image theme", value: site.ogImage?.theme ?? '', options: ['', 'light', 'dark'], onChange: (v) => patchSiteOg('theme', (v || undefined)), placeholderLabel: `default: ${staticSite.ogImage?.theme ?? 'light'}`, hint: "Applied by the built-in /og.png renderer." })] }), _jsxs("div", { className: "flex flex-wrap gap-6 border-t border-gray-100 p-4", children: [_jsx(Toggle, { label: "Block known AI bots in robots.txt", checked: !!site.robots?.blockAIBots, onChange: (v) => patchSiteRobots('blockAIBots', v), hint: "GPTBot, ClaudeBot, anthropic-ai, Bytespider, etc." }), _jsx(Toggle, { label: "Disable robots.txt route", checked: !!site.robots?.disabled, onChange: (v) => patchSiteRobots('disabled', v) }), _jsx(Toggle, { label: "Disable sitemap.xml route", checked: !!site.sitemap?.disabled, onChange: (v) => patchSiteSitemap('disabled', v) }), _jsx(Toggle, { label: "Disable /og.png route", checked: !!site.ogImage?.disabled, onChange: (v) => patchSiteOg('disabled', v) })] })] }), _jsxs("section", { className: "border-border bg-card rounded-lg border", children: [_jsxs("header", { className: "border-border flex items-center gap-2 border-b px-4 py-3", children: [_jsx(FileText, { className: "text-primary h-4 w-4" }), _jsx("h3", { className: "text-foreground text-sm font-medium", children: "Per-collection defaults" }), _jsx("span", { className: "text-muted-foreground text-xs", children: "\u2014 shown placeholders are the static defaults" })] }), _jsxs("div", { className: "divide-y divide-gray-100", children: [data?.collections.map((col) => {
                                const override = collections[col.slug] ?? {};
                                const staticC = col.staticSeo ?? {};
                                return (_jsxs("div", { className: "p-4", children: [_jsx("div", { className: "mb-3 flex items-baseline justify-between gap-4", children: _jsxs("div", { children: [_jsx("h4", { className: "text-foreground text-sm font-medium", children: col.label }), _jsxs("p", { className: "text-muted-foreground text-xs", children: [_jsx("code", { className: "bg-muted rounded px-1 py-0.5", children: col.slug }), col.urlPrefix !== undefined && (_jsxs(_Fragment, { children: [' ', "prefix:", ' ', _jsxs("code", { className: "bg-muted rounded px-1 py-0.5", children: ["/", col.urlPrefix] })] })), ' ', "\u2014 ", col.type] })] }) }), _jsxs("div", { className: "grid grid-cols-1 gap-3 md:grid-cols-3", children: [_jsx(Select, { label: "Default Schema.org type", value: override.defaultSchemaType ?? '', options: SCHEMA_TYPES, onChange: (v) => patchCollection(col.slug, 'defaultSchemaType', v || undefined), placeholderLabel: `default: ${staticC.defaultSchemaType ?? 'auto-detect'}` }), _jsx(Field, { label: "Archive path", placeholder: staticC.archivePath ?? '/blog', value: override.archivePath ?? '', onChange: (v) => patchCollection(col.slug, 'archivePath', v) }), _jsx(Select, { label: "Sitemap change frequency", value: override.sitemapChangeFreq ?? '', options: CHANGE_FREQS, onChange: (v) => patchCollection(col.slug, 'sitemapChangeFreq', v), placeholderLabel: `default: ${staticC.sitemapChangeFreq ?? 'weekly'}` }), _jsx(NumberField, { label: "Sitemap priority", value: override.sitemapPriority, placeholder: `default: ${staticC.sitemapPriority ?? (col.type === 'page' ? 0.8 : 0.6)}`, onChange: (v) => patchCollection(col.slug, 'sitemapPriority', v), min: 0, max: 1, step: 0.1 })] }), _jsxs("div", { className: "mt-3 flex flex-wrap gap-4", children: [_jsx(Toggle, { label: "Exclude from sitemap", checked: !!override.excludeFromSitemap, onChange: (v) => patchCollection(col.slug, 'excludeFromSitemap', v) }), _jsx(Toggle, { label: "Default: noindex", checked: !!override.defaultRobots?.noIndex, onChange: (v) => patchCollectionRobots(col.slug, 'noIndex', v) }), _jsx(Toggle, { label: "Default: nofollow", checked: !!override.defaultRobots?.noFollow, onChange: (v) => patchCollectionRobots(col.slug, 'noFollow', v) })] })] }, col.slug));
                            }), data?.collections.length === 0 && (_jsxs("div", { className: "text-muted-foreground p-4 text-sm", children: ["No collections configured in ", _jsx("code", { children: "actuate.config.ts" }), "."] }))] })] }), _jsx("div", { className: "flex justify-end", children: _jsxs("button", { onClick: handleSave, disabled: saving, className: "bg-primary hover:bg-primary/90 inline-flex items-center gap-1.5 rounded-md px-4 py-2 text-sm font-medium text-white shadow-sm disabled:opacity-50", children: [saving ? _jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : _jsx(Save, { className: "h-4 w-4" }), "Save SEO defaults"] }) })] }));
}
function stripEmpty(obj) {
    const out = {};
    for (const [k, v] of Object.entries(obj)) {
        if (v === undefined || v === null)
            continue;
        if (typeof v === 'string' && v.trim() === '')
            continue;
        if (typeof v === 'object' && !Array.isArray(v)) {
            const nested = stripEmpty(v);
            if (Object.keys(nested).length > 0)
                out[k] = nested;
            continue;
        }
        out[k] = v;
    }
    return out;
}
function Field({ label, value, placeholder, onChange, hint, }) {
    return (_jsxs("div", { children: [_jsx("label", { className: "text-foreground mb-1 block text-xs font-medium", children: label }), _jsx("input", { type: "text", value: value, placeholder: placeholder, onChange: (e) => onChange(e.target.value), className: "border-border text-foreground focus-visible:border-ring focus-visible:ring-ring/50 w-full rounded-md border px-2.5 py-1.5 text-sm placeholder-gray-400 focus:ring-1 focus:outline-none" }), hint && _jsx("p", { className: "text-muted-foreground mt-1 text-[11px]", children: hint })] }));
}
function NumberField({ label, value, placeholder, onChange, min, max, step, hint, }) {
    return (_jsxs("div", { children: [_jsx("label", { className: "text-foreground mb-1 block text-xs font-medium", children: label }), _jsx("input", { type: "number", value: value ?? '', placeholder: placeholder, min: min, max: max, step: step, onChange: (e) => {
                    const raw = e.target.value;
                    if (raw === '')
                        return onChange(undefined);
                    const n = Number(raw);
                    if (Number.isFinite(n))
                        onChange(n);
                }, className: "border-border text-foreground focus-visible:border-ring focus-visible:ring-ring/50 w-full rounded-md border px-2.5 py-1.5 text-sm placeholder-gray-400 focus:ring-1 focus:outline-none" }), hint && _jsx("p", { className: "text-muted-foreground mt-1 text-[11px]", children: hint })] }));
}
function Select({ label, value, options, onChange, placeholderLabel, hint, }) {
    return (_jsxs("div", { children: [_jsx("label", { className: "text-foreground mb-1 block text-xs font-medium", children: label }), _jsx("select", { value: value, onChange: (e) => onChange(e.target.value), className: "border-border bg-card text-foreground focus-visible:border-ring focus-visible:ring-ring/50 w-full rounded-md border px-2.5 py-1.5 text-sm focus:ring-1 focus:outline-none", children: options.map((opt) => (_jsx("option", { value: opt, children: opt === '' ? (placeholderLabel ?? '— use default —') : opt }, opt))) }), hint && _jsx("p", { className: "text-muted-foreground mt-1 text-[11px]", children: hint })] }));
}
function Toggle({ label, checked, onChange, hint, }) {
    return (_jsxs("label", { className: "text-foreground flex cursor-pointer items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: checked, onChange: (e) => onChange(e.target.checked), className: "border-border text-primary focus-visible:ring-ring/50 h-4 w-4 rounded" }), _jsxs("span", { children: [label, hint && _jsxs("span", { className: "text-muted-foreground ml-1 text-[11px]", children: ["\u2014 ", hint] })] })] }));
}
//# sourceMappingURL=SEOConfigPanel.js.map