'use client'

import { useCallback, useEffect, useMemo, useState } from 'react'
import { AlertTriangle, Loader2 } from 'lucide-react'
import { toast } from 'sonner'
import { ConfirmDialog } from '../../components/ui/ConfirmDialog.js'
import { ErrorBoundary } from '../../components/ErrorBoundary.js'
import {
  addSection,
  createSection,
  duplicateSection,
  emptyPage,
  fetchPageForEditor,
  missingPageFields,
  moveSection,
  publishPage,
  removeSection,
  reorderSections,
  savePageDraft,
  toggleSectionVisibility,
  updateSectionContent,
  updateSectionMeta,
  updateSectionSettings,
  validatePage,
  type EditorPage,
  type ValidationResult,
} from '../../lib/page-editor-service.js'
import {
  buildPublicPreviewUrl,
  createPreviewToken,
  resolveSiteUrl,
} from '../../lib/preview-link.js'
import type { SectionSettings, SectionTypeId } from './section-types.js'
import { DEFAULT_VIEWPORT, type ViewportId } from './viewports.js'
import { EditorTopBar, type SaveState } from './EditorTopBar.js'
import { SectionsPanel } from './SectionsPanel.js'
import { EditorCanvas } from './EditorCanvas.js'
import { SectionInspector } from './SectionInspector.js'
import { AddSectionModal } from './AddSectionModal.js'
import { PageSettingsModal } from './PageSettingsModal.js'
import { ValidationSummary } from './ValidationSummary.js'

interface PageSectionEditorProps {
  /** undefined when creating a new page (`/pages/new`) */
  documentId?: string
  config?: any
  session?: any
  onNavigate: (path: string) => void
  /**
   * Open a preview route in a new browser tab. `prepare` runs before the tab
   * navigates (used to save an unsaved draft first); returning `false` cancels
   * the open. Falls back to in-tab navigation when not provided.
   */
  onOpenPreview?: (path: string, prepare?: () => Promise<boolean | void> | boolean | void) => void
}

function deriveCan(session: any): { canEdit: boolean; canPublish: boolean } {
  const role: string | undefined = session?.user?.role
  // No session info (e.g. local dev) → allow; otherwise gate by role.
  if (!role) return { canEdit: true, canPublish: true }
  const editors = ['ADMIN', 'EDITOR', 'AUTHOR']
  const publishers = ['ADMIN', 'EDITOR']
  return { canEdit: editors.includes(role), canPublish: publishers.includes(role) }
}

export function PageSectionEditor({
  documentId,
  config,
  session,
  onNavigate,
  onOpenPreview,
}: PageSectionEditorProps) {
  const [page, setPage] = useState<EditorPage | null>(null)
  const [loading, setLoading] = useState(true)
  const [loadError, setLoadError] = useState<string | null>(null)

  const [selectedId, setSelectedId] = useState<string | null>(null)
  const [viewport, setViewport] = useState<ViewportId>(DEFAULT_VIEWPORT)
  const [dirty, setDirty] = useState(false)
  const [saveState, setSaveState] = useState<SaveState>('idle')
  const [publishing, setPublishing] = useState(false)

  const [addOpen, setAddOpen] = useState(false)
  const [settingsOpen, setSettingsOpen] = useState(false)
  const [deleteTargetId, setDeleteTargetId] = useState<string | null>(null)
  const [confirmPublish, setConfirmPublish] = useState(false)
  const [leaveTarget, setLeaveTarget] = useState<string | null>(null)
  const [validation, setValidation] = useState<ValidationResult | null>(null)

  const { canEdit, canPublish } = useMemo(() => deriveCan(session), [session])

  // A misconfigured `pages` collection (missing the fields the editor
  // writes) would silently drop content on save — detect it up front.
  // Memoized to a string/null so the load effect can depend on it without
  // re-running when `config`'s object identity changes between renders.
  const schemaError = useMemo(() => {
    const missing = missingPageFields(config)
    if (missing.length === 0) return null
    const plural = missing.length > 1
    return (
      `The "pages" collection is missing required field${plural ? 's' : ''}: ` +
      `${missing.join(', ')}. Declare ${plural ? 'them' : 'it'} in your CMS config ` +
      `so the page editor can save without losing content.`
    )
  }, [config])

  // ─── Load ──────────────────────────────────────────────────────────
  useEffect(() => {
    let cancelled = false
    setLoading(true)
    setLoadError(null)

    // Fail loud on a misconfigured `pages` collection rather than letting
    // the user build a page whose content would be silently dropped on save.
    if (schemaError) {
      setLoadError(schemaError)
      setLoading(false)
      return
    }

    if (!documentId) {
      setPage(emptyPage())
      setLoading(false)
      return
    }

    fetchPageForEditor(documentId)
      .then((p) => {
        if (cancelled) return
        setPage(p)
        // Default to a clean canvas (no selection / no inspector) to match
        // the editor's default composition; the inspector opens only once
        // the user picks a section to edit.
        setSelectedId(null)
      })
      .catch((err) => {
        if (!cancelled) setLoadError(err instanceof Error ? err.message : 'Failed to load page')
      })
      .finally(() => {
        if (!cancelled) setLoading(false)
      })

    return () => {
      cancelled = true
    }
  }, [documentId, schemaError])

  // ─── Unsaved-changes guard (browser unload) ──────────────────────────
  useEffect(() => {
    if (!dirty) return
    function onBeforeUnload(e: BeforeUnloadEvent) {
      e.preventDefault()
      e.returnValue = ''
    }
    window.addEventListener('beforeunload', onBeforeUnload)
    return () => window.removeEventListener('beforeunload', onBeforeUnload)
  }, [dirty])

  // ─── Section mutations ───────────────────────────────────────────────
  const mutateSections = useCallback(
    (fn: (sections: EditorPage['sections']) => EditorPage['sections']) => {
      setPage((prev) => (prev ? { ...prev, sections: fn(prev.sections) } : prev))
      setDirty(true)
      setSaveState('idle')
    },
    [],
  )

  const handleAdd = useCallback(
    (type: SectionTypeId) => {
      const section = createSection(type)
      mutateSections((s) => addSection(s, section))
      setSelectedId(section.id)
      setAddOpen(false)
    },
    [mutateSections],
  )

  const handleReorder = useCallback(
    (orderedIds: string[]) => mutateSections((s) => reorderSections(s, orderedIds)),
    [mutateSections],
  )

  const handleToggleVisibility = useCallback(
    (id: string) => mutateSections((s) => toggleSectionVisibility(s, id)),
    [mutateSections],
  )

  const handleDuplicate = useCallback(
    (id: string) => mutateSections((s) => duplicateSection(s, id)),
    [mutateSections],
  )

  const confirmDelete = useCallback(() => {
    if (!deleteTargetId) return
    mutateSections((s) => removeSection(s, deleteTargetId))
    setSelectedId((cur) => (cur === deleteTargetId ? null : cur))
    setDeleteTargetId(null)
  }, [deleteTargetId, mutateSections])

  const handleContentChange = useCallback(
    (patch: Record<string, unknown>) => {
      if (!selectedId) return
      mutateSections((s) => updateSectionContent(s, selectedId, patch))
    },
    [selectedId, mutateSections],
  )

  const handleSettingsChange = useCallback(
    (patch: Partial<SectionSettings>) => {
      if (!selectedId) return
      mutateSections((s) => updateSectionSettings(s, selectedId, patch))
    },
    [selectedId, mutateSections],
  )

  const handleMetaChange = useCallback(
    (patch: { name?: string; internalLabel?: string }) => {
      if (!selectedId) return
      mutateSections((s) => updateSectionMeta(s, selectedId, patch))
    },
    [selectedId, mutateSections],
  )

  // ─── Page-level settings ─────────────────────────────────────────────
  const handlePageSettings = useCallback((patch: Partial<EditorPage>) => {
    setPage((prev) => (prev ? { ...prev, ...patch } : prev))
    setDirty(true)
    setSaveState('idle')
  }, [])

  // ─── Save / publish ──────────────────────────────────────────────────
  const handleSaveDraft = useCallback(async (): Promise<boolean> => {
    if (!page) return false
    const result = validatePage(page, false)
    if (result.errors.length > 0) {
      setValidation(result)
      toast.error(result.errors[0]!)
      setSettingsOpen(true)
      return false
    }
    setSaveState('saving')
    try {
      const saved = await savePageDraft(page)
      setPage(saved)
      setDirty(false)
      setSaveState('saved')
      setValidation(null)
      toast.success('Draft saved')
      if (!documentId && saved.id) onNavigate(`/pages/${saved.id}/edit`)
      return true
    } catch (err) {
      setSaveState('error')
      toast.error(err instanceof Error ? err.message : 'Failed to save draft')
      return false
    }
  }, [page, documentId, onNavigate])

  const requestPublish = useCallback(() => {
    if (!page) return
    const result = validatePage(page, true)
    setValidation(result.errors.length || result.warnings.length ? result : null)
    if (result.errors.length > 0) {
      toast.error('Resolve the listed issues before publishing')
      return
    }
    setConfirmPublish(true)
  }, [page])

  const doPublish = useCallback(async () => {
    if (!page) return
    setPublishing(true)
    try {
      const published = await publishPage(page)
      setPage(published)
      setDirty(false)
      setSaveState('saved')
      setValidation(null)
      toast.success('Page published')
      if (!documentId && published.id) onNavigate(`/pages/${published.id}/edit`)
    } catch (err) {
      toast.error(err instanceof Error ? err.message : 'Failed to publish page')
    } finally {
      setPublishing(false)
    }
  }, [page, documentId, onNavigate])

  // ─── Navigation with unsaved guard ───────────────────────────────────
  const guardedNavigate = useCallback(
    (path: string) => {
      if (dirty) setLeaveTarget(path)
      else onNavigate(path)
    },
    [dirty, onNavigate],
  )

  const handlePreview = useCallback(() => {
    if (!page?.id) {
      toast.error('Save the page before previewing')
      return
    }
    const path = `/pages/${page.id}/preview`
    // New-tab preview: save first when there are unsaved edits so the preview
    // (which reads the persisted document) reflects what's on screen.
    if (onOpenPreview) {
      onOpenPreview(path, dirty ? handleSaveDraft : undefined)
    } else {
      guardedNavigate(path)
    }
  }, [page, dirty, onOpenPreview, handleSaveDraft, guardedNavigate])

  // "View on site": render the draft through the consumer's real public route
  // via a signed preview token. The blank tab is opened synchronously inside
  // the click (popup-safe), then pointed at the public URL once any unsaved
  // draft is persisted and the token is minted.
  const handleViewOnSite = useCallback(() => {
    if (!page?.id || !page.slug) {
      toast.error('Save the page before viewing it on the site')
      return
    }
    const tab = window.open('about:blank', '_blank')
    if (!tab) {
      toast.error('Enable pop-ups for this site to open the preview in a new tab.')
      return
    }
    void (async () => {
      try {
        if (dirty) {
          const ok = await handleSaveDraft()
          if (!ok) {
            tab.close()
            return
          }
        }
        const { token } = await createPreviewToken('pages', page.id!)
        const target = buildPublicPreviewUrl({
          siteUrl: resolveSiteUrl(config?.seo?.siteUrl),
          urlPrefix: '',
          slug: page.slug,
          token,
        })
        try {
          tab.opener = null
        } catch {
          /* same-origin guard */
        }
        tab.location.replace(target)
      } catch (err) {
        tab.close()
        toast.error(err instanceof Error ? err.message : 'Failed to open site preview')
      }
    })()
  }, [page, dirty, config, handleSaveDraft])

  // ─── Render states ───────────────────────────────────────────────────
  if (loading) {
    return (
      <div className="bg-background flex h-full items-center justify-center">
        <Loader2 className="text-muted-foreground h-6 w-6 animate-spin" aria-hidden />
        <span className="sr-only">Loading page editor…</span>
      </div>
    )
  }

  if (loadError || !page) {
    return (
      <div className="bg-background flex h-full items-center justify-center p-6">
        <div className="border-destructive/30 bg-destructive/5 flex max-w-md items-center gap-3 rounded-lg border p-4">
          <AlertTriangle className="text-destructive shrink-0" size={20} aria-hidden />
          <div className="min-w-0">
            <p className="text-foreground text-sm font-medium">Failed to load page</p>
            <p className="text-muted-foreground mt-1 text-sm">{loadError ?? 'Page not found.'}</p>
            <button
              type="button"
              onClick={() => onNavigate('/pages')}
              className="text-primary mt-2 text-sm font-medium hover:underline"
            >
              Back to Pages
            </button>
          </div>
        </div>
      </div>
    )
  }

  const selected = page.sections.find((s) => s.id === selectedId) ?? null

  return (
    <ErrorBoundary>
      <div className="bg-background flex h-full flex-col overflow-hidden">
        <EditorTopBar
          title={page.title}
          status={page.status}
          dirty={dirty}
          saveState={saveState}
          publishing={publishing}
          canEdit={canEdit}
          canPublish={canPublish && canEdit}
          viewport={viewport}
          onViewport={setViewport}
          onBack={() => guardedNavigate('/pages')}
          onPreview={handlePreview}
          onViewOnSite={page.id ? handleViewOnSite : undefined}
          onOpenSettings={() => setSettingsOpen(true)}
          onSaveDraft={handleSaveDraft}
          onPublish={requestPublish}
        />

        {validation && (
          <ValidationSummary
            errors={validation.errors}
            warnings={validation.warnings}
            onDismiss={() => setValidation(null)}
          />
        )}

        <div className="flex flex-1 overflow-hidden">
          <SectionsPanel
            sections={page.sections}
            selectedId={selectedId}
            canEdit={canEdit}
            onSelect={setSelectedId}
            onReorder={handleReorder}
            onToggleVisibility={handleToggleVisibility}
            onDuplicate={handleDuplicate}
            onRequestDelete={(id) => setDeleteTargetId(id)}
            onAdd={() => setAddOpen(true)}
          />

          <div className="flex-1 overflow-hidden">
            <EditorCanvas
              sections={page.sections}
              selectedId={selectedId}
              viewport={viewport}
              onSelect={setSelectedId}
              onAddSection={() => setAddOpen(true)}
            />
          </div>

          {selected && (
            <SectionInspector
              section={selected}
              canEdit={canEdit}
              onClose={() => setSelectedId(null)}
              onContentChange={handleContentChange}
              onSettingsChange={handleSettingsChange}
              onMetaChange={handleMetaChange}
              onToggleVisibility={() => handleToggleVisibility(selected.id)}
            />
          )}
        </div>
      </div>

      <AddSectionModal open={addOpen} onClose={() => setAddOpen(false)} onAdd={handleAdd} />

      <PageSettingsModal
        open={settingsOpen}
        page={page}
        canEdit={canEdit}
        onClose={() => setSettingsOpen(false)}
        onSave={handlePageSettings}
      />

      <ConfirmDialog
        open={deleteTargetId !== null}
        onClose={() => setDeleteTargetId(null)}
        onConfirm={confirmDelete}
        title="Delete section?"
        description="This removes the section from the page. You can undo by leaving without saving."
        confirmLabel="Delete"
        destructive
      />

      <ConfirmDialog
        open={confirmPublish}
        onClose={() => setConfirmPublish(false)}
        onConfirm={doPublish}
        title="Publish this page?"
        description="The current content will go live and replace the published version."
        confirmLabel="Publish"
      />

      <ConfirmDialog
        open={leaveTarget !== null}
        onClose={() => setLeaveTarget(null)}
        onConfirm={() => {
          const target = leaveTarget
          setLeaveTarget(null)
          setDirty(false)
          if (target) onNavigate(target)
        }}
        title="Discard unsaved changes?"
        description="You have unsaved changes that will be lost if you leave now."
        confirmLabel="Leave"
        cancelLabel="Stay"
        destructive
      />
    </ErrorBoundary>
  )
}
