/**
 * TreeBrowser: A tree browser component using react-arborist for managing
 * Logos pages in a hierarchical folder structure.
 * 
 * Features:
 * - Drag and drop reordering
 * - Folder expansion/collapse
 * - Context menu for actions
 * - Shadcn theme integration
 * - Inline editing for page titles
 * - Smooth animations
 * - Search functionality
 * - Expand/collapse all
 */

import React, { useCallback, useMemo, useRef, useState } from 'react'
import { useResizeHeight } from '@/hooks/useResizeHeight'
import {
  Tree,
  NodeApi,
  NodeRendererProps,
  RowRendererProps,
  TreeApi,
  type CursorProps,
} from 'react-arborist'
import { cn } from '@/lib/utils'
import {
  FileText,
  ChevronRight,
  ChevronDown,
  Plus,
  MoreHorizontal,
  Trash2,
  Pencil,
  Search,
  ChevronsDownUp,
  ChevronsUpDown,
  X,
  GripVertical,
} from 'lucide-react'
import { Button } from '@/components/ui/button'
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { Input } from '@/components/ui/input'
import { useRecollectionSidebar } from './RecollectionSidebarContext'
import { useParams, useNavigate, useLocation } from 'react-router-dom'
import type { LogosPageMeta } from '../state/recollectionStore'
import { removeLogosPageContent } from '../state/recollectionStore'

// Tree node: page metadata plus tree shape (react-arborist `node.data` is this whole object).
type TreeNode = LogosPageMeta & {
  children?: TreeNode[]
  isFolder: boolean
}

// Convert flat tree to hierarchical structure
function buildTree(pages: LogosPageMeta[]): TreeNode[] {
  const pageMap = new Map<string, TreeNode>()
  const rootNodes: TreeNode[] = []

  // First pass: create all nodes
  pages.forEach((page) => {
    pageMap.set(page.id, {
      ...page,
      children: [],
      isFolder: false,
    })
  })

  // Second pass: build parent-child relationships
  pages.forEach((page) => {
    const node = pageMap.get(page.id)!
    if (page.parentId === null) {
      rootNodes.push(node)
    } else {
      const parent = pageMap.get(page.parentId)
      if (parent) {
        parent.children?.push(node)
        parent.isFolder = true
      }
    }
  })

  // Sort children by position
  function sortNodes(nodes: TreeNode[]) {
    nodes.sort((a, b) => a.position - b.position)
    nodes.forEach((node) => sortNodes(node.children || []))
  }
  sortNodes(rootNodes)

  return rootNodes
}

// Memoize the buildTree result to avoid unnecessary re-creation
function useTreeNodes(tree: LogosPageMeta[]) {
  return useMemo(() => buildTree(tree), [tree])
}

// Inline editable title component
function EditableTitle({
  title,
  onSave,
  onCancel,
}: {
  title: string
  onSave: (newTitle: string) => void
  onCancel: () => void
}) {
  const inputRef = useRef<HTMLInputElement>(null)

  React.useEffect(() => {
    inputRef.current?.focus()
    inputRef.current?.select()
  }, [])

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter') {
      onSave(inputRef.current?.value || '')
    } else if (e.key === 'Escape') {
      onCancel()
    }
  }

  return (
    <input
      ref={inputRef}
      type="text"
      defaultValue={title}
      onBlur={(e) => onSave(e.currentTarget.value)}
      onKeyDown={handleKeyDown}
      className="w-full rounded px-1 py-0.5 text-sm outline-none ring-2 ring-ring focus:ring-2 transition-all duration-200 bg-background"
      onClick={(e) => e.stopPropagation()}
    />
  )
}

const ROW_INDENT_PX = 20
const ROW_GUTTER_PX = 8

/**
 * Line cursor = “insert as sibling at this indent” (not “drop into folder”).
 * Triangle + dashed rail read as a slot between rows; matches sidebar theme.
 */
const LogosDropCursor = React.memo(function LogosDropCursor({ top, left, indent }: CursorProps) {
  return (
    <div
      role="presentation"
      aria-hidden
      className="pointer-events-none absolute z-20 flex items-center gap-1.5"
      style={{
        top: top - 5,
        left: left + ROW_GUTTER_PX,
        right: Math.max(indent, ROW_GUTTER_PX),
      }}
    >
      <span className="flex h-3 w-2.5 shrink-0 items-center justify-center text-primary drop-shadow-sm">
        <svg width="9" height="10" viewBox="0 0 9 10" fill="currentColor" aria-hidden>
          <path d="M0 5 L9 1.5 L9 8.5 Z" />
        </svg>
      </span>
      <div className="flex h-3 min-w-[2rem] flex-1 items-center">
        <div
          className={cn(
            'h-0 w-full border-t-2 border-dashed border-primary',
            'shadow-[0_1px_0_0_hsl(var(--primary)/0.35)]',
            'motion-safe:animate-[tree-drop-line-pulse_1.8s_ease-in-out_infinite]'
          )}
        />
      </div>
    </div>
  )
})

/**
 * Drag ref on a compact handle at the start of the row; handle is a floating pill shown on row hover
 * (always visible on coarse pointers). Title/icon strip is click-to-navigate only.
 */
function LogosTreeNode({ node, dragHandle, style }: NodeRendererProps<TreeNode>) {
  const { recollectionId } = useParams<{ recollectionId: string }>()
  const navigate = useNavigate()
  const { tree, handleSelectPage, handleTreeChange } = useRecollectionSidebar()

  const base = recollectionId ? `/recollections/${recollectionId}` : ''
  const baseLogos = `${base}/logos`

  const handleToggle = useCallback(
    (e: React.MouseEvent) => {
      e.stopPropagation()
      node.toggle()
    },
    [node]
  )

  const handleSelect = useCallback(
    (e: React.MouseEvent) => {
      if (!node.state.isEditing && !node.state.isDragging) {
        handleSelectPage(node.data.id)
        navigate(`${baseLogos}?page=${encodeURIComponent(node.data.id)}`)
      }
    },
    [handleSelectPage, node.data.id, baseLogos, navigate, node.state.isEditing, node.state.isDragging]
  )

  const handleRename = useCallback(() => {
    node.edit()
  }, [node])

  const handleDelete = useCallback(() => {
    const toDelete = new Set<string>()
    function collectDescendants(id: string) {
      toDelete.add(id)
      tree.forEach((p) => {
        if (p.parentId === id) collectDescendants(p.id)
      })
    }
    collectDescendants(node.data.id)
    if (recollectionId) {
      toDelete.forEach((pageId) => {
        removeLogosPageContent(recollectionId, pageId)
      })
    }
    handleTreeChange(tree.filter((p) => !toDelete.has(p.id)))
  }, [tree, handleTreeChange, node.data.id, recollectionId])

  const handleAddChild = useCallback(() => {
    const maxPos = Math.max(
      0,
      ...tree.filter((p) => p.parentId === node.data.id).map((p) => p.position),
      -1
    )
    const newPage: LogosPageMeta = {
      id: `page-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
      title: 'Untitled',
      parentId: node.data.id,
      position: maxPos + 1,
    }
    handleTreeChange([...tree, newPage])
    if (!node.isOpen) node.open()
  }, [tree, handleTreeChange, node])

  const hasChildren = (node.children?.length || 0) > 0
  const pageTitle = node.data.title || 'Untitled'

  return (
    <div className="flex min-w-0 flex-1 items-center gap-1">
      {/* Floating drag pill: expands on row hover; coarse pointers keep it tappable */}
      <div
        className={cn(
          'flex shrink-0 items-center justify-center overflow-hidden',
          'transition-[width,opacity] duration-200 ease-out',
          'w-0 opacity-0 group-hover:w-4 group-hover:opacity-100',
          '[@media(pointer:coarse)]:w-5 [@media(pointer:coarse)]:opacity-100',
          node.state.isDragging && 'w-4 opacity-100 [@media(pointer:coarse)]:w-5',
          node.state.isEditing && 'pointer-events-none w-0 opacity-0'
        )}
      >
        <div
          ref={dragHandle}
          style={{ ...style, paddingLeft: 0 }}
          title={node.state.isEditing ? undefined : 'Drag to reorder'}
          className={cn(
            'logos-tree-drag-handle group/drag',
            'relative flex h-6 w-4 shrink-0 touch-none items-center justify-center rounded-md',
            '[@media(pointer:coarse)]:h-7 [@media(pointer:coarse)]:w-5',
            'border border-sidebar-border/50 bg-sidebar-accent/40',
            'text-sidebar-foreground/70',
            'transition-[color,background-color,border-color,box-shadow,transform] duration-150 ease-out',
            'hover:border-sidebar-border hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
            'hover:shadow-sm',
            'active:scale-[0.97] active:cursor-grabbing',
            'cursor-grab outline-none',
            'dark:border-sidebar-border/55 dark:bg-sidebar-accent/35 dark:text-sidebar-foreground/75',
            'dark:hover:bg-sidebar-accent dark:hover:text-sidebar-accent-foreground',
            node.state.isDragging &&
              'cursor-grabbing border-primary/55 bg-primary/18 text-primary shadow-sm ring-1 ring-primary/30 dark:border-primary/50 dark:bg-primary/22 dark:text-primary'
          )}
          onClick={(e) => e.stopPropagation()}
        >
          <GripVertical
            className={cn(
              'size-3 shrink-0 text-current transition-opacity duration-150 opacity-85 group-hover/drag:opacity-100',
              node.state.isDragging && 'opacity-100'
            )}
            strokeWidth={2}
          />
        </div>
      </div>

      {hasChildren ? (
        <button
          type="button"
          className="shrink-0 p-0.5 hover:bg-sidebar-accent rounded transition-colors"
          onClick={handleToggle}
        >
          {node.isOpen ? (
            <ChevronDown className="size-3.5 text-sidebar-foreground/70" />
          ) : (
            <ChevronRight className="size-3.5 text-sidebar-foreground/70" />
          )}
        </button>
      ) : (
        <div className="w-4 shrink-0" />
      )}

      <div
        className={cn(
          'flex min-h-[28px] min-w-0 flex-1 items-center gap-1 rounded-md px-0.5 -mx-0.5',
          node.state.isEditing ? 'cursor-text' : 'cursor-pointer hover:bg-sidebar-accent/40'
        )}
        onClick={handleSelect}
      >
        <FileText className="size-4 shrink-0 text-sidebar-foreground/70" />

        <div className="min-w-0 flex-1">
          {node.state.isEditing ? (
            <EditableTitle
              title={node.data.title}
              onSave={(newTitle) => {
                if (newTitle.trim()) {
                  handleTreeChange(
                    tree.map((p) => (p.id === node.data.id ? { ...p, title: newTitle.trim() } : p))
                  )
                }
                node.submit(newTitle)
              }}
              onCancel={() => node.reset()}
            />
          ) : (
            <span className="block truncate select-none">{pageTitle}</span>
          )}
        </div>
      </div>

      <div
        className="flex shrink-0 items-center gap-0.5 opacity-0 transition-opacity group-hover:opacity-100"
        onPointerDown={(e) => e.stopPropagation()}
      >
        <button
          type="button"
          className="p-0.5 hover:bg-sidebar-accent rounded transition-colors"
          onClick={(e) => {
            e.stopPropagation()
            handleAddChild()
          }}
          title="Add child page"
        >
          <Plus className="size-3.5 text-sidebar-foreground/70" />
        </button>
        <DropdownMenu>
          <DropdownMenuTrigger asChild>
            <button
              type="button"
              className="p-0.5 hover:bg-sidebar-accent rounded transition-colors"
              onClick={(e) => e.stopPropagation()}
            >
              <MoreHorizontal className="size-3.5 text-sidebar-foreground/70" />
            </button>
          </DropdownMenuTrigger>
          <DropdownMenuContent align="end" className="w-40" onClick={(e) => e.stopPropagation()}>
            <DropdownMenuItem onClick={handleRename}>
              <Pencil className="mr-2 size-3.5" />
              Rename
            </DropdownMenuItem>
            <DropdownMenuSeparator />
            <DropdownMenuItem onClick={handleDelete} className="text-destructive focus:text-destructive">
              <Trash2 className="mr-2 size-3.5" />
              Delete
            </DropdownMenuItem>
          </DropdownMenuContent>
        </DropdownMenu>
      </div>
    </div>
  )
}

/** Drop target + layout shell; `children` must be the Node renderer (drag layer). */
function TreeRow({ node, innerRef, attrs, children }: RowRendererProps<TreeNode>) {
  const { pathname } = useLocation()
  const { activePageId } = useRecollectionSidebar()
  const { recollectionId } = useParams<{ recollectionId: string }>()
  const base = recollectionId ? `/recollections/${recollectionId}` : ''
  const baseLogos = `${base}/logos`
  const isActive = pathname.startsWith(baseLogos) && activePageId === node.data.id
  const level = node.level
  const indentPx = ROW_GUTTER_PX + level * ROW_INDENT_PX

  return (
    <div
      ref={innerRef}
      {...attrs}
      style={{
        ...attrs.style,
        paddingLeft: `${indentPx}px`,
      }}
      className={cn(
        'group relative flex items-center gap-1 py-1 pr-2 text-sm rounded-md transition-[box-shadow,background-color,ring-color] duration-150',
        'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
        isActive && 'bg-sidebar-accent text-sidebar-accent-foreground',
        node.state.isDragging && 'cursor-grabbing opacity-45',
        // Folder drop target: index === null in library — reads as “open container”, not a line between rows
        node.state.willReceiveDrop &&
          'cursor-copy bg-sidebar-accent/30 ring-2 ring-inset ring-dashed ring-sidebar-ring/80 shadow-[inset_0_0_0_1px_hsl(var(--sidebar-border))]'
      )}
      title={node.state.willReceiveDrop ? 'Release to drop inside this folder' : undefined}
    >
      {level > 0 && (
        <div
          className="pointer-events-none absolute bottom-0 top-0 border-l-2 border-sidebar-border/45"
          style={{ left: `${ROW_GUTTER_PX + (level - 1) * ROW_INDENT_PX + ROW_INDENT_PX / 2}px` }}
        />
      )}
      {children}
    </div>
  )
}

export function TreeBrowser() {
  const { tree, handleTreeChange } = useRecollectionSidebar()
  const [searchQuery, setSearchQuery] = useState('')
  const treeRef = useRef<TreeApi<TreeNode> | null>(null)
  const [treeContainerHeight, treeContainerRef] = useResizeHeight(600)

  // Build tree from flat structure
  const treeNodes = useTreeNodes(tree)

  // Filter tree based on search query
  const filteredTree = useMemo(() => {
    if (!searchQuery.trim()) return treeNodes

    const searchTerm = searchQuery.toLowerCase()

    function filterNodes(nodes: TreeNode[]): TreeNode[] {
      const result: TreeNode[] = []
      for (const node of nodes) {
        const matches = node.title.toLowerCase().includes(searchTerm)
        const children = filterNodes(node.children || [])

        if (matches || children.length > 0) {
          result.push({
            ...node,
            children: children.length > 0 ? children : undefined,
          })
        }
      }
      return result
    }

    return filterNodes(treeNodes)
  }, [treeNodes, searchQuery])

  // Get all folder IDs for initial open state
  const initialOpenState = useMemo(() => {
    const folderIds: Record<string, boolean> = {}
    function collectIds(nodes: TreeNode[]) {
      nodes.forEach((node) => {
        if (node.isFolder) {
          folderIds[node.id] = true
          collectIds(node.children || [])
        }
      })
    }
    collectIds(treeNodes)
    return folderIds
  }, [treeNodes])

  // Handle drag and drop reordering (react-arborist onMove)
  const handleMove = useCallback(
    ({
      dragIds,
      parentId: newParentId,
      index,
    }: {
      dragIds: string[]
      parentId: string | null
      index: number
    }) => {
      const dragId = dragIds[0]
      if (!dragId) return

      const dragged = tree.find((p) => p.id === dragId)
      if (!dragged) return

      const oldParentId = dragged.parentId
      const moved: LogosPageMeta = { ...dragged, parentId: newParentId }

      const newSiblings = tree
        .filter((p) => p.parentId === newParentId && p.id !== dragId)
        .sort((a, b) => a.position - b.position)
      newSiblings.splice(index, 0, moved)

      const updates = new Map<string, LogosPageMeta>()
      newSiblings.forEach((p, i) => {
        updates.set(p.id, { ...p, parentId: newParentId, position: i })
      })

      if (oldParentId !== newParentId) {
        tree
          .filter((p) => p.parentId === oldParentId && p.id !== dragId)
          .sort((a, b) => a.position - b.position)
          .forEach((p, i) => {
            updates.set(p.id, { ...p, position: i })
          })
      }

      handleTreeChange(tree.map((p) => updates.get(p.id) ?? p))
    },
    [tree, handleTreeChange]
  )

  // Expand all folders
  const handleExpandAll = useCallback(() => {
    treeRef.current?.openAll()
  }, [])

  // Collapse all folders
  const handleCollapseAll = useCallback(() => {
    treeRef.current?.closeAll()
  }, [])

  // Clear search
  const handleClearSearch = useCallback(() => {
    setSearchQuery('')
  }, [])

  // Fixed prop shape: react-arborist TreeProvider uses [...Object.values(treeProps), …] as useMemo deps;
  // varying key counts on the props object trigger "dependency array changed size" warnings.
  const arboristTreeProps = useMemo(
    () => ({
      data: filteredTree,
      idAccessor: 'id' as const,
      childrenAccessor: 'children' as const,
      width: '100%' as const,
      height: Math.max(treeContainerHeight, 100),
      rowHeight: 32,
      indent: ROW_INDENT_PX,
      renderRow: TreeRow,
      initialOpenState,
      onMove: handleMove,
      disableDrag: Boolean(searchQuery),
      disableDrop: Boolean(searchQuery),
      className: 'react-arborist-tree',
      renderCursor: LogosDropCursor,
      children: LogosTreeNode,
    }),
    [filteredTree, initialOpenState, handleMove, searchQuery, treeContainerHeight]
  )

  return (
    <div className="flex flex-col gap-2 h-full">
      {/* Search Toolbar */}
      <div className="flex items-center gap-1.5">
        <div className="relative flex-1">
          <Search className="absolute left-2 top-1/2 -translate-y-1/2 size-3.5 text-sidebar-foreground/50 pointer-events-none" />
          <Input
            type="text"
            placeholder="Search pages..."
            value={searchQuery}
            onChange={(e) => setSearchQuery(e.target.value)}
            className="pl-7 pr-7 h-7 text-xs bg-sidebar-accent/50 border-sidebar-border"
          />
          {searchQuery && (
            <Button
              type="button"
              variant="ghost"
              size="icon"
              className="absolute right-0 top-1/2 -translate-y-1/2 h-7 w-7"
              onClick={handleClearSearch}
            >
              <X className="size-3" />
            </Button>
          )}
        </div>
        <DropdownMenu>
          <DropdownMenuTrigger asChild>
            <Button
              type="button"
              variant="ghost"
              size="icon"
              className="h-7 w-7 shrink-0"
              title="Expand/Collapse options"
            >
              <ChevronsUpDown className="size-3.5" />
            </Button>
          </DropdownMenuTrigger>
          <DropdownMenuContent align="end">
            <DropdownMenuItem onClick={handleExpandAll}>
              <ChevronsDownUp className="mr-2 size-3.5" />
              Expand all
            </DropdownMenuItem>
            <DropdownMenuItem onClick={handleCollapseAll}>
              <ChevronsUpDown className="mr-2 size-3.5" />
              Collapse all
            </DropdownMenuItem>
          </DropdownMenuContent>
        </DropdownMenu>
      </div>

      {/* Tree */}
      <div ref={treeContainerRef} className="flex-1 min-h-0 overflow-hidden">
        <Tree ref={treeRef} {...arboristTreeProps} />
      </div>
    </div>
  )
}