"use client"

import "@/styles/excalidraw.css"
import dynamic from "next/dynamic"
import { useCallback, useEffect, useMemo, useState } from "react"
import { HocuspocusProvider } from "@hocuspocus/provider"
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types"
import type { ExcalidrawElement } from "@excalidraw/excalidraw/element/types"
import type { AppState, BinaryFiles } from "@excalidraw/excalidraw/types"
import { ExcalidrawBinding, yjsToExcalidraw } from "@mizuka-wu/y-excalidraw"
import * as Y from "yjs"
import { apiClient } from "@/lib/api/client"
import { driveDownloadApiPath } from "@/lib/api/drive-download"
import { UltidrawChrome } from "@/components/drive/ultidraw-chrome"
import { DocsLoadingSplash } from "@/components/drive/richtext/docs-loading-splash"
import {
  resolveUltidrawDocumentLoadingPhase,
  type DocsLoadingPhase,
} from "@/lib/drive/docs-loading-phase"
import { seedYExcalidrawElements } from "@/lib/drive/ultidraw-seed"
import type { UltidrawSessionResponse, UltidrawSaveStatus } from "@/lib/drive/ultidraw-types"
import { useCollabPresence } from "@/lib/drive/use-collab-presence"

type ParsedDrawFile = {
  elements: ExcalidrawElement[]
  appState: Partial<AppState>
  files: BinaryFiles
}

function parseDrawFile(raw: string): ParsedDrawFile {
  const data = JSON.parse(raw) as {
    elements?: ExcalidrawElement[]
    appState?: Partial<AppState>
    files?: BinaryFiles
  }
  return {
    elements: data.elements ?? [],
    appState: data.appState ?? {},
    files: data.files ?? {},
  }
}

function seedYdocIfEmpty(ydoc: Y.Doc, parsed: ParsedDrawFile): void {
  const yElements = ydoc.getArray<Y.Map<unknown>>("elements")
  if (yElements.length > 0) return
  if (parsed.elements.length === 0) return
  seedYExcalidrawElements(ydoc, parsed.elements, parsed.files)
}

const ExcalidrawCanvas = dynamic(
  async () => {
    const { Excalidraw } = await import("@excalidraw/excalidraw")
    return Excalidraw
  },
  {
    ssr: false,
    loading: () => (
      <div className="flex h-full min-h-[360px] items-center justify-center text-sm text-muted-foreground">
        Chargement de l&apos;éditeur…
      </div>
    ),
  }
)

export type UltidrawChromeProps = {
  title: string
  onRename?: (next: string) => Promise<void>
  renameDisabled?: boolean
  backHref?: string
  backLabel?: string
  showBack?: boolean
  shares?: import("@/lib/api/types").DriveShare[]
  onShareClick?: () => void
  showShare?: boolean
  showAccount?: boolean
}

export function UltidrawDocumentEditor({
  session,
  mode,
  userName,
  userColor,
  chrome,
  fetchDocument,
  deferSplash = false,
  onLoadingChange,
}: {
  session: UltidrawSessionResponse
  mode: "edit" | "view"
  userName: string
  userColor: string
  chrome: UltidrawChromeProps
  fetchDocument?: (path: string) => Promise<string>
  deferSplash?: boolean
  onLoadingChange?: (loading: boolean, phase: DocsLoadingPhase) => void
}) {
  const editable = mode === "edit" && session.mode !== "view"
  const collaboration = session.collaboration && Boolean(session.wsUrl && session.token)

  const [parsed, setParsed] = useState<ParsedDrawFile | null>(null)
  const [loadError, setLoadError] = useState<string | null>(null)
  const [ydoc, setYdoc] = useState<Y.Doc | null>(null)
  const [provider, setProvider] = useState<HocuspocusProvider | null>(null)
  const [collabSynced, setCollabSynced] = useState(false)
  const [collabError, setCollabError] = useState<string | null>(null)
  const [api, setApi] = useState<ExcalidrawImperativeAPI | null>(null)
  const [binding, setBinding] = useState<ExcalidrawBinding | null>(null)
  const [saveStatus, setSaveStatus] = useState<UltidrawSaveStatus>("idle")

  const handleExcalidrawApi = useCallback((next: ExcalidrawImperativeAPI) => {
    setApi(next)
  }, [])

  useEffect(() => {
    let cancelled = false
    setParsed(null)
    setLoadError(null)
    void (async () => {
      try {
        let text: string
        if (fetchDocument) {
          text = await fetchDocument(session.canonicalPath)
        } else if (session.documentUrl) {
          const res = await fetch(session.documentUrl)
          if (!res.ok) throw new Error("document introuvable")
          text = await res.text()
        } else {
          const blob = await apiClient.getBlob(driveDownloadApiPath(session.canonicalPath))
          text = await blob.text()
        }
        if (!cancelled) setParsed(parseDrawFile(text))
      } catch (e) {
        if (!cancelled) {
          setLoadError(e instanceof Error ? e.message : "Impossible de charger le dessin")
        }
      }
    })()
    return () => {
      cancelled = true
    }
  }, [fetchDocument, session.canonicalPath, session.documentUrl])

  useEffect(() => {
    if (!collaboration) {
      setYdoc(null)
      return
    }
    const doc = new Y.Doc()
    setYdoc(doc)
    return () => {
      doc.destroy()
      setYdoc(null)
    }
  }, [collaboration, session.roomId])

  useEffect(() => {
    if (!collaboration || !ydoc || !parsed) return

    setCollabSynced(false)
    setCollabError(null)
    setApi(null)
    setBinding(null)

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

    p.awareness?.setLocalStateField("user", {
      name: userName,
      color: userColor,
    })

    p.on("status", (event: { status: string }) => {
      if (event.status === "connecting") setSaveStatus("saving")
      if (event.status === "connected") setSaveStatus("saved")
    })

    setProvider(p)
    return () => {
      p.destroy()
      setProvider(null)
      setCollabSynced(false)
      setApi(null)
      setBinding(null)
    }
  }, [collaboration, parsed, session.roomId, session.token, session.wsUrl, userColor, userName, ydoc])

  useEffect(() => {
    if (!api || !ydoc || !collaboration || !provider || !collabSynced) return

    const yElements = ydoc.getArray<Y.Map<unknown>>("elements")
    const yAssets = ydoc.getMap("assets")

    const b = new ExcalidrawBinding(
      yElements,
      yAssets,
      api,
      provider.awareness ?? undefined
    )
    setBinding(b)
    return () => {
      b.destroy()
      setBinding(null)
    }
  }, [api, collaboration, collabSynced, provider, ydoc])

  const presenceUsers = useCollabPresence(provider, { name: userName, color: userColor })

  const excalidrawInitialData = useMemo(() => {
    if (!parsed) return undefined
    if (collaboration && ydoc && collabSynced) {
      const elements = yjsToExcalidraw(ydoc.getArray("elements"))
      return {
        elements,
        appState: parsed.appState,
        files: parsed.files,
      }
    }
    if (!collaboration) {
      return {
        elements: parsed.elements,
        appState: parsed.appState,
        files: parsed.files,
      }
    }
    return undefined
  }, [collaboration, collabSynced, parsed, ydoc])

  const collabReady = !collaboration || collabSynced
  const editorReady = parsed && collabReady && excalidrawInitialData
  const documentLoading = !editorReady
  const loadingPhase = resolveUltidrawDocumentLoadingPhase({
    collaboration: Boolean(collaboration),
    collabSynced,
  })

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

  if (loadError) {
    return (
      <div className="flex h-full flex-col items-center justify-center gap-4 p-8">
        <p className="text-sm text-muted-foreground">{loadError}</p>
      </div>
    )
  }

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

  if (deferSplash && documentLoading) {
    return null
  }

  if (collabError) {
    return (
      <div className="flex h-full flex-col items-center justify-center gap-4 p-8">
        <p className="text-sm text-muted-foreground">{collabError}</p>
      </div>
    )
  }

  return (
    <div className="flex h-full min-h-0 flex-col">
      <UltidrawChrome
        {...chrome}
        saveStatus={collaboration ? saveStatus : "idle"}
        presenceUsers={collaboration ? presenceUsers : []}
      />
      <div className="relative min-h-0 flex-1">
        <ExcalidrawCanvas
          key={session.roomId}
          excalidrawAPI={handleExcalidrawApi}
          initialData={excalidrawInitialData}
          langCode="fr-FR"
          viewModeEnabled={!editable}
          onPointerUpdate={binding?.onPointerUpdate}
          UIOptions={{
            canvasActions: {
              changeViewBackgroundColor: true,
              clearCanvas: editable,
              export: false,
              loadScene: false,
              saveToActiveFile: false,
              toggleTheme: true,
            },
          }}
        />
      </div>
    </div>
  )
}
