"use client"

import { create } from "zustand"
import { persist } from "zustand/middleware"
import { debouncedPersistJSONStorage } from "@/lib/stores/debounced-json-storage"
import {
  cloneDefaultFolderTree,
  cloneDefaultLabelRows,
  isSystemNavLabelId,
  normalizeLabelRow,
  reconcileLabelRowsFromPersisted,
  type FolderTreeNode,
  type LabelRowItem,
} from "@/lib/sidebar-nav-data"
import {
  collectFolderIdsInTree,
  proposeNewFolderId,
  rekeyFolderSubtreeAt,
  slugifyNavSegment,
} from "@/lib/sidebar-nav-folder-ids"
import {
  buildEmailLabelToSidebarFolderId,
  buildFolderIdToLabelRecord,
} from "@/lib/sidebar-nav-maps"

export type LabelListSidebarVisibility = "show" | "showUnread" | "hide"
export type LabelInMessageListVisibility = "show" | "hide"

export type NavItemPrefs = {
  sidebar?: LabelListSidebarVisibility
  messages?: LabelInMessageListVisibility
}

type NavStoreState = {
  folderTree: FolderTreeNode[]
  labelRows: LabelRowItem[]
  navItemPrefs: Record<string, NavItemPrefs>
}

function newLabelRowId(label: string): string {
  const slug = slugifyNavSegment(label)
  return `lbl-${slug || "libelle"}`
}

function allLabelTextsLower(tree: FolderTreeNode[], rows: LabelRowItem[]): Set<string> {
  const s = new Set<string>()
  const walk = (nodes: FolderTreeNode[]) => {
    for (const n of nodes) {
      s.add(n.label.toLowerCase())
      if (n.children?.length) walk(n.children)
    }
  }
  walk(tree)
  for (const r of rows) s.add(r.label.toLowerCase())
  return s
}

function insertFolderChild(
  nodes: FolderTreeNode[],
  parentId: string | null,
  child: FolderTreeNode
): FolderTreeNode[] {
  if (parentId === null) return [...nodes, child]
  return nodes.map((n) => {
    if (n.id === parentId) return { ...n, children: [...(n.children ?? []), child] }
    if (n.children?.length) return { ...n, children: insertFolderChild(n.children, parentId, child) }
    return n
  })
}

function uniqueLabelRowId(base: string, existingIds: Set<string>): string {
  let id = base
  let n = 0
  while (existingIds.has(id)) {
    n += 1
    id = `${base}-${n}`
  }
  return id
}

function findNodeInTree(nodes: FolderTreeNode[], id: string): FolderTreeNode | null {
  for (const n of nodes) {
    if (n.id === id) return n
    if (n.children?.length) {
      const hit = findNodeInTree(n.children, id)
      if (hit) return hit
    }
  }
  return null
}

function collectSubtreeIds(node: FolderTreeNode): Set<string> {
  const s = new Set<string>([node.id])
  if (node.children?.length) {
    for (const c of node.children) {
      for (const x of collectSubtreeIds(c)) s.add(x)
    }
  }
  return s
}

function isDescendantOf(tree: FolderTreeNode[], maybeDescendantId: string, ancestorId: string): boolean {
  const anc = findNodeInTree(tree, ancestorId)
  if (!anc) return false
  return collectSubtreeIds(anc).has(maybeDescendantId)
}

type FolderNodeLocation = {
  parentId: string | null
  index: number
}

function findNodeLocation(
  nodes: FolderTreeNode[],
  id: string,
  parentId: string | null = null
): FolderNodeLocation | null {
  for (let i = 0; i < nodes.length; i++) {
    if (nodes[i].id === id) return { parentId, index: i }
    const children = nodes[i].children
    if (children?.length) {
      const hit = findNodeLocation(children, id, nodes[i].id)
      if (hit) return hit
    }
  }
  return null
}

function insertNodeAtParentIndex(
  nodes: FolderTreeNode[],
  parentId: string | null,
  index: number,
  node: FolderTreeNode
): FolderTreeNode[] {
  if (parentId === null) {
    const next = [...nodes]
    next.splice(Math.max(0, Math.min(index, next.length)), 0, node)
    return next
  }
  return nodes.map((n) => {
    if (n.id === parentId) {
      const children = [...(n.children ?? [])]
      children.splice(Math.max(0, Math.min(index, children.length)), 0, node)
      return { ...n, children }
    }
    if (n.children?.length) {
      return { ...n, children: insertNodeAtParentIndex(n.children, parentId, index, node) }
    }
    return n
  })
}

function applyFolderTreeRekey(
  tree: FolderTreeNode[],
  movedNodeId: string,
  labelRowIds: string[],
  navItemPrefs: Record<string, NavItemPrefs>
): {
  tree: FolderTreeNode[]
  navItemPrefs: Record<string, NavItemPrefs>
  idMap: Record<string, string>
} {
  const rk = rekeyFolderSubtreeAt(tree, movedNodeId, labelRowIds)
  const idMap = rk?.idMap ?? {}
  if (rk && Object.keys(idMap).length > 0) {
    return {
      tree: rk.tree,
      navItemPrefs: remapNavItemPrefs(navItemPrefs, idMap),
      idMap,
    }
  }
  return { tree, navItemPrefs, idMap: {} }
}

function extractNode(
  nodes: FolderTreeNode[],
  id: string
): { next: FolderTreeNode[]; extracted: FolderTreeNode | null } {
  for (let i = 0; i < nodes.length; i++) {
    const n = nodes[i]
    if (n.id === id) {
      return { next: [...nodes.slice(0, i), ...nodes.slice(i + 1)], extracted: n }
    }
    if (n.children?.length) {
      const inner = extractNode(n.children, id)
      if (inner.extracted) {
        return {
          next: nodes.map((x, j) => (j === i ? { ...x, children: inner.next } : x)),
          extracted: inner.extracted,
        }
      }
    }
  }
  return { next: nodes, extracted: null }
}

function updateNodeInTree(
  nodes: FolderTreeNode[],
  id: string,
  patch: Partial<Pick<FolderTreeNode, "label" | "color">>
): FolderTreeNode[] {
  return nodes.map((n) => {
    if (n.id === id) return { ...n, ...patch }
    if (n.children?.length) return { ...n, children: updateNodeInTree(n.children, id, patch) }
    return n
  })
}

function remapNavItemPrefs(
  prefs: Record<string, NavItemPrefs>,
  idMap: Record<string, string>
): Record<string, NavItemPrefs> {
  if (Object.keys(idMap).length === 0) return prefs
  const next = { ...prefs }
  for (const [oldId, newId] of Object.entries(idMap)) {
    const slice = next[oldId]
    if (!slice) continue
    delete next[oldId]
    next[newId] = { ...next[newId], ...slice }
  }
  return next
}

type NavStoreActions = {
  setNavItemSidebarVisibility: (id: string, v: LabelListSidebarVisibility) => void
  setNavItemMessageVisibility: (id: string, v: LabelInMessageListVisibility) => void
  ensureLabelRowForLabelText: (raw: string) => void
  addLabelRowFromSidebar: (raw: string) => void
  addFolder: (parentId: string | null, name: string) => void
  addSubfolder: (parentId: string, name: string) => void
  updateFolderOrLabelColor: (id: string, color: string) => void
  renameFolderOrLabel: (id: string, newLabel: string) => { idMap: Record<string, string>; emailRename: { from: string; to: string } | null }
  removeFolderOrLabelRow: (id: string) => string[]
  moveFolder: (id: string, newParentId: string | null) => Record<string, string>
  reorderLabelRows: (
    draggedId: string,
    targetId: string,
    placement: "before" | "after"
  ) => void
  moveFolderRelative: (
    draggedId: string,
    targetId: string,
    placement: "before" | "after" | "inside"
  ) => Record<string, string>
  addChildLabelRow: (parentLabelRowId: string, childName: string) => void
  setLabelRowEnabled: (id: string, enabled: boolean) => void

  /** Derived selectors */
  getFolderIdToLabel: () => Record<string, string>
  getEmailLabelToSidebarFolderId: () => Record<string, string>
  getNavItemPrefs: (id: string) => Required<Pick<NavItemPrefs, "sidebar" | "messages">>
}

export const useNavStore = create<NavStoreState & NavStoreActions>()(
  persist(
    (set, get) => ({
      folderTree: cloneDefaultFolderTree(),
      labelRows: cloneDefaultLabelRows(),
      navItemPrefs: {},

      setNavItemSidebarVisibility: (id, v) =>
        set((s) => ({
          navItemPrefs: { ...s.navItemPrefs, [id]: { ...s.navItemPrefs[id], sidebar: v } },
        })),

      setNavItemMessageVisibility: (id, v) =>
        set((s) => ({
          navItemPrefs: { ...s.navItemPrefs, [id]: { ...s.navItemPrefs[id], messages: v } },
        })),

      ensureLabelRowForLabelText: (raw) => {
        const label = raw.trim()
        if (!label) return
        set((s) => {
          const known = allLabelTextsLower(s.folderTree, s.labelRows)
          if (known.has(label.toLowerCase())) return s
          const ids = new Set(s.labelRows.map((r) => r.id))
          const id = uniqueLabelRowId(newLabelRowId(label), ids)
          const row = normalizeLabelRow({
            id,
            label,
            color: "bg-gray-500",
            tabbed: false,
            favorite: false,
            excludeFromPrincipal: false,
            showInMessageList: true,
            enabled: true,
          })
          return { labelRows: [...s.labelRows, row] }
        })
      },

      addLabelRowFromSidebar: (raw) => {
        get().ensureLabelRowForLabelText(raw)
      },

      addFolder: (parentId, name) => {
        const label = name.trim()
        if (!label) return
        set((s) => {
          const fid = proposeNewFolderId(
            s.folderTree,
            s.labelRows.map((r) => r.id),
            parentId,
            label
          )
          const node: FolderTreeNode = { id: fid, label, color: "bg-slate-400" }
          return { folderTree: insertFolderChild(s.folderTree, parentId, node) }
        })
      },

      addSubfolder: (parentId, name) => {
        get().addFolder(parentId, name)
      },

      updateFolderOrLabelColor: (id, color) =>
        set((s) => {
          const inRow = s.labelRows.some((r) => r.id === id)
          if (inRow) {
            return { labelRows: s.labelRows.map((r) => (r.id === id ? { ...r, color } : r)) }
          }
          return { folderTree: updateNodeInTree(s.folderTree, id, { color }) }
        }),

      renameFolderOrLabel: (id, raw) => {
        const nextLabel = raw.trim()
        if (!nextLabel) return { idMap: {}, emailRename: null }

        const snap = get()
        const snapMap = buildFolderIdToLabelRecord(snap.folderTree, snap.labelRows)
        const oldLabelForSync = snapMap[id]
        const emailRename =
          oldLabelForSync && oldLabelForSync !== nextLabel
            ? { from: oldLabelForSync, to: nextLabel }
            : null

        let resultIdMap: Record<string, string> = {}

        set((prev) => {
          const inRow = prev.labelRows.some((r) => r.id === id)
          let nextTree = prev.folderTree
          let nextRows = prev.labelRows
          let navItemPrefs = prev.navItemPrefs
          if (inRow) {
            if (isSystemNavLabelId(id)) {
              nextRows = prev.labelRows.map((r) =>
                r.id === id ? { ...r, label: nextLabel } : r
              )
            } else {
              const usedIds = new Set([
                ...collectFolderIdsInTree(prev.folderTree),
                ...prev.labelRows.filter((r) => r.id !== id).map((r) => r.id),
              ])
              const newRowId = uniqueLabelRowId(newLabelRowId(nextLabel), usedIds)
              const rowMap: Record<string, string> = newRowId !== id ? { [id]: newRowId } : {}
              nextRows = prev.labelRows.map((r) =>
                r.id === id ? { ...r, id: newRowId, label: nextLabel } : r
              )
              if (Object.keys(rowMap).length > 0) {
                navItemPrefs = remapNavItemPrefs(prev.navItemPrefs, rowMap)
                resultIdMap = rowMap
              }
            }
          } else {
            nextTree = updateNodeInTree(prev.folderTree, id, { label: nextLabel })
            const rk = rekeyFolderSubtreeAt(nextTree, id, prev.labelRows.map((r) => r.id))
            const idMap = rk?.idMap ?? {}
            if (rk && Object.keys(idMap).length > 0) {
              nextTree = rk.tree
              navItemPrefs = remapNavItemPrefs(prev.navItemPrefs, idMap)
              resultIdMap = idMap
            }
          }
          return { folderTree: nextTree, labelRows: nextRows, navItemPrefs }
        })

        return { idMap: resultIdMap, emailRename }
      },

      removeFolderOrLabelRow: (id) => {
        if (isSystemNavLabelId(id)) return []
        const snap = get()
        const snapMap = buildFolderIdToLabelRecord(snap.folderTree, snap.labelRows)
        const row = snap.labelRows.find((r) => r.id === id)
        let labelsToRemove: string[] = []
        if (row) {
          const lab = snapMap[id]
          if (lab) labelsToRemove = [lab]
        } else {
          const extracted = extractNode(snap.folderTree, id)
          if (extracted.extracted) {
            const uniq = new Set<string>()
            const collectLabs = (n: FolderTreeNode) => {
              uniq.add(n.label)
              n.children?.forEach(collectLabs)
            }
            collectLabs(extracted.extracted)
            labelsToRemove = [...uniq]
          }
        }

        set((prev) => {
          const rowInner = prev.labelRows.find((r) => r.id === id)
          if (rowInner) {
            return {
              labelRows: prev.labelRows.filter((r) => r.id !== id),
              navItemPrefs: Object.fromEntries(
                Object.entries(prev.navItemPrefs).filter(([k]) => k !== id)
              ),
            }
          }
          const extracted = extractNode(prev.folderTree, id)
          if (extracted.extracted) {
            const dropIds = collectSubtreeIds(extracted.extracted)
            return {
              folderTree: extracted.next,
              navItemPrefs: Object.fromEntries(
                Object.entries(prev.navItemPrefs).filter(([k]) => !dropIds.has(k))
              ),
            }
          }
          return prev
        })

        return labelsToRemove
      },

      moveFolder: (id, newParentId) => {
        let resultIdMap: Record<string, string> = {}
        set((prev) => {
          if (newParentId !== null) {
            if (newParentId === id) return prev
            if (isDescendantOf(prev.folderTree, newParentId, id)) return prev
          }
          const ex = extractNode(prev.folderTree, id)
          if (!ex.extracted) return prev
          let tree = ex.next
          tree = insertFolderChild(tree, newParentId, ex.extracted)
          const movedOldId = ex.extracted.id
          const rekeyed = applyFolderTreeRekey(
            tree,
            movedOldId,
            prev.labelRows.map((r) => r.id),
            prev.navItemPrefs
          )
          tree = rekeyed.tree
          resultIdMap = rekeyed.idMap
          return { folderTree: tree, navItemPrefs: rekeyed.navItemPrefs }
        })
        return resultIdMap
      },

      reorderLabelRows: (draggedId, targetId, placement) => {
        if (draggedId === targetId) return
        if (isSystemNavLabelId(draggedId) || isSystemNavLabelId(targetId)) return
        set((prev) => {
          const from = prev.labelRows.findIndex((r) => r.id === draggedId)
          const targetIdx = prev.labelRows.findIndex((r) => r.id === targetId)
          if (from < 0 || targetIdx < 0) return prev
          let toIndex = placement === "before" ? targetIdx : targetIdx + 1
          if (from < toIndex) toIndex -= 1
          const next = [...prev.labelRows]
          const [item] = next.splice(from, 1)
          next.splice(toIndex, 0, item)
          return { labelRows: next }
        })
      },

      moveFolderRelative: (draggedId, targetId, placement) => {
        let resultIdMap: Record<string, string> = {}
        set((prev) => {
          if (draggedId === targetId) return prev
          if (isDescendantOf(prev.folderTree, targetId, draggedId)) return prev

          const draggedLoc = findNodeLocation(prev.folderTree, draggedId)
          const targetLoc = findNodeLocation(prev.folderTree, targetId)
          if (!targetLoc) return prev

          const ex = extractNode(prev.folderTree, draggedId)
          if (!ex.extracted) return prev

          let tree = ex.next
          let navItemPrefs = prev.navItemPrefs

          if (placement === "inside") {
            if (draggedId === targetId) return prev
            tree = insertFolderChild(tree, targetId, ex.extracted)
          } else {
            let insertIndex =
              placement === "before" ? targetLoc.index : targetLoc.index + 1
            if (
              draggedLoc &&
              draggedLoc.parentId === targetLoc.parentId &&
              draggedLoc.index < insertIndex
            ) {
              insertIndex -= 1
            }
            tree = insertNodeAtParentIndex(
              tree,
              targetLoc.parentId,
              insertIndex,
              ex.extracted
            )
          }

          const rekeyed = applyFolderTreeRekey(
            tree,
            ex.extracted.id,
            prev.labelRows.map((r) => r.id),
            navItemPrefs
          )
          tree = rekeyed.tree
          navItemPrefs = rekeyed.navItemPrefs
          resultIdMap = rekeyed.idMap
          return { folderTree: tree, navItemPrefs }
        })
        return resultIdMap
      },

      addChildLabelRow: (parentLabelRowId, childName) => {
        const sub = childName.trim()
        if (!sub) return
        set((s) => {
          const parent = s.labelRows.find((r) => r.id === parentLabelRowId)
          if (!parent) return s
          const combined = `${parent.label}/${sub}`
          const known = allLabelTextsLower(s.folderTree, s.labelRows)
          if (known.has(combined.toLowerCase())) return s
          const ids = new Set(s.labelRows.map((r) => r.id))
          const nid = uniqueLabelRowId(newLabelRowId(combined), ids)
          return {
            labelRows: [
              ...s.labelRows,
              normalizeLabelRow({
                id: nid,
                label: combined,
                color: parent.color ?? "bg-gray-500",
                tabbed: false,
                favorite: false,
                excludeFromPrincipal: false,
                showInMessageList: true,
                enabled: true,
              }),
            ],
          }
        })
      },

      setLabelRowEnabled: (id, enabled) =>
        set((s) => ({
          labelRows: s.labelRows.map((r) =>
            r.id === id ? normalizeLabelRow({ ...r, enabled }) : r
          ),
        })),

      getFolderIdToLabel: () => {
        const s = get()
        return buildFolderIdToLabelRecord(s.folderTree, s.labelRows)
      },

      getEmailLabelToSidebarFolderId: () => {
        const s = get()
        const fToL = buildFolderIdToLabelRecord(s.folderTree, s.labelRows)
        return buildEmailLabelToSidebarFolderId(fToL)
      },

      getNavItemPrefs: (id) => {
        const p = get().navItemPrefs[id]
        return { sidebar: p?.sidebar ?? "show", messages: p?.messages ?? "show" }
      },
    }),
    {
      name: "ultimail-nav-state",
      storage: debouncedPersistJSONStorage,
      version: 2,
      migrate: (persisted, fromVersion) => {
        if (fromVersion < 2 && persisted && typeof persisted === "object") {
          const p = persisted as { labelRows?: LabelRowItem[] }
          if (Array.isArray(p.labelRows)) {
            return {
              ...persisted,
              labelRows: reconcileLabelRowsFromPersisted(p.labelRows),
            }
          }
        }
        return persisted
      },
    }
  )
)
