"use client"

import * as React from "react"
import { Suspense } from "react"
import { useSearchParams } from "next/navigation"

import {
  useDesignSystemSearchParams,
  type DesignSystemSearchParams,
} from "@/app/(app)/(create)/lib/search-params"

type HistoryContextValue = {
  canGoBack: boolean
  canGoForward: boolean
  goBack: () => void
  goForward: () => void
}

const HistoryContext = React.createContext<HistoryContextValue | null>(null)

// Reads useSearchParams() in its own Suspense boundary so the
// provider never blanks out children while search params resolve.
// useSearchParams reflects the *settled* preset, which coalesces the
// transient values nuqs emits mid-update — reading nuqs state directly
// here would record those transients as phantom history entries.
function PresetSync({
  onPresetChange,
}: {
  onPresetChange: (preset: string) => void
}) {
  const searchParams = useSearchParams()
  const preset = searchParams.get("preset") ?? ""

  React.useEffect(() => {
    onPresetChange(preset)
  }, [preset, onPresetChange])

  return null
}

export function HistoryProvider({ children }: { children: React.ReactNode }) {
  // Write through the shared search-params hook (shallow) instead of
  // router.replace. router.replace triggers a full server navigation that
  // resets the preset on prod — the same failure the customizer avoids by
  // going shallow (#11060).
  const [, setParams] = useDesignSystemSearchParams()

  const [preset, setPreset] = React.useState("")

  const entriesRef = React.useRef<string[]>([preset])
  const indexRef = React.useRef(0)
  const maxIndexRef = React.useRef(0)
  const isNavigatingRef = React.useRef(false)

  const [index, setIndex] = React.useState(0)
  const [maxIndex, setMaxIndex] = React.useState(0)

  const onPresetChange = React.useCallback((nextPreset: string) => {
    setPreset(nextPreset)
  }, [])

  React.useEffect(() => {
    if (isNavigatingRef.current) {
      isNavigatingRef.current = false
      return
    }

    if (preset === entriesRef.current[indexRef.current]) {
      return
    }

    const nextEntries = entriesRef.current.slice(0, indexRef.current + 1)
    nextEntries.push(preset)
    entriesRef.current = nextEntries

    const nextIndex = nextEntries.length - 1
    indexRef.current = nextIndex
    maxIndexRef.current = nextIndex
    setIndex(nextIndex)
    setMaxIndex(nextIndex)
  }, [preset])

  const canGoBack = index > 0
  const canGoForward = index < maxIndex

  const goBack = React.useCallback(() => {
    if (indexRef.current <= 0) {
      return
    }

    isNavigatingRef.current = true
    const nextIndex = indexRef.current - 1
    indexRef.current = nextIndex
    setIndex(nextIndex)

    // The first history entry is "" (no preset in the URL); null clears the
    // param to restore that state. nuqs accepts null to clear a key, which the
    // wrapper's input type does not model.
    setParams(
      {
        preset: entriesRef.current[nextIndex] || null,
      } as Partial<DesignSystemSearchParams>,
      { history: "replace" }
    )
  }, [setParams])

  const goForward = React.useCallback(() => {
    if (indexRef.current >= maxIndexRef.current) {
      return
    }

    isNavigatingRef.current = true
    const nextIndex = indexRef.current + 1
    indexRef.current = nextIndex
    setIndex(nextIndex)

    // The first history entry is "" (no preset in the URL); null clears the
    // param to restore that state. nuqs accepts null to clear a key, which the
    // wrapper's input type does not model.
    setParams(
      {
        preset: entriesRef.current[nextIndex] || null,
      } as Partial<DesignSystemSearchParams>,
      { history: "replace" }
    )
  }, [setParams])

  React.useEffect(() => {
    const down = (e: KeyboardEvent) => {
      if (!e.metaKey && !e.ctrlKey) {
        return
      }

      if (
        (e.target instanceof HTMLElement && e.target.isContentEditable) ||
        e.target instanceof HTMLInputElement ||
        e.target instanceof HTMLTextAreaElement ||
        e.target instanceof HTMLSelectElement
      ) {
        return
      }

      const key = e.key.toLowerCase()

      if ((key === "z" && e.shiftKey) || (key === "y" && e.ctrlKey)) {
        e.preventDefault()
        goForward()
        return
      }

      if (key === "z") {
        e.preventDefault()
        goBack()
      }
    }

    document.addEventListener("keydown", down)

    return () => {
      document.removeEventListener("keydown", down)
    }
  }, [goBack, goForward])

  const value = React.useMemo(
    () => ({ canGoBack, canGoForward, goBack, goForward }),
    [canGoBack, canGoForward, goBack, goForward]
  )

  return (
    <HistoryContext value={value}>
      <Suspense>
        <PresetSync onPresetChange={onPresetChange} />
      </Suspense>
      {children}
    </HistoryContext>
  )
}

export function useHistory() {
  const context = React.useContext(HistoryContext)
  if (!context) {
    throw new Error("useHistory must be used within HistoryProvider")
  }
  return context
}
