'use client'

/**
 * 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'

interface RobotsSettings {
  noIndex?: boolean
  noFollow?: boolean
  noArchive?: boolean
  noSnippet?: boolean
}

interface SiteSEO {
  siteUrl?: string
  siteName?: string
  defaultOgImage?: string
  twitterHandle?: string
  robots?: { blockAIBots?: boolean; disabled?: boolean }
  sitemap?: {
    disabled?: boolean
    defaultChangeFreq?: string
    defaultPriority?: number
  }
  ogImage?: { disabled?: boolean; theme?: 'light' | 'dark' }
}

interface CollectionSEO {
  archivePath?: string
  defaultSchemaType?: string
  sitemapPriority?: number
  sitemapChangeFreq?: string
  excludeFromSitemap?: boolean
  defaultRobots?: RobotsSettings
}

interface ConfigCollection {
  slug: string
  label: string
  type: 'page' | 'post' | string
  urlPrefix?: string
  staticSeo: CollectionSEO | null
  effectiveSeo: CollectionSEO | null
}

interface ConfigResponse {
  static: { site: SiteSEO | null }
  overrides: {
    site?: Partial<SiteSEO>
    collections?: Record<string, Partial<CollectionSEO>>
  } | null
  effective: { site: SiteSEO | null }
  collections: ConfigCollection[]
}

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<ConfigResponse | null>(null)
  const [loading, setLoading] = useState(true)
  const [saving, setSaving] = useState(false)
  const [error, setError] = useState<string | null>(null)

  const [site, setSite] = useState<Partial<SiteSEO>>({})
  const [collections, setCollections] = useState<Record<string, Partial<CollectionSEO>>>({})

  async function load() {
    setLoading(true)
    setError(null)
    const res = await cmsApi<ConfigResponse>('/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: Record<string, Partial<CollectionSEO>> = {}
    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<K extends keyof SiteSEO>(key: K, value: SiteSEO[K]) {
    setSite((prev) => ({ ...prev, [key]: value }))
  }
  function patchSiteRobots(key: 'blockAIBots' | 'disabled', value: boolean) {
    setSite((prev) => ({ ...prev, robots: { ...(prev.robots ?? {}), [key]: value } }))
  }
  function patchSiteSitemap<K extends keyof NonNullable<SiteSEO['sitemap']>>(
    key: K,
    value: NonNullable<SiteSEO['sitemap']>[K],
  ) {
    setSite((prev) => ({ ...prev, sitemap: { ...(prev.sitemap ?? {}), [key]: value } }))
  }
  function patchSiteOg<K extends keyof NonNullable<SiteSEO['ogImage']>>(
    key: K,
    value: NonNullable<SiteSEO['ogImage']>[K],
  ) {
    setSite((prev) => ({ ...prev, ogImage: { ...(prev.ogImage ?? {}), [key]: value } }))
  }
  function patchCollection<K extends keyof CollectionSEO>(
    slug: string,
    key: K,
    value: CollectionSEO[K],
  ) {
    setCollections((prev) => ({
      ...prev,
      [slug]: { ...(prev[slug] ?? {}), [key]: value },
    }))
  }
  function patchCollectionRobots(slug: string, key: keyof RobotsSettings, value: boolean) {
    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 as { updatedAt?: string } | undefined)?.updatedAt
    if (!ts) return null
    try {
      return new Date(ts).toLocaleString()
    } catch {
      return ts
    }
  }, [data])

  if (loading) {
    return (
      <div className="text-muted-foreground flex items-center gap-2 py-8 text-sm">
        <Loader2 className="h-4 w-4 animate-spin" /> Loading SEO defaults…
      </div>
    )
  }

  if (error) {
    return (
      <div className="border-destructive/30 bg-destructive/10 text-destructive rounded-lg border p-4 text-sm">
        <div className="mb-2 font-medium">Failed to load SEO config</div>
        <div className="font-mono text-xs">{error}</div>
        <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"
        >
          <RefreshCw className="h-3.5 w-3.5" /> Retry
        </button>
      </div>
    )
  }

  return (
    <div className="space-y-6">
      <div className="flex items-start justify-between gap-4">
        <div>
          <p className="text-muted-foreground text-sm">
            Edit the SEO defaults that flow into{' '}
            <code className="bg-muted rounded px-1 py-0.5 text-[11px]">/sitemap.xml</code>,{' '}
            <code className="bg-muted rounded px-1 py-0.5 text-[11px]">/robots.txt</code>,{' '}
            <code className="bg-muted rounded px-1 py-0.5 text-[11px]">/og.png</code>, and the{' '}
            <code className="bg-muted rounded px-1 py-0.5 text-[11px]">/resolve</code> meta +
            JSON-LD response. Empty fields fall back to{' '}
            <code className="bg-muted rounded px-1 py-0.5 text-[11px]">actuate.config.ts</code>.
          </p>
          {updatedAt && (
            <p className="text-muted-foreground mt-1 text-xs">Last saved: {updatedAt}</p>
          )}
        </div>
        <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"
        >
          {saving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
          Save SEO defaults
        </button>
      </div>

      {/* Site-wide section */}
      <section className="border-border bg-card rounded-lg border">
        <header className="border-border flex items-center gap-2 border-b px-4 py-3">
          <Globe className="text-primary h-4 w-4" />
          <h3 className="text-foreground text-sm font-medium">Site-wide defaults</h3>
        </header>
        <div className="grid grid-cols-1 gap-4 p-4 md:grid-cols-2">
          <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."
          />
          <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."
          />
          <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."
          />
          <Field
            label="Twitter handle"
            placeholder={staticSite.twitterHandle ?? '@yourbrand'}
            value={site.twitterHandle ?? ''}
            onChange={(v) => patchSite('twitterHandle', v)}
            hint="Including the leading @."
          />
        </div>

        <div className="grid grid-cols-1 gap-4 border-t border-gray-100 p-4 md:grid-cols-3">
          <Select
            label="Default change frequency"
            value={site.sitemap?.defaultChangeFreq ?? ''}
            options={CHANGE_FREQS}
            onChange={(v) => patchSiteSitemap('defaultChangeFreq', v as any)}
            placeholderLabel={`default: ${staticSite.sitemap?.defaultChangeFreq ?? 'weekly'}`}
            hint="Applied per-URL in sitemap.xml when a collection doesn't override."
          />
          <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."
          />
          <Select
            label="OG image theme"
            value={site.ogImage?.theme ?? ''}
            options={['', 'light', 'dark']}
            onChange={(v) => patchSiteOg('theme', (v || undefined) as 'light' | 'dark' | undefined)}
            placeholderLabel={`default: ${staticSite.ogImage?.theme ?? 'light'}`}
            hint="Applied by the built-in /og.png renderer."
          />
        </div>

        <div className="flex flex-wrap gap-6 border-t border-gray-100 p-4">
          <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."
          />
          <Toggle
            label="Disable robots.txt route"
            checked={!!site.robots?.disabled}
            onChange={(v) => patchSiteRobots('disabled', v)}
          />
          <Toggle
            label="Disable sitemap.xml route"
            checked={!!site.sitemap?.disabled}
            onChange={(v) => patchSiteSitemap('disabled', v)}
          />
          <Toggle
            label="Disable /og.png route"
            checked={!!site.ogImage?.disabled}
            onChange={(v) => patchSiteOg('disabled', v)}
          />
        </div>
      </section>

      {/* Per-collection section */}
      <section className="border-border bg-card rounded-lg border">
        <header className="border-border flex items-center gap-2 border-b px-4 py-3">
          <FileText className="text-primary h-4 w-4" />
          <h3 className="text-foreground text-sm font-medium">Per-collection defaults</h3>
          <span className="text-muted-foreground text-xs">
            — shown placeholders are the static defaults
          </span>
        </header>
        <div className="divide-y divide-gray-100">
          {data?.collections.map((col) => {
            const override = collections[col.slug] ?? {}
            const staticC = col.staticSeo ?? {}
            return (
              <div key={col.slug} className="p-4">
                <div className="mb-3 flex items-baseline justify-between gap-4">
                  <div>
                    <h4 className="text-foreground text-sm font-medium">{col.label}</h4>
                    <p className="text-muted-foreground text-xs">
                      <code className="bg-muted rounded px-1 py-0.5">{col.slug}</code>
                      {col.urlPrefix !== undefined && (
                        <>
                          {' '}
                          prefix:{' '}
                          <code className="bg-muted rounded px-1 py-0.5">/{col.urlPrefix}</code>
                        </>
                      )}{' '}
                      — {col.type}
                    </p>
                  </div>
                </div>

                <div className="grid grid-cols-1 gap-3 md:grid-cols-3">
                  <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'}`}
                  />
                  <Field
                    label="Archive path"
                    placeholder={staticC.archivePath ?? '/blog'}
                    value={override.archivePath ?? ''}
                    onChange={(v) => patchCollection(col.slug, 'archivePath', v)}
                  />
                  <Select
                    label="Sitemap change frequency"
                    value={override.sitemapChangeFreq ?? ''}
                    options={CHANGE_FREQS}
                    onChange={(v) => patchCollection(col.slug, 'sitemapChangeFreq', v as any)}
                    placeholderLabel={`default: ${staticC.sitemapChangeFreq ?? 'weekly'}`}
                  />
                  <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}
                  />
                </div>

                <div className="mt-3 flex flex-wrap gap-4">
                  <Toggle
                    label="Exclude from sitemap"
                    checked={!!override.excludeFromSitemap}
                    onChange={(v) => patchCollection(col.slug, 'excludeFromSitemap', v)}
                  />
                  <Toggle
                    label="Default: noindex"
                    checked={!!override.defaultRobots?.noIndex}
                    onChange={(v) => patchCollectionRobots(col.slug, 'noIndex', v)}
                  />
                  <Toggle
                    label="Default: nofollow"
                    checked={!!override.defaultRobots?.noFollow}
                    onChange={(v) => patchCollectionRobots(col.slug, 'noFollow', v)}
                  />
                </div>
              </div>
            )
          })}
          {data?.collections.length === 0 && (
            <div className="text-muted-foreground p-4 text-sm">
              No collections configured in <code>actuate.config.ts</code>.
            </div>
          )}
        </div>
      </section>

      <div className="flex justify-end">
        <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"
        >
          {saving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
          Save SEO defaults
        </button>
      </div>
    </div>
  )
}

function stripEmpty<T extends Record<string, any>>(obj: T): Partial<T> {
  const out: Record<string, any> = {}
  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 as Record<string, any>)
      if (Object.keys(nested).length > 0) out[k] = nested
      continue
    }
    out[k] = v
  }
  return out as Partial<T>
}

function Field({
  label,
  value,
  placeholder,
  onChange,
  hint,
}: {
  label: string
  value: string
  placeholder?: string
  onChange: (v: string) => void
  hint?: string
}) {
  return (
    <div>
      <label className="text-foreground mb-1 block text-xs font-medium">{label}</label>
      <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 && <p className="text-muted-foreground mt-1 text-[11px]">{hint}</p>}
    </div>
  )
}

function NumberField({
  label,
  value,
  placeholder,
  onChange,
  min,
  max,
  step,
  hint,
}: {
  label: string
  value: number | undefined
  placeholder?: string
  onChange: (v: number | undefined) => void
  min?: number
  max?: number
  step?: number
  hint?: string
}) {
  return (
    <div>
      <label className="text-foreground mb-1 block text-xs font-medium">{label}</label>
      <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 && <p className="text-muted-foreground mt-1 text-[11px]">{hint}</p>}
    </div>
  )
}

function Select({
  label,
  value,
  options,
  onChange,
  placeholderLabel,
  hint,
}: {
  label: string
  value: string
  options: string[]
  onChange: (v: string) => void
  placeholderLabel?: string
  hint?: string
}) {
  return (
    <div>
      <label className="text-foreground mb-1 block text-xs font-medium">{label}</label>
      <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"
      >
        {options.map((opt) => (
          <option key={opt} value={opt}>
            {opt === '' ? (placeholderLabel ?? '— use default —') : opt}
          </option>
        ))}
      </select>
      {hint && <p className="text-muted-foreground mt-1 text-[11px]">{hint}</p>}
    </div>
  )
}

function Toggle({
  label,
  checked,
  onChange,
  hint,
}: {
  label: string
  checked: boolean
  onChange: (v: boolean) => void
  hint?: string
}) {
  return (
    <label className="text-foreground flex cursor-pointer items-center gap-2 text-sm">
      <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"
      />
      <span>
        {label}
        {hint && <span className="text-muted-foreground ml-1 text-[11px]">— {hint}</span>}
      </span>
    </label>
  )
}
