'use client'

import { useCallback, useEffect, useMemo, useState } from 'react'
import { AlertTriangle, Blocks, Loader2, Lock } from 'lucide-react'
import { toast } from 'sonner'
import { ConfirmDialog } from '../../components/ui/ConfirmDialog.js'
import { useNavigationBlocker } from '../../router/index.js'
import { SchedulePublishDialog } from '../../components/SchedulePublishDialog.js'
import { EditorSeoAside } from '../../components/EditorSeoAside.js'
import { ErrorBoundary } from '../../components/ErrorBoundary.js'
import { VersionHistory } from '../../components/VersionHistory.js'
import {
  addSection,
  createSection,
  duplicateSection,
  emptyPage,
  fetchPageForEditor,
  missingPageFields,
  moveSection,
  publishPage,
  PAGES_COLLECTION,
  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 { hasCanvasRenderer, useAdminSectionRenderers } from './sections/index.js'
import { EditorTopBar, type SaveState } from './EditorTopBar.js'
import { SectionsPanel } from './SectionsPanel.js'
import { EditorCanvas } from './EditorCanvas.js'
import { SitePreviewFrame } from './SitePreviewFrame.js'
import { useSitePreview } from './useSitePreview.js'
import { SectionInspector } from './SectionInspector.js'
import { AddSectionModal } from './AddSectionModal.js'
import { PageSettingsModal } from './PageSettingsModal.js'
import { ValidationSummary } from './ValidationSummary.js'
import { resolveDefaultSitePreview } from '../../lib/editor-preview.js'
import { computeLiveSeoScore } from '../../lib/editor-seo-content.js'

interface PageSectionEditorProps {
  /** undefined when creating a new page (`/pages/new`) */
  documentId?: string
  /**
   * Parent page to preselect on a new page — set when arriving via
   * "Add sub-page" in the Pages tree view (`/pages/new?parent=<id>`).
   * Ignored when `documentId` is present.
   */
  initialParentId?: string | null
  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
  // Default closed: when we can't identify a role, hide the edit/publish
  // affordances rather than exposing them. The server independently enforces
  // access on every write, so this is a UX guard — but it should fail safe.
  // (Matches the Post editor's behavior.)
  if (!role) return { canEdit: false, canPublish: false }
  const editors = ['ADMIN', 'EDITOR', 'AUTHOR']
  const publishers = ['ADMIN', 'EDITOR']
  return { canEdit: editors.includes(role), canPublish: publishers.includes(role) }
}

export function PageSectionEditor({
  documentId,
  initialParentId,
  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 [seoOpen, setSeoOpen] = useState(false)
  const [historyOpen, setHistoryOpen] = useState(false)
  const [deleteTargetId, setDeleteTargetId] = useState<string | null>(null)
  const [confirmPublish, setConfirmPublish] = useState(false)
  const [scheduleOpen, setScheduleOpen] = useState(false)
  const [leaveTarget, setLeaveTarget] = useState<string | null>(null)
  const [validation, setValidation] = useState<ValidationResult | null>(null)

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

  // 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])

  const publishCheck = useMemo(
    () => (page ? validatePage(page, true) : { errors: [''], warnings: [] }),
    [page],
  )
  const publishReady = Boolean(page) && publishCheck.errors.length === 0

  const liveSeoScore = useMemo(() => {
    if (!page) return 0
    return computeLiveSeoScore({
      title: page.title,
      slug: page.slug,
      seoTitle: page.seoTitle,
      seoDescription: page.seoDescription,
      sections: page.sections,
    })
  }, [page])
  const publishDisabledReason = publishCheck.errors[0]

  // ─── 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(initialParentId ?? null))
      setLoading(false)
      // Start a brand-new page the way WordPress does: name it first. The
      // settings modal opens with the title focused and the slug/path
      // deriving live, so the required fields are done before building.
      setSettingsOpen(true)
      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, initialParentId, schemaError])

  // ─── Unsaved-changes guard (client-side navigation + browser unload) ──
  useNavigationBlocker(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])

  // Inline branded preview — iframe of the public site rendering this draft.
  const sitePreview = useSitePreview({
    collection: 'pages',
    documentId: page?.id,
    slug: page?.slug,
    urlPrefix: '',
    siteUrl: config?.seo?.siteUrl,
    dirty,
    saveState,
    saveDraft: handleSaveDraft,
    defaultActive: resolveDefaultSitePreview(config?.admin?.editor, 'pages'),
  })

  const sitePreviewAvailable = Boolean(page?.id && page?.slug)

  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 requestSchedule = useCallback(async () => {
    if (!page) return
    if (!page.id) {
      toast.error('Save the page before scheduling')
      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 scheduling')
      return
    }
    if (dirty) {
      const ok = await handleSaveDraft()
      if (!ok) return
    }
    setScheduleOpen(true)
  }, [page, dirty, handleSaveDraft])

  const handleScheduled = useCallback(
    (next: { status: string; scheduledAt: string | null; scheduledUnpublishAt: string | null }) => {
      setPage((prev) =>
        prev
          ? {
              ...prev,
              status: next.status as EditorPage['status'],
              scheduledAt: next.scheduledAt,
              scheduledUnpublishAt: next.scheduledUnpublishAt,
            }
          : prev,
      )
      setValidation(null)
    },
    [],
  )

  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])

  // After a version restore the server has already written the restored
  // data — refetch so the editor reflects it instead of the stale state.
  const handleRestored = useCallback(() => {
    if (!page?.id) return
    fetchPageForEditor(page.id)
      .then((p) => {
        setPage(p)
        setSelectedId(null)
        setSeoOpen(false)
        setDirty(false)
        setSaveState('idle')
        setValidation(null)
      })
      .catch(() => {
        toast.error('Version restored — refresh the page to see it.')
      })
  }, [page?.id])

  const handleSeoSaved = useCallback(() => {
    if (!page?.id) return
    fetchPageForEditor(page.id)
      .then((p) => setPage(p))
      .catch(() => {
        /* SEO API saved; editor meta will sync on next full load */
      })
  }, [page?.id])

  const handleToggleSeo = useCallback(() => {
    setSeoOpen((open) => {
      if (!open) setSelectedId(null)
      return !open
    })
  }, [])

  const handleSelectSection = useCallback((id: string | null) => {
    setSeoOpen(false)
    setSelectedId(id)
  }, [])

  // ─── 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

  // ADR 0002: a Page-Builder-owned page (it has a `_layout`) compiles its
  // sections from the builder layout on every save, so editing them here would
  // be overwritten. Open this editor read-only and route the user to the
  // builder. The server independently refuses such edits with 409.
  const readOnly = page.builderOwned
  const effectiveCanEdit = canEdit && !readOnly

  // Sections whose type has no registered definition (seed/code-owned, e.g.
  // `opal-*`). They are preserved verbatim on save but can't be edited here.
  const unmanagedTypes = Array.from(
    new Set(page.sections.filter((s) => s.unmanaged).map((s) => s.sectionType)),
  )

  // Section types the canvas renders as placeholders — no built-in body
  // component and no consumer-registered admin renderer. When present the
  // editor surfaces "structure view" messaging and promotes "View on site"
  // as the primary design preview.
  const structuralTypes = Array.from(
    new Set(
      page.sections
        .filter((s) => !hasCanvasRenderer(s.sectionType) && !customRenderers[s.sectionType])
        .map((s) => s.sectionType),
    ),
  )

  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={effectiveCanEdit}
          canPublish={canPublish && effectiveCanEdit}
          publishReady={publishReady}
          publishDisabledReason={publishDisabledReason}
          viewport={viewport}
          onViewport={setViewport}
          onBack={() => guardedNavigate('/pages')}
          onPreview={handlePreview}
          onViewOnSite={page.id ? handleViewOnSite : undefined}
          sitePreview={sitePreview.active}
          onToggleSitePreview={page.id && page.slug ? sitePreview.toggle : undefined}
          onHistory={page.id ? () => setHistoryOpen(true) : undefined}
          onOpenSettings={() => setSettingsOpen(true)}
          onOpenSeo={handleToggleSeo}
          seoOpen={seoOpen}
          seoScore={liveSeoScore}
          onSaveDraft={handleSaveDraft}
          onPublish={requestPublish}
          onSchedule={page.id ? requestSchedule : undefined}
          scheduleReady={Boolean(page.id)}
          scheduledAt={page.scheduledAt}
          scheduledUnpublishAt={page.scheduledUnpublishAt}
          structuralTypes={structuralTypes}
        />

        {readOnly && (
          <div
            role="status"
            className="border-border bg-accent/40 flex items-center gap-3 border-b px-4 py-2.5"
          >
            <Blocks className="text-muted-foreground shrink-0" size={16} aria-hidden />
            <p className="text-muted-foreground flex-1 text-sm">
              This page is built in the{' '}
              <span className="text-foreground font-medium">Page Builder</span>. Its sections are
              generated from the builder layout and are read-only here.
            </p>
            {page.id && (
              <button
                type="button"
                onClick={() => guardedNavigate(`/page-builder/${page.id}`)}
                className="bg-primary text-primary-foreground hover:bg-primary/90 shrink-0 rounded-lg px-3 py-1.5 text-sm font-medium transition-colors"
              >
                Open in Page Builder
              </button>
            )}
          </div>
        )}

        {unmanagedTypes.length > 0 && (
          <div
            role="status"
            className="border-border bg-muted/50 flex items-start gap-3 border-b px-4 py-2.5"
          >
            <Lock className="text-muted-foreground mt-0.5 shrink-0" size={16} aria-hidden />
            <p className="text-muted-foreground flex-1 text-sm">
              This page has{' '}
              <span className="text-foreground font-medium">
                {unmanagedTypes.length} section type{unmanagedTypes.length > 1 ? 's' : ''}
              </span>{' '}
              managed outside the editor ({unmanagedTypes.join(', ')}). They are preserved on save
              but edited in your seed script or code, not here.
            </p>
          </div>
        )}

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

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

          <div className="flex-1 overflow-hidden">
            {sitePreview.active ? (
              <SitePreviewFrame
                src={sitePreview.url}
                viewport={viewport}
                reloadKey={sitePreview.reloadKey}
                title={`Site preview: ${page.title || 'Untitled page'}`}
              />
            ) : (
              <EditorCanvas
                sections={page.sections}
                selectedId={selectedId}
                viewport={viewport}
                onSelect={handleSelectSection}
                onAddSection={() => setAddOpen(true)}
                structuralTypes={structuralTypes}
                sitePreviewAvailable={sitePreviewAvailable && !sitePreview.active}
              />
            )}
          </div>

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

          {seoOpen && (
            <EditorSeoAside
              entityType="page"
              entityId={page.id}
              title={page.title}
              slug={page.slug}
              seoTitle={page.seoTitle}
              seoDescription={page.seoDescription}
              sections={page.sections}
              canEdit={effectiveCanEdit}
              onClose={() => setSeoOpen(false)}
              onDraftChange={handlePageSettings}
              onSaved={handleSeoSaved}
            />
          )}
        </div>
      </div>

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

      {page.id && (
        <VersionHistory
          collectionSlug="pages"
          documentId={page.id}
          open={historyOpen}
          onClose={() => setHistoryOpen(false)}
          onRestore={handleRestored}
        />
      )}

      <PageSettingsModal
        open={settingsOpen}
        page={page}
        canEdit={effectiveCanEdit}
        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"
      />

      {page.id && (
        <SchedulePublishDialog
          collectionSlug={PAGES_COLLECTION}
          documentId={page.id}
          scheduledAt={page.scheduledAt}
          scheduledUnpublishAt={page.scheduledUnpublishAt}
          open={scheduleOpen}
          onClose={() => setScheduleOpen(false)}
          onScheduled={handleScheduled}
        />
      )}

      <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>
  )
}
