'use client'

import { useCallback, useEffect, useMemo, useState } from 'react'
import { cmsApi } from '../../lib/api.js'
import type { SettingsSourceMetadata, ValidationIssue } from './useGeneralSettings.js'

/**
 * Settings → SEO ("Meta Defaults / Verification / Sitemap") form state.
 *
 * Single source of truth: every field reads from and writes back to the SAME
 * shared SEO config service used by Settings → General and the /seo workspace.
 *   - meta defaults + verification  → `/seo/config` (`site` overrides)
 *   - sitemap auto-generate / ping  → `/seo/crawl-settings` (`module`)
 *   - sitemap status (read-only)    → `/seo/sitemap/status`
 *
 * It deliberately does NOT own site URL or the document robots defaults — those
 * are owned by Settings → General (also via `/seo/config`) and must not be
 * duplicated here.
 */
export interface SeoSettingsForm {
  /** Site-wide title template (`%page% — %site%`), applied when a page has none. */
  titleTemplate: string
  /** Fallback meta description when a page/post defines none. */
  defaultMetaDescription: string
  /**
   * Fallback social (Open Graph / Twitter) image URL. Kept as a denormalized
   * cache of the selected asset's public URL so the synchronous public renderer
   * doesn't need a DB lookup. Empty = use the dynamic /og.png fallback.
   */
  defaultOgImage: string
  /** Canonical reference to the selected media asset (source of truth). */
  defaultSocialImageAssetId: string
  googleVerification: string
  bingVerification: string
  yandexVerification: string
  pinterestVerification: string
  facebookDomainVerification: string
  /** Rebuild the sitemap whenever content is published (`module`). */
  autoGenerateSitemap: boolean
  /** Notify Google/Bing when the sitemap changes (`module`). */
  pingOnPublish: boolean
}

export interface SitemapSummary {
  sitemapUrl: string
  lastGeneratedAt: string | null
  pagesIncluded: number
  pagesExcluded: number
  warnings: string[]
}

const EMPTY_FORM: SeoSettingsForm = {
  titleTemplate: '',
  defaultMetaDescription: '',
  defaultOgImage: '',
  defaultSocialImageAssetId: '',
  googleVerification: '',
  bingVerification: '',
  yandexVerification: '',
  pinterestVerification: '',
  facebookDomainVerification: '',
  autoGenerateSitemap: true,
  pingOnPublish: true,
}

/** Recommended max length for the default meta description (SERP truncation). */
export const META_DESCRIPTION_MAX = 160
/** Below this, the description is likely too thin to be useful. */
export const META_DESCRIPTION_MIN = 70

// Mirror of cms-core's `SITE_TITLE_TOKENS` (kept tiny + stable so the client can
// validate without importing the full cms-core module graph). The server
// re-validates on save via `validateTitleTemplate`, so this is a UX aid only.
const SITE_TITLE_TOKENS = ['page', 'site', 'type', 'separator', 'author', 'date', 'category', 'tag']
const TOKEN_RE = /%(\w+)%/g

export function validateTitleTemplateClient(template: string): {
  valid: boolean
  unknownTokens: string[]
} {
  if (!template || template.trim() === '') return { valid: true, unknownTokens: [] }
  const known = new Set(SITE_TITLE_TOKENS)
  const unknown: string[] = []
  let hasKnown = false
  for (const m of template.matchAll(TOKEN_RE)) {
    const t = m[1]!
    if (known.has(t)) hasKnown = true
    else if (!unknown.includes(t)) unknown.push(t)
  }
  const literal = template.replace(TOKEN_RE, '').trim()
  if (unknown.length > 0) return { valid: false, unknownTokens: unknown }
  if (!hasKnown && literal === '') return { valid: false, unknownTokens: [] }
  return { valid: true, unknownTokens: [] }
}

function validate(form: SeoSettingsForm): ValidationIssue[] {
  const issues: ValidationIssue[] = []
  const tpl = validateTitleTemplateClient(form.titleTemplate)
  if (!tpl.valid) {
    issues.push({
      field: 'titleTemplate',
      message:
        tpl.unknownTokens.length > 0
          ? `Unsupported token${tpl.unknownTokens.length > 1 ? 's' : ''}: ${tpl.unknownTokens
              .map((t) => `%${t}%`)
              .join(', ')}`
          : 'Template must include at least one token or some literal text.',
      severity: 'error',
    })
  }
  const desc = form.defaultMetaDescription.trim()
  if (desc.length > META_DESCRIPTION_MAX) {
    issues.push({
      field: 'defaultMetaDescription',
      message: `Default description is ${desc.length} characters — search engines truncate around ${META_DESCRIPTION_MAX}.`,
      severity: 'warning',
    })
  } else if (desc.length > 0 && desc.length < META_DESCRIPTION_MIN) {
    issues.push({
      field: 'defaultMetaDescription',
      message: `Default description is short (${desc.length} characters). Aim for ${META_DESCRIPTION_MIN}–${META_DESCRIPTION_MAX}.`,
      severity: 'warning',
    })
  }
  return issues
}

interface SeoConfigResponse {
  static?: { site?: Record<string, any> | null }
  overrides?: { site?: Record<string, any> | null } | null
  effective?: { site?: Record<string, any> | null }
}

type SaveState = 'idle' | 'saving' | 'success' | 'error'

export interface UseSeoSettings {
  loading: boolean
  loadError: string | null
  form: SeoSettingsForm
  setField: <K extends keyof SeoSettingsForm>(key: K, value: SeoSettingsForm[K]) => void
  dirty: boolean
  issues: ValidationIssue[]
  hasErrors: boolean
  sources: Record<string, SettingsSourceMetadata>
  /** Effective site name (read-only here; edited in General/advanced) for preview. */
  siteName: string
  /** Effective site URL for preview + warnings. */
  siteUrl: string
  isProduction: boolean
  sitemap: SitemapSummary | null
  saveState: SaveState
  saveError: string | null
  save: () => Promise<void>
  reset: () => void
  refetch: () => void
  regenerateSitemap: () => Promise<void>
  regenerating: boolean
}

export function useSeoSettings(canEdit: boolean): UseSeoSettings {
  const [loading, setLoading] = useState(true)
  const [loadError, setLoadError] = useState<string | null>(null)
  const [baseline, setBaseline] = useState<SeoSettingsForm>(EMPTY_FORM)
  const [form, setForm] = useState<SeoSettingsForm>(EMPTY_FORM)
  const [overrideSite, setOverrideSite] = useState<Record<string, any>>({})
  const [sources, setSources] = useState<Record<string, SettingsSourceMetadata>>({})
  const [siteName, setSiteName] = useState('')
  const [siteUrl, setSiteUrl] = useState('')
  const [isProduction, setIsProduction] = useState(false)
  const [sitemap, setSitemap] = useState<SitemapSummary | null>(null)
  const [saveState, setSaveState] = useState<SaveState>('idle')
  const [saveError, setSaveError] = useState<string | null>(null)
  const [regenerating, setRegenerating] = useState(false)
  const [reloadKey, setReloadKey] = useState(0)

  useEffect(() => {
    let cancelled = false
    setLoading(true)
    setLoadError(null)
    Promise.all([
      cmsApi<SeoConfigResponse>('/seo/config'),
      cmsApi<Record<string, any>>('/seo/crawl-settings'),
      cmsApi<SitemapSummary>('/seo/sitemap/status'),
      cmsApi<{ environment?: string }>('/seo/index-status'),
    ])
      .then(([seoRes, crawlRes, sitemapRes, statusRes]) => {
        if (cancelled) return
        if (seoRes.error && seoRes.status !== 404) setLoadError(seoRes.error)

        const seo = seoRes.data ?? {}
        const effectiveSite = seo.effective?.site ?? {}
        const staticSite = seo.static?.site ?? {}
        const overSite = seo.overrides?.site ?? {}
        const verification = effectiveSite.verification ?? {}
        const crawl = crawlRes.data ?? {}

        const loaded: SeoSettingsForm = {
          titleTemplate: String(effectiveSite.titleTemplate ?? ''),
          defaultMetaDescription: String(effectiveSite.defaultMetaDescription ?? ''),
          defaultOgImage: String(effectiveSite.defaultOgImage ?? ''),
          defaultSocialImageAssetId: String(effectiveSite.defaultSocialImageAssetId ?? ''),
          googleVerification: String(verification.googleSearchConsole ?? ''),
          bingVerification: String(verification.bingWebmasterTools ?? ''),
          yandexVerification: String(verification.yandex ?? ''),
          pinterestVerification: String(verification.pinterest ?? ''),
          facebookDomainVerification: String(verification.facebookDomain ?? ''),
          autoGenerateSitemap: crawl.autoRegenerateSitemapOnPublish !== false,
          pingOnPublish: crawl.pingSearchEnginesOnPublish !== false,
        }

        // `editable` reflects the field's MANAGEMENT source (config vs database),
        // not the user's role — role gates the inputs via `canEdit`. This keeps
        // the source badge (and the field's accessible label) stable across
        // roles, and only surfaces it when a value is locked by config.
        const configManaged = (key: string): SettingsSourceMetadata =>
          staticSite[key]
            ? { source: 'config', editable: false, reason: 'Set in actuate.config.ts' }
            : { source: 'database', editable: true }

        setSources({
          titleTemplate: configManaged('titleTemplate'),
          defaultMetaDescription: configManaged('defaultMetaDescription'),
          defaultOgImage: configManaged('defaultOgImage'),
          verification: { source: 'database', editable: true },
          sitemap: { source: 'database', editable: true },
        })

        setBaseline(loaded)
        setForm(loaded)
        setOverrideSite(overSite ?? {})
        setSiteName(String(effectiveSite.siteName ?? ''))
        setSiteUrl(String(effectiveSite.siteUrl ?? ''))
        if (statusRes.data?.environment)
          setIsProduction(statusRes.data.environment === 'production')
        if (!sitemapRes.error && sitemapRes.data) setSitemap(sitemapRes.data)
      })
      .catch(() => {
        if (!cancelled) setLoadError('Failed to load SEO settings.')
      })
      .finally(() => {
        if (!cancelled) setLoading(false)
      })
    return () => {
      cancelled = true
    }
  }, [reloadKey, canEdit])

  const setField = useCallback(
    <K extends keyof SeoSettingsForm>(key: K, value: SeoSettingsForm[K]) => {
      setForm((prev) => ({ ...prev, [key]: value }))
      setSaveState('idle')
    },
    [],
  )

  const dirty = useMemo(
    () => (Object.keys(form) as Array<keyof SeoSettingsForm>).some((k) => form[k] !== baseline[k]),
    [form, baseline],
  )
  const issues = useMemo(() => validate(form), [form])
  const hasErrors = issues.some((i) => i.severity === 'error')

  const refetchSitemap = useCallback(async () => {
    const res = await cmsApi<SitemapSummary>('/seo/sitemap/status')
    if (!res.error && res.data) setSitemap(res.data)
  }, [])

  const save = useCallback(async () => {
    if (!canEdit || hasErrors) return
    setSaveState('saving')
    setSaveError(null)

    const siteChanged =
      form.titleTemplate !== baseline.titleTemplate ||
      form.defaultMetaDescription !== baseline.defaultMetaDescription ||
      form.defaultOgImage !== baseline.defaultOgImage ||
      form.defaultSocialImageAssetId !== baseline.defaultSocialImageAssetId ||
      form.googleVerification !== baseline.googleVerification ||
      form.bingVerification !== baseline.bingVerification ||
      form.yandexVerification !== baseline.yandexVerification ||
      form.pinterestVerification !== baseline.pinterestVerification ||
      form.facebookDomainVerification !== baseline.facebookDomainVerification
    const sitemapChanged =
      form.autoGenerateSitemap !== baseline.autoGenerateSitemap ||
      form.pingOnPublish !== baseline.pingOnPublish

    try {
      // 1. Meta defaults + verification → shared /seo/config (merge over the
      //    current override site so General's siteUrl/robots aren't wiped).
      if (siteChanged) {
        const nextSite: Record<string, any> = {
          ...overrideSite,
          titleTemplate: form.titleTemplate.trim() || undefined,
          defaultMetaDescription: form.defaultMetaDescription.trim() || undefined,
          defaultOgImage: form.defaultOgImage.trim() || undefined,
          defaultSocialImageAssetId: form.defaultSocialImageAssetId.trim() || undefined,
          verification: {
            ...(overrideSite.verification ?? {}),
            googleSearchConsole: form.googleVerification.trim() || undefined,
            bingWebmasterTools: form.bingVerification.trim() || undefined,
            yandex: form.yandexVerification.trim() || undefined,
            pinterest: form.pinterestVerification.trim() || undefined,
            facebookDomain: form.facebookDomainVerification.trim() || undefined,
          },
        }
        const res = await cmsApi('/seo/config', {
          method: 'PUT',
          body: JSON.stringify({ site: nextSite }),
        })
        if (res.error) throw new Error(res.error)
        setOverrideSite(nextSite)
      }

      // 2. Sitemap toggles → shared /seo/crawl-settings (`module`).
      if (sitemapChanged) {
        const res = await cmsApi('/seo/crawl-settings', {
          method: 'PUT',
          body: JSON.stringify({
            autoRegenerateSitemapOnPublish: form.autoGenerateSitemap,
            pingSearchEnginesOnPublish: form.pingOnPublish,
          }),
        })
        if (res.error) throw new Error(res.error)
      }

      setBaseline(form)
      setSaveState('success')
      if (sitemapChanged) await refetchSitemap()
    } catch (err) {
      setSaveState('error')
      setSaveError(err instanceof Error ? err.message : 'Failed to save SEO settings.')
    }
  }, [canEdit, hasErrors, form, baseline, overrideSite, refetchSitemap])

  const reset = useCallback(() => {
    setForm(baseline)
    setSaveState('idle')
    setSaveError(null)
  }, [baseline])

  const refetch = useCallback(() => setReloadKey((k) => k + 1), [])

  const regenerateSitemap = useCallback(async () => {
    setRegenerating(true)
    try {
      const res = await cmsApi<SitemapSummary>('/seo/sitemap/regenerate', { method: 'POST' })
      if (!res.error && res.data) setSitemap((prev) => ({ ...(prev ?? res.data!), ...res.data! }))
    } finally {
      setRegenerating(false)
    }
  }, [])

  return {
    loading,
    loadError,
    form,
    setField,
    dirty,
    issues,
    hasErrors,
    sources,
    siteName,
    siteUrl,
    isProduction,
    sitemap,
    saveState,
    saveError,
    save,
    reset,
    refetch,
    regenerateSitemap,
    regenerating,
  }
}
