"use client"

import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import type { ReactNode } from "react"
import { useEditor, EditorContent } from "@tiptap/react"
import { HocuspocusProvider } from "@hocuspocus/provider"
import * as Y from "yjs"
import { toast } from "sonner"
import { DocsChrome } from "@/components/drive/richtext/docs-chrome"
import { DocsEditorWorkspace } from "@/components/drive/richtext/docs-editor-workspace"
import { DocsLoadingSplash } from "@/components/drive/richtext/docs-loading-splash"
import { DocsStatusBar } from "@/components/drive/richtext/docs-page-view"
import { DocsToolbar } from "@/components/drive/richtext/docs-toolbar"
import { buildRichTextExtensions, RICHTEXT_EDITOR_CLASS } from "@/lib/drive/richtext-extensions"
import type { RichTextSaveStatus, RichTextSessionResponse } from "@/lib/drive/richtext-types"
import type { DriveShare, DriveFileInfo } from "@/lib/api/types"
import { useDocsEditMenu } from "@/lib/drive/use-docs-edit-menu"
import { useDocsFileMenu } from "@/lib/drive/use-docs-file-menu"
import { useDocsFormatMenu } from "@/lib/drive/use-docs-format-menu"
import { useDocsInsertMenu } from "@/lib/drive/use-docs-insert-menu"
import type { DocsViewMenuActions, DocsViewMenuState } from "@/components/drive/richtext/docs-view-menu"
import { useDocsViewSettings } from "@/lib/drive/docs-view-settings"
import { useDocsKeyboardShortcutsStore } from "@/lib/stores/docs-keyboard-shortcuts-store"
import { useCollabPresence } from "@/lib/drive/use-collab-presence"
import { apiClient } from "@/lib/api/client"
import { driveDownloadApiPath } from "@/lib/api/drive-download"
import { buildRegionPatch } from "@/lib/drive/docs-header-footer-layout"
import {
  buildPageSetupForFormat,
  buildPageSetupFromDraft,
  resolveDocumentPageLayout,
  type DocPageSetup,
} from "@/lib/drive/doc-page-setup"
import { readUserPageSetupDefaults } from "@/lib/drive/docs-page-defaults"
import { ensureMinimalTipTapDoc, isEmptyTipTapDoc } from "@/lib/drive/richtext-content"
import { importFileToTipTap } from "@/lib/drive/richtext-import"
import { migrateBase64ImagesInContent } from "@/lib/drive/docs-graphic-assets"
import { isUltidocPath } from "@/lib/drive/richtext-formats"
import { defaultDocumentParagraphStyles, type DocParagraphStylesCatalog } from "@/lib/drive/docs-paragraph-styles"
import { DocsParagraphStylesProvider } from "@/lib/drive/docs-paragraph-styles-context"
import { useDocsParagraphStyles } from "@/lib/drive/use-docs-paragraph-styles"
import {
  resolveRichTextDocumentLoadingPhase,
  type DocsLoadingPhase,
} from "@/lib/drive/docs-loading-phase"
import { buildDocsExportSnapshot } from "@/lib/drive/docs-export-snapshot"
import { printDocsDocument } from "@/lib/drive/docs-print"
import { DocsAiPanel, DocsAiPanelToggle } from "@/components/ai/docs-ai-panel"
import { useDocsAiPanelStore } from "@/lib/ai/use-docs-ai-panel"
import { cn } from "@/lib/utils"

const SAVE_DEBOUNCE_MS = 2000
/** Align with Hocuspocus store debounce + buffer */
const COLLAB_SAVE_IDLE_MS = 2000
const PAGE_SETUP_DEBOUNCE_MS = 1500

export type RichTextDocsChromeProps = {
  title: string
  onRename?: (next: string) => Promise<void>
  renameDisabled?: boolean
  backHref?: string
  backLabel?: string
  showBack?: boolean
  shares?: DriveShare[]
  onShareClick?: () => void
  showShare?: boolean
  showAccount?: boolean
  trailing?: ReactNode
  moveFile?: DriveFileInfo
  onFileMoved?: (newPath: string) => void
  file?: DriveFileInfo
  onRenameRequest?: () => void
  renameSignal?: number
}

export function RichTextDocumentEditor({
  session,
  mode,
  userName,
  userColor,
  onSaveStatus,
  fetchSourceBytes,
  importApi,
  chrome,
  deferSplash = false,
  onLoadingChange,
}: {
  session: RichTextSessionResponse
  mode: "edit" | "view"
  userName: string
  userColor: string
  onSaveStatus?: (status: RichTextSaveStatus) => void
  fetchSourceBytes?: (path: string) => Promise<ArrayBuffer>
  importApi?: (body: {
    source_path: string
    content: Record<string, unknown>
    pageSetup?: DocPageSetup | null
  }) => Promise<void>
  chrome?: RichTextDocsChromeProps
  deferSplash?: boolean
  onLoadingChange?: (loading: boolean, phase: DocsLoadingPhase) => void
}) {
  const editable = mode === "edit"
  const collaboration = session.collaboration && Boolean(session.wsUrl && session.token)
  const ydocRef = useRef<Y.Doc | null>(null)
  if (collaboration && !ydocRef.current) {
    ydocRef.current = new Y.Doc()
  }
  const ydoc = collaboration ? ydocRef.current : null

  const [provider, setProvider] = useState<HocuspocusProvider | null>(null)
  const [collabSynced, setCollabSynced] = useState(false)
  const [collabError, setCollabError] = useState<string | null>(null)
  const [importDone, setImportDone] = useState(!session.importRequired)
  const [contentImportPending, setContentImportPending] = useState(session.importRequired)
  const [importedContent, setImportedContent] = useState<Record<string, unknown> | null>(null)
  const [documentParagraphStyles, setDocumentParagraphStyles] = useState<DocParagraphStylesCatalog>(
    () => session.paragraphStyles ?? defaultDocumentParagraphStyles()
  )
  const [documentPageSetup, setDocumentPageSetup] = useState<DocPageSetup | null>(
    session.pageSetup ?? null
  )
  const [saveStatus, setSaveStatus] = useState<RichTextSaveStatus>("idle")
  const [pageCount, setPageCount] = useState(1)
  const [currentPage, setCurrentPage] = useState(1)
  const [regionEditor, setRegionEditor] = useState<import("@tiptap/react").Editor | null>(null)
  const saveTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
  const saveIdleTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
  const pageSetupTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
  const documentPageSetupRef = useRef<DocPageSetup | null>(session.pageSetup ?? null)
  const saveStatusRef = useRef<RichTextSaveStatus>("idle")
  const reloadAfterReimportRef = useRef(false)
  const purgeReimportingRef = useRef(false)
  const providerRef = useRef<HocuspocusProvider | null>(null)

  const {
    settings,
    setPageFormatId,
    setZoom,
    toggleSpellcheck,
    toggleChromeCollapsed,
    setEditorMode,
    setCommentsDisplay,
    toggleOutlineSidebarExpanded,
    toggleShowLayout,
    toggleShowRuler,
    toggleShowEquationToolbar,
    toggleShowNonPrintableChars,
  } = useDocsViewSettings()
  const shellRef = useRef<HTMLDivElement>(null)
  const getPageStackElementRef = useRef<() => HTMLElement | null>(() => null)
  const presenceUsers = useCollabPresence(provider, { name: userName, color: userColor })
  const pageLayout = useMemo(
    () => resolveDocumentPageLayout(documentPageSetup, settings.pageFormatId),
    [documentPageSetup, settings.pageFormatId]
  )
  const activePageFormatId = pageLayout.format.id

  const reportSaveStatus = useCallback(
    (status: RichTextSaveStatus) => {
      saveStatusRef.current = status
      setSaveStatus(status)
      onSaveStatus?.(status)
    },
    [onSaveStatus]
  )

  const persistPageSetup = useCallback(
    async (setup: DocPageSetup) => {
      if (!editable) return
      try {
        const body = JSON.stringify({ pageSetup: setup })
        if (session.saveUrl) {
          const res = await fetch(session.saveUrl, {
            method: "PUT",
            headers: { "Content-Type": "application/json" },
            body,
          })
          if (!res.ok) throw new Error("save failed")
        } else {
          await apiClient.put("/richtext/save", {
            path: session.canonicalPath,
            pageSetup: setup,
          })
        }
        reportSaveStatus("saved")
      } catch {
        reportSaveStatus("error")
      }
    },
    [editable, reportSaveStatus, session.canonicalPath, session.saveUrl]
  )

  const schedulePageSetupPatch = useCallback(
    (patch: Partial<DocPageSetup>, options?: { immediate?: boolean }) => {
      const base =
        documentPageSetupRef.current ??
        buildPageSetupForFormat(settings.pageFormatId, null)
      const next = { ...base, ...patch }
      documentPageSetupRef.current = next
      setDocumentPageSetup(next)
      if (next.formatId) setPageFormatId(next.formatId)

      const flush = () => {
        const setup = documentPageSetupRef.current
        if (!setup) return
        void persistPageSetup(setup)
      }

      if (options?.immediate) {
        if (pageSetupTimer.current) clearTimeout(pageSetupTimer.current)
        if (saveStatusRef.current === "idle") {
          reportSaveStatus("saving")
        }
        flush()
        return
      }

      if (saveStatusRef.current === "idle") {
        reportSaveStatus("saving")
      }
      if (pageSetupTimer.current) clearTimeout(pageSetupTimer.current)
      pageSetupTimer.current = setTimeout(flush, PAGE_SETUP_DEBOUNCE_MS)
    },
    [persistPageSetup, reportSaveStatus, settings.pageFormatId, setPageFormatId]
  )

  const handlePageFormatChange = useCallback(
    (formatId: typeof settings.pageFormatId) => {
      schedulePageSetupPatch(buildPageSetupForFormat(formatId, documentPageSetupRef.current), {
        immediate: true,
      })
    },
    [schedulePageSetupPatch, settings.pageFormatId]
  )

  const handleRegionContentChange = useCallback(
    (
      region: "header" | "footer",
      content: Record<string, unknown>,
      meta: { pageIndex: number; contentHeightPx: number },
      options?: { immediate?: boolean }
    ) => {
      const base =
        documentPageSetupRef.current ?? buildPageSetupForFormat(settings.pageFormatId, null)
      schedulePageSetupPatch(
        buildRegionPatch(base, region, meta.pageIndex, content, meta.contentHeightPx),
        options
      )
    },
    [schedulePageSetupPatch, settings.pageFormatId]
  )

  const handlePageSetupPatch = useCallback(
    (patch: Partial<DocPageSetup>, options?: { immediate?: boolean }) => {
      schedulePageSetupPatch(patch, options)
    },
    [schedulePageSetupPatch]
  )

  useEffect(() => {
    documentPageSetupRef.current = documentPageSetup
  }, [documentPageSetup])

  useEffect(() => {
    if (session.pageSetup) setDocumentPageSetup(session.pageSetup)
  }, [session.pageSetup])

  useEffect(() => {
    if (session.pageSetup) return
    setDocumentPageSetup((current) => {
      if (current) return current
      const setup = buildPageSetupFromDraft(
        readUserPageSetupDefaults(settings.pageFormatId),
        null
      )
      if (setup.formatId) setPageFormatId(setup.formatId)
      return setup
    })
  }, [session.pageSetup, settings.pageFormatId, setPageFormatId])

  const handlePageCountChange = useCallback((count: number) => {
    setPageCount(count)
    setCurrentPage((page) => Math.min(page, count))
  }, [])

  const handleCurrentPageChange = useCallback((page: number) => {
    setCurrentPage(page)
  }, [])

  const handlePurgeSidecarAndReimport = useCallback(async () => {
    if (!editable) return
    if (!session.sourcePath) {
      toast.error("Chemin source introuvable — ouvrez le document depuis le fichier DOCX")
      return
    }
    if (isUltidocPath(session.sourcePath)) {
      toast.error("Aucun fichier source à réimporter")
      return
    }
    if (
      !window.confirm(
        "Supprimer le sidecar (.ultidoc.json) et réimporter le document source ? Cette action est temporaire (dev)."
      )
    ) {
      return
    }

    reportSaveStatus("saving")
    purgeReimportingRef.current = true
    reloadAfterReimportRef.current = true

    try {
      providerRef.current?.destroy()
      providerRef.current = null
      setProvider(null)
      setCollabSynced(false)

      if (ydocRef.current) {
        ydocRef.current.destroy()
        ydocRef.current = collaboration ? new Y.Doc() : null
      }

      await apiClient.delete(`/drive/files${session.canonicalPath}`)
      setDocumentPageSetup(null)
      setImportedContent(null)
      setImportDone(false)
      setContentImportPending(true)
      toast.success("Sidecar purgé — réimport en cours…")
    } catch {
      purgeReimportingRef.current = false
      reloadAfterReimportRef.current = false
      reportSaveStatus("error")
      toast.error("Impossible de purger le sidecar")
    }
  }, [collaboration, editable, reportSaveStatus, session.canonicalPath, session.sourcePath])

  const markCollabDirty = useCallback(() => {
    if (saveStatusRef.current !== "saving") {
      reportSaveStatus("saving")
    }
    if (saveIdleTimer.current) clearTimeout(saveIdleTimer.current)
    saveIdleTimer.current = setTimeout(() => {
      reportSaveStatus("saved")
    }, COLLAB_SAVE_IDLE_MS)
  }, [reportSaveStatus])

  useEffect(() => {
    return () => {
      if (saveTimer.current) clearTimeout(saveTimer.current)
      if (saveIdleTimer.current) clearTimeout(saveIdleTimer.current)
      if (pageSetupTimer.current) clearTimeout(pageSetupTimer.current)
    }
  }, [])

  useEffect(() => {
    setContentImportPending(session.importRequired)
    setImportDone(!session.importRequired)
  }, [session.importRequired, session.canonicalPath])

  useEffect(() => {
    if (session.importRequired || !session.sourcePath) return
    let cancelled = false
    void (async () => {
      try {
        let parsed: { content?: Record<string, unknown> }
        if (session.documentUrl) {
          const res = await fetch(session.documentUrl)
          if (!res.ok) return
          parsed = JSON.parse(await res.text()) as { content?: Record<string, unknown> }
        } else {
          const blob = await apiClient.getBlob(driveDownloadApiPath(session.canonicalPath))
          parsed = JSON.parse(await blob.text()) as { content?: Record<string, unknown> }
        }
        if (cancelled || !isEmptyTipTapDoc(parsed.content)) return
        setContentImportPending(true)
        setImportDone(false)
      } catch {
        /* keep current import flags */
      }
    })()
    return () => {
      cancelled = true
    }
  }, [session.canonicalPath, session.documentUrl, session.importRequired, session.sourcePath])

  useEffect(() => {
    if (!contentImportPending || importDone) return
    let cancelled = false
    void (async () => {
      reportSaveStatus("saving")
      try {
        const source = session.sourcePath
        if (!source) {
          throw new Error("Chemin source manquant pour le réimport")
        }
        const buf = fetchSourceBytes
          ? await fetchSourceBytes(source)
          : await (await apiClient.getBlob(driveDownloadApiPath(source))).arrayBuffer()
        const imported = await importFileToTipTap(source.split("/").pop() ?? "file.docx", buf)
        if (cancelled) return
        const content = ensureMinimalTipTapDoc(imported.content as Record<string, unknown>)
        const payload = {
          source_path: source,
          content,
          pageSetup: imported.pageSetup ?? undefined,
        }
        if (importApi) {
          await importApi(payload)
        } else {
          await apiClient.post("/richtext/import", payload)
        }
        if (!cancelled) {
          if (imported.pageSetup) setDocumentPageSetup(imported.pageSetup)
          if (reloadAfterReimportRef.current) {
            reloadAfterReimportRef.current = false
            window.location.reload()
            return
          }
          setImportedContent(content)
          setContentImportPending(false)
          setImportDone(true)
          purgeReimportingRef.current = false
          reportSaveStatus("saved")
        }
      } catch (error) {
        if (!cancelled) {
          purgeReimportingRef.current = false
          reloadAfterReimportRef.current = false
          reportSaveStatus("error")
          toast.error(error instanceof Error ? error.message : "Réimport échoué")
        }
      }
    })()
    return () => {
      cancelled = true
    }
  }, [
    contentImportPending,
    importDone,
    session.sourcePath,
    fetchSourceBytes,
    importApi,
    reportSaveStatus,
  ])

  useEffect(() => {
    if (purgeReimportingRef.current) return
    if (!collaboration || !ydoc || !importDone) return

    setCollabSynced(false)
    setCollabError(null)

    const p = new HocuspocusProvider({
      url: session.wsUrl,
      name: session.roomId,
      token: session.token,
      document: ydoc,
      sessionAwareness: false,
      onSynced: () => setCollabSynced(true),
      onAuthenticationFailed: ({ reason }) => {
        setCollabError(reason ?? "Authentification collaboration refusée")
        setCollabSynced(false)
      },
    })
    providerRef.current = p
    setProvider(p)

    return () => {
      p.destroy()
      providerRef.current = null
      setProvider(null)
      setCollabSynced(false)
    }
  }, [collaboration, importDone, session.roomId, session.token, session.wsUrl, ydoc])

  const persistDocument = useCallback(
    async (json: Record<string, unknown>) => {
      let content = json
      try {
        content = await migrateBase64ImagesInContent(json, async ({ dataUrl }) => {
          const res = await apiClient.post<{ assetId: string; url: string }>(
            "/richtext/assets",
            { path: session.canonicalPath, dataUrl }
          )
          return { assetId: res.assetId, url: res.url }
        })
      } catch {
        /* keep base64 fallback */
      }
      const doc = { schemaVersion: 1, editor: "tiptap", content }
      const body = JSON.stringify(doc)
      const savePromise = session.saveUrl
        ? fetch(session.saveUrl, {
            method: "PUT",
            headers: { "Content-Type": "application/json" },
            body,
          }).then((res) => {
            if (!res.ok) throw new Error("save failed")
          })
        : apiClient.put("/richtext/save", { path: session.canonicalPath, document: content })
      await savePromise
    },
    [session.canonicalPath, session.saveUrl]
  )

  const scheduleSave = useCallback(
    (json: Record<string, unknown>, options?: { immediate?: boolean }) => {
      if (!editable || collaboration) return
      if (saveTimer.current) clearTimeout(saveTimer.current)
      if (saveStatusRef.current !== "saving") {
        reportSaveStatus("saving")
      }
      const runSave = () => {
        void persistDocument(json)
          .then(() => reportSaveStatus("saved"))
          .catch(() => reportSaveStatus("error"))
      }
      if (options?.immediate) {
        runSave()
        return
      }
      saveTimer.current = setTimeout(runSave, SAVE_DEBOUNCE_MS)
    },
    [collaboration, editable, persistDocument, reportSaveStatus]
  )

  const collabReady = !collaboration || (Boolean(provider) && collabSynced)
  const editorEnabled = importDone && collabReady

  const extensions = useMemo(
    () =>
      buildRichTextExtensions({
        collaboration: collaboration && ydoc ? { document: ydoc } : undefined,
        collaborationCaret:
          collaboration && provider
            ? { provider, user: { name: userName, color: userColor } }
            : undefined,
        editable,
      }),
    [collaboration, ydoc, provider, userName, userColor, editable]
  )

  const editor = useEditor(
    {
      immediatelyRender: false,
      editable,
      extensions,
      editorProps: {
        attributes: {
          class: RICHTEXT_EDITOR_CLASS,
        },
      },
      onUpdate: ({ editor: ed }) => {
        if (collaboration) {
          markCollabDirty()
          return
        }
        scheduleSave(ed.getJSON() as Record<string, unknown>)
      },
    },
    [editorEnabled, extensions, collaboration, markCollabDirty, scheduleSave]
  )

  useEffect(() => {
    if (!editor || editor.isDestroyed) return
    const flushAfterDrawSave = () => {
      if (!editable) return
      const json = editor.getJSON() as Record<string, unknown>
      if (collaboration) {
        if (saveStatusRef.current !== "saving") {
          reportSaveStatus("saving")
        }
        void persistDocument(json)
          .then(() => reportSaveStatus("saved"))
          .catch(() => reportSaveStatus("error"))
        return
      }
      scheduleSave(json, { immediate: true })
    }
    window.addEventListener("ultidocs:graphic-draw-saved", flushAfterDrawSave)
    return () => window.removeEventListener("ultidocs:graphic-draw-saved", flushAfterDrawSave)
  }, [collaboration, editable, editor, persistDocument, reportSaveStatus, scheduleSave])

  useEffect(() => {
    if (!editor || editor.isDestroyed) return

    const syncSpellcheck = () => {
      if (editor.isDestroyed || !editor.isInitialized) return
      const dom = editor.view.dom
      dom.spellcheck = settings.spellcheck
      if (settings.spellcheck) {
        dom.setAttribute("spellcheck", "true")
        dom.removeAttribute("autocorrect")
        dom.removeAttribute("autocapitalize")
      } else {
        dom.setAttribute("spellcheck", "false")
        dom.setAttribute("autocorrect", "off")
        dom.setAttribute("autocapitalize", "off")
      }
    }

    syncSpellcheck()
    editor.on("create", syncSpellcheck)
    return () => {
      editor.off("create", syncSpellcheck)
    }
  }, [editor, settings.spellcheck])

  const editMenu = useDocsEditMenu({
    editor,
    disabled: !editable,
  })

  const insertMenu = useDocsInsertMenu({
    editor,
    disabled: !editable,
    pageSetup: documentPageSetup,
    onPageSetupPatch: handlePageSetupPatch,
  })

  const paragraphStyles = useDocsParagraphStyles({
    editor,
    initialDocumentStyles: documentParagraphStyles,
    editable,
    canonicalPath: session.canonicalPath,
    saveUrl: session.saveUrl,
  })

  useEffect(() => {
    setDocumentParagraphStyles(paragraphStyles.state.documentStyles)
  }, [paragraphStyles.state.documentStyles])

  useEffect(() => {
    if (session.paragraphStyles) setDocumentParagraphStyles(session.paragraphStyles)
  }, [session.paragraphStyles])

  const paragraphStylesContextValue = useMemo(
    () => ({
      state: paragraphStyles.state,
      applyStyle: paragraphStyles.applyStyle,
      updateStyleFromSelection: paragraphStyles.updateStyleFromSelection,
      createUserStyle: paragraphStyles.createUserStyle,
      updateDocumentStyle: paragraphStyles.updateDocumentStyle,
    }),
    [paragraphStyles]
  )

  const getExportSnapshot = useCallback(() => {
    if (!editor || !chrome?.file) return null
    return buildDocsExportSnapshot({
      editor,
      sourceName: chrome.file.name,
      title: chrome.title,
      pageSetup: documentPageSetup,
      fallbackFormatId: settings.pageFormatId,
      paragraphStyles: paragraphStyles.state.mergedCatalog,
      pageCount,
      getPageStackElement: () => getPageStackElementRef.current(),
    })
  }, [
    chrome?.file,
    chrome?.title,
    documentPageSetup,
    editor,
    pageCount,
    paragraphStyles.state.mergedCatalog,
    settings.pageFormatId,
  ])

  const handlePrintDocument = useCallback(async () => {
    const snapshot = getExportSnapshot()
    if (!snapshot) {
      window.print()
      return
    }
    try {
      await printDocsDocument(snapshot)
    } catch {
      toast.error("Impossible d'imprimer le document")
    }
  }, [getExportSnapshot])

  const fileMenu = useDocsFileMenu({
    file: chrome?.file,
    editor,
    pageSetup: documentPageSetup,
    fallbackFormatId: settings.pageFormatId,
    getExportSnapshot,
    onPageSetupApply: (setup) => {
      documentPageSetupRef.current = setup
      schedulePageSetupPatch(setup, { immediate: true })
    },
    onPurgeSidecarAndReimport: () => void handlePurgeSidecarAndReimport(),
    onShareClick: chrome?.onShareClick,
    onRenameRequest: chrome?.onRenameRequest,
    onFileMoved: chrome?.onFileMoved,
    disabled: !editable,
  })

  const formatMenu = useDocsFormatMenu({
    editor,
    disabled: !editable,
    onPageSetup: fileMenu.actions.onPageSetup,
  })

  const handleFullscreen = useCallback(() => {
    const el = shellRef.current
    if (!el) return
    if (document.fullscreenElement) {
      void document.exitFullscreen()
      return
    }
    void el.requestFullscreen?.()
  }, [])

  const viewMenuState = useMemo<DocsViewMenuState>(
    () => ({
      editorMode: settings.editorMode,
      commentsDisplay: settings.commentsDisplay,
      showLayout: settings.showLayout,
      showRuler: settings.showRuler,
      showEquationToolbar: settings.showEquationToolbar,
      showNonPrintableChars: settings.showNonPrintableChars,
    }),
    [settings]
  )

  const viewMenuActions = useMemo<DocsViewMenuActions>(
    () => ({
      onEditorModeChange: setEditorMode,
      onCommentsDisplayChange: setCommentsDisplay,
      onToggleOutlineSidebar: toggleOutlineSidebarExpanded,
      onToggleShowLayout: toggleShowLayout,
      onToggleShowRuler: toggleShowRuler,
      onToggleShowEquationToolbar: toggleShowEquationToolbar,
      onToggleShowNonPrintableChars: toggleShowNonPrintableChars,
      onFullscreen: handleFullscreen,
    }),
    [
      handleFullscreen,
      setCommentsDisplay,
      setEditorMode,
      toggleOutlineSidebarExpanded,
      toggleShowEquationToolbar,
      toggleShowLayout,
      toggleShowNonPrintableChars,
      toggleShowRuler,
    ]
  )

  const closeDocsAiPanel = useDocsAiPanelStore((s) => s.closePanel)

  useEffect(() => {
    closeDocsAiPanel()
  }, [session.canonicalPath, closeDocsAiPanel])

  const chromeProps = chrome
    ? {
        ...chrome,
        trailing: (
          <>
            {chrome.trailing}
            <DocsAiPanelToggle />
          </>
        ),
        fileMenuActions: fileMenu.actions,
        fileMenuDialogs: fileMenu.dialogs,
        fileMenuDisabled: fileMenu.disabled,
        editMenuActions: editMenu.actions,
        editMenuState: editMenu.state,
        editMenuDisabled: editMenu.disabled,
        insertMenuActions: insertMenu.actions,
        insertMenuDialogs: insertMenu.dialogs,
        insertMenuDisabled: insertMenu.disabled,
        insertMenuPageElementsEnabled: insertMenu.pageElementsEnabled,
        formatMenuActions: formatMenu.actions,
        formatMenuState: formatMenu.state,
        formatMenuDisabled: formatMenu.disabled,
        viewMenuActions,
        viewMenuState,
        viewMenuDisabled: false,
      }
    : undefined

  useEffect(() => {
    if (!editor || editor.isDestroyed) return
    const canEdit = editable && settings.editorMode !== "view"
    editor.setEditable(canEdit)
  }, [editor, editable, settings.editorMode])

  useEffect(() => {
    const onKeyDown = (event: KeyboardEvent) => {
      const id = useDocsKeyboardShortcutsStore.getState().matchEvent(
        event,
        (definition) =>
          definition.scope === "document" && definition.handler === "custom"
      )
      if (id === "view.showNonPrintable") {
        event.preventDefault()
        toggleShowNonPrintableChars()
      }
    }
    window.addEventListener("keydown", onKeyDown)
    return () => window.removeEventListener("keydown", onKeyDown)
  }, [toggleShowNonPrintableChars])

  useEffect(() => {
    if (!editor || collaboration || !importDone) return

    if (importedContent) {
      editor.commands.setContent(importedContent)
      setImportedContent(null)
      return
    }

    if (contentImportPending) return

    let cancelled = false
    void (async () => {
      try {
        let parsed: { content?: Record<string, unknown>; pageSetup?: DocPageSetup }
        if (session.documentUrl) {
          const res = await fetch(session.documentUrl)
          if (!res.ok) throw new Error("load failed")
          parsed = JSON.parse(await res.text()) as { content?: Record<string, unknown>; pageSetup?: DocPageSetup }
        } else {
          const blob = await apiClient.getBlob(driveDownloadApiPath(session.canonicalPath))
          parsed = JSON.parse(await blob.text()) as { content?: Record<string, unknown>; pageSetup?: DocPageSetup }
        }
        if (!cancelled && parsed.pageSetup) {
          setDocumentPageSetup(parsed.pageSetup)
        } else if (!cancelled) {
          setDocumentPageSetup(
            (current) =>
              current ??
              buildPageSetupFromDraft(readUserPageSetupDefaults(settings.pageFormatId), null)
          )
        }
        if (!cancelled && parsed.content && !isEmptyTipTapDoc(parsed.content)) {
          editor.commands.setContent(parsed.content)
        } else if (!cancelled && session.sourcePath) {
          setContentImportPending(true)
          setImportDone(false)
        }
      } catch {
        /* blank */
      }
    })()
    return () => {
      cancelled = true
    }
  }, [
    editor,
    collaboration,
    importDone,
    importedContent,
    contentImportPending,
    session.canonicalPath,
    session.documentUrl,
    session.sourcePath,
    settings.pageFormatId,
  ])

  const documentLoading = !editorEnabled || !editor
  const loadingPhase = resolveRichTextDocumentLoadingPhase({
    contentImportPending,
    importDone,
    importRequired: session.importRequired,
    collaboration: Boolean(collaboration),
    collabSynced,
  })

  useEffect(() => {
    if (!deferSplash) return
    onLoadingChange?.(documentLoading, loadingPhase)
  }, [deferSplash, documentLoading, loadingPhase, onLoadingChange])

  if (collabError) {
    return (
      <div className="flex h-full items-center justify-center p-6 text-sm text-destructive">
        Collaboration indisponible : {collabError}
      </div>
    )
  }

  if (!deferSplash && documentLoading) {
    return (
      <DocsLoadingSplash
        phase={loadingPhase}
        title={chromeProps?.title}
      />
    )
  }

  if (deferSplash && documentLoading) {
    return null
  }

  if (!editor) {
    return null
  }

  return (
    <DocsParagraphStylesProvider value={paragraphStylesContextValue}>
    <div
      ref={shellRef}
      className={cn(
        "flex min-h-0 flex-1 flex-col bg-white dark:bg-background",
        settings.outlineSidebarExpanded && "docs-outline-sidebar-expanded"
      )}
    >
      {chromeProps && !settings.chromeCollapsed ? (
        <DocsChrome
          {...chromeProps}
          saveStatus={saveStatus}
          presenceUsers={presenceUsers}
        />
      ) : null}
      {chrome ? (
        <div className="flex min-h-0 min-w-0 flex-1 flex-row">
          <DocsEditorWorkspace
            editor={editor}
            pageLayout={pageLayout}
            zoom={settings.zoom}
            editable={editable && settings.editorMode !== "view"}
            showLayout={settings.showLayout}
            showRuler={settings.showRuler}
            showNonPrintableChars={settings.showNonPrintableChars}
            editorMode={settings.editorMode}
            outlineExpanded={settings.outlineSidebarExpanded}
            onToggleOutline={toggleOutlineSidebarExpanded}
            onPageCountChange={handlePageCountChange}
            onCurrentPageChange={handleCurrentPageChange}
            onRegionContentChange={handleRegionContentChange}
            onPageSetupChange={handlePageSetupPatch}
            onRegionEditorChange={setRegionEditor}
            onPageStackReady={(getPageStack) => {
              getPageStackElementRef.current = getPageStack
            }}
            toolbarShellClassName={
              settings.chromeCollapsed ? "docs-toolbar-shell--collapsed" : undefined
            }
            toolbar={
              editable ? (
                <DocsToolbar
                  editor={regionEditor ?? editor}
                  zoom={settings.zoom}
                  onZoomChange={setZoom}
                  spellcheck={settings.spellcheck}
                  onToggleSpellcheck={toggleSpellcheck}
                  showChromeToggle={Boolean(chrome)}
                  chromeCollapsed={settings.chromeCollapsed}
                  onToggleChromeCollapsed={toggleChromeCollapsed}
                  onPrint={() => void handlePrintDocument()}
                  embedded
                />
              ) : null
            }
          />
          <DocsAiPanel
            editor={editor}
            documentPath={session.canonicalPath}
            documentTitle={chromeProps?.title ?? "Document"}
            sourcePath={session.sourcePath}
            editable={editable && settings.editorMode !== "view"}
          />
        </div>
      ) : (
        <div className="min-h-0 flex-1 overflow-auto">
          <EditorContent editor={editor} className="h-full" />
        </div>
      )}
      {chrome ? (
        <DocsStatusBar
          pageLayout={pageLayout}
          pageCount={pageCount}
          currentPage={currentPage}
        />
      ) : null}
    </div>
    </DocsParagraphStylesProvider>
  )
}
