// SPDX-FileCopyrightText: 2025 Weibo, Inc.
//
// SPDX-License-Identifier: Apache-2.0

'use client'

import { useState, useMemo, useEffect } from 'react'
import {
  ChevronRight,
  ChevronDown,
  Folder,
  FolderOpen,
  FolderPlus,
  Pencil,
  Trash2,
} from 'lucide-react'
import { DocumentItem } from './DocumentItem'
import type { KnowledgeDocument, KnowledgeFolder } from '@/types/knowledge'
import { useTranslation } from '@/hooks/useTranslation'

/** A node in the merged tree: folder, document, or a synthetic folder from document names */
interface FolderNode {
  type: 'folder' | 'api-folder'
  id: number
  name: string
  path: string
  children: TreeNode[]
  documentCount: number
  /** Whether this is a real folder from the API (enables CRUD operations) */
  isApiFolder: boolean
  created_at?: string
  updated_at?: string
}

interface DocumentNode {
  type: 'document'
  displayName: string
  document: KnowledgeDocument
}

type TreeNode = FolderNode | DocumentNode

interface FolderTreeProps {
  /** API folders from knowledge base */
  folders: KnowledgeFolder[]
  /** All documents in the knowledge base */
  documents: KnowledgeDocument[]
  compact?: boolean
  withBorder?: boolean
  onViewDetail?: (doc: KnowledgeDocument) => void
  onEdit?: (doc: KnowledgeDocument) => void
  onDelete?: (doc: KnowledgeDocument) => void
  onRefresh?: (doc: KnowledgeDocument) => void
  onReindex?: (doc: KnowledgeDocument) => void
  onMove?: (doc: KnowledgeDocument) => void
  refreshingDocId?: number | null
  reindexingDocId?: number | null
  canManage?: (doc: KnowledgeDocument) => boolean
  canSelect?: (doc: KnowledgeDocument) => boolean
  selectedIds?: Set<number>
  onSelect?: (doc: KnowledgeDocument, selected: boolean) => void
  ragConfigured?: boolean
  nameColumnWidth?: number
  /** Folder CRUD handlers */
  onCreateFolder?: (parentId: number) => void
  onRenameFolder?: (folderId: number, currentName: string) => void
  onDeleteFolder?: (folderId: number, folderName: string) => void
  /** Whether the user can manage folders (permission from KB) */
  canManageFolders?: boolean
  /** Sort field for mixed folder/document sorting (required) */
  sortField: SortField
  /** Sort order (required) */
  sortOrder: SortOrder
}

export type SortField = 'name' | 'size' | 'createdAt' | 'updatedAt'
export type SortOrder = 'asc' | 'desc'

/** Get display name for a tree node */
function getNodeName(node: TreeNode): string {
  return node.type === 'document' ? (node as DocumentNode).displayName : (node as FolderNode).name
}

/** Get sortable value from a tree node based on sort field */
function getNodeSortValue(node: TreeNode, field: SortField): string | number {
  if (node.type === 'document') {
    const doc = (node as DocumentNode).document
    switch (field) {
      case 'name':
        return doc.name
      case 'size':
        return doc.file_size
      case 'createdAt':
        return new Date(doc.created_at).getTime()
      case 'updatedAt':
        return new Date(doc.updated_at).getTime()
    }
  }
  // Folders: use created_at/updated_at when available, fallback to name for size
  const folder = node as FolderNode
  switch (field) {
    case 'createdAt':
      return folder.created_at ? new Date(folder.created_at).getTime() : 0
    case 'updatedAt':
      return folder.updated_at ? new Date(folder.updated_at).getTime() : 0
    default:
      return folder.name
  }
}

/**
 * Sort tree nodes: folders and documents mixed together.
 * For name/createdAt/updatedAt: mixed comparison by field value.
 * For size: folders first (folders lack size), then documents by size.
 */
function sortTreeNodes(nodes: TreeNode[], field: SortField, order: SortOrder): TreeNode[] {
  const sorted = [...nodes]
  sorted.sort((a, b) => {
    const aVal = getNodeSortValue(a, field)
    const bVal = getNodeSortValue(b, field)

    // For name/createdAt/updatedAt: mixed comparison by field value
    if (field !== 'size') {
      const cmp =
        typeof aVal === 'number' && typeof bVal === 'number'
          ? aVal - bVal
          : String(aVal).localeCompare(String(bVal))
      return order === 'asc' ? cmp : -cmp
    }

    // For size: folders first, then documents by field value
    const aIsFolder = a.type !== 'document'
    const bIsFolder = b.type !== 'document'
    if (aIsFolder !== bIsFolder) {
      return aIsFolder ? -1 : 1
    }
    if (aIsFolder && bIsFolder) {
      // Folders always sorted by name ascending, consistent with macOS behavior
      return getNodeName(a).localeCompare(getNodeName(b))
    }
    // Both are documents: compare by file_size (guaranteed number for document nodes)
    const cmp = Number(aVal) - Number(bVal)
    return order === 'asc' ? cmp : -cmp
  })
  return sorted
}

/**
 * Convert API folder nodes to TreeNode structure recursively.
 * Does NOT sort children — sorting is handled uniformly by sortTreeRecursive
 * after the full tree is built.
 */
function convertFolderToNode(
  folder: KnowledgeFolder,
  docsByFolderId: Map<number, KnowledgeDocument[]>
): FolderNode {
  const folderDocs = docsByFolderId.get(folder.id) || []

  const children: TreeNode[] = [
    ...folderDocs.map(doc => ({
      type: 'document' as const,
      displayName: doc.name,
      document: doc,
    })),
    ...folder.children.map(child => convertFolderToNode(child, docsByFolderId)),
  ]

  // Count total documents recursively
  const totalDocs =
    folderDocs.length + folder.children.reduce((sum, c) => sum + c.document_count, 0)

  return {
    type: 'api-folder',
    id: folder.id,
    name: folder.name,
    path: `folder:${folder.id}`,
    children,
    documentCount: totalDocs,
    isApiFolder: true,
    created_at: folder.created_at,
    updated_at: folder.updated_at,
  }
}

/**
 * Build a merged tree from API folders and documents.
 *
 * Documents with folder_id = 0 go to root level.
 * Documents with folder_id > 0 go under the matching folder.
 * API folders form the hierarchy, and documents are leaf nodes within them.
 *
 * When no API folders exist, falls back to building a virtual tree
 * from document name paths (backward compatible '/' splitting).
 */
function buildMergedTree(
  folders: KnowledgeFolder[],
  documents: KnowledgeDocument[],
  sortField: SortField,
  sortOrder: SortOrder
): TreeNode[] {
  // Group documents: root (folder_id=0) vs folder-specific
  const rootDocs: KnowledgeDocument[] = []
  const docsByFolderId = new Map<number, KnowledgeDocument[]>()
  let hasAnyRealFolder = folders.length > 0

  for (const doc of documents) {
    if (doc.folder_id === 0) {
      rootDocs.push(doc)
    } else {
      const arr = docsByFolderId.get(doc.folder_id) || []
      arr.push(doc)
      docsByFolderId.set(doc.folder_id, arr)
      hasAnyRealFolder = true
    }
  }

  // If there are real folders from the API, use them to build the tree
  if (hasAnyRealFolder) {
    const tree: TreeNode[] = [
      ...rootDocs.map(doc => ({
        type: 'document' as const,
        displayName: doc.name,
        document: doc,
      })),
      ...folders.map(folder => convertFolderToNode(folder, docsByFolderId)),
    ]
    return sortTreeRecursive(tree, sortField, sortOrder)
  }

  // Fallback: build virtual tree from document names (backward compat)
  return buildFallbackTree(documents, sortField, sortOrder)
}

/**
 * Build virtual tree by splitting document names on '/'.
 * This is the backward-compatible fallback when no real folders exist.
 */
function buildFallbackTree(
  documents: KnowledgeDocument[],
  sortField: SortField,
  sortOrder: SortOrder
): TreeNode[] {
  const root: TreeNode[] = []
  const folderMap = new Map<string, FolderNode>()

  const getOrCreateFolder = (segments: string[], parentChildren: TreeNode[]): FolderNode => {
    const path = segments.join('/')
    if (folderMap.has(path)) {
      return folderMap.get(path)!
    }
    const folderNode: FolderNode = {
      type: 'folder',
      id: 0,
      name: segments[segments.length - 1],
      path,
      children: [],
      documentCount: 0,
      isApiFolder: false,
    }
    folderMap.set(path, folderNode)
    parentChildren.push(folderNode)
    return folderNode
  }

  for (const doc of documents) {
    const parts = doc.name.split('/')
    if (parts.length === 1) {
      root.push({
        type: 'document',
        displayName: doc.name,
        document: doc,
      })
    } else {
      let currentChildren = root
      for (let i = 0; i < parts.length - 1; i++) {
        const pathSegments = parts.slice(0, i + 1)
        const folder = getOrCreateFolder(pathSegments, currentChildren)
        folder.documentCount++
        currentChildren = folder.children
      }
      currentChildren.push({
        type: 'document',
        displayName: parts[parts.length - 1],
        document: doc,
      })
    }
  }

  return sortTreeRecursive(root, sortField, sortOrder)
}

/** Recursively sort tree nodes and their children */
function sortTreeRecursive(nodes: TreeNode[], field: SortField, order: SortOrder): TreeNode[] {
  return sortTreeNodes(nodes, field, order).map(node => {
    if (node.type !== 'document' && (node as FolderNode).children.length > 0) {
      return { ...node, children: sortTreeRecursive((node as FolderNode).children, field, order) }
    }
    return node
  })
}

/** Generate a stable key for a tree node based on its type */
function treeNodeKey(node: TreeNode): string {
  if (node.type === 'document') {
    return `doc:${(node as DocumentNode).document.id}`
  }
  return `folder:${node.path}`
}

interface FolderRowProps {
  node: FolderNode
  depth: number
  compact: boolean
  expanded: boolean
  onToggle: (path: string) => void
  onCreateFolder?: (parentId: number) => void
  onRenameFolder?: (folderId: number, currentName: string) => void
  onDeleteFolder?: (folderId: number, folderName: string) => void
  canManageFolders?: boolean
}

function FolderRow({
  node,
  depth,
  compact,
  expanded,
  onToggle,
  onCreateFolder,
  onRenameFolder,
  onDeleteFolder,
  canManageFolders,
}: FolderRowProps) {
  const { t } = useTranslation('knowledge')
  const indent = depth * (compact ? 12 : 16)
  const isApiFolder = node.isApiFolder

  const folderActions =
    isApiFolder && canManageFolders ? (
      <span
        className="flex items-center gap-1 ml-auto flex-shrink-0"
        onClick={e => e.stopPropagation()}
      >
        {onCreateFolder && (
          <button
            className="p-1.5 rounded-md text-text-muted hover:text-primary hover:bg-primary/10 transition-colors"
            title={t('document.folder.create')}
            onClick={() => onCreateFolder(node.id)}
            data-testid={`create-subfolder-${node.id}`}
          >
            <FolderPlus className="w-3.5 h-3.5" />
          </button>
        )}
        {onRenameFolder && (
          <button
            className="p-1.5 rounded-md text-text-muted hover:text-primary hover:bg-primary/10 transition-colors"
            title={t('document.folder.rename')}
            onClick={() => onRenameFolder(node.id, node.name)}
            data-testid={`rename-folder-${node.id}`}
          >
            <Pencil className="w-3.5 h-3.5" />
          </button>
        )}
        {onDeleteFolder && (
          <button
            className="p-1.5 rounded-md text-text-muted hover:text-error hover:bg-error/10 transition-colors"
            title={t('document.folder.delete')}
            onClick={() => onDeleteFolder(node.id, node.name)}
            data-testid={`delete-folder-${node.id}`}
          >
            <Trash2 className="w-3.5 h-3.5" />
          </button>
        )}
      </span>
    ) : null

  if (compact) {
    return (
      <div
        role="button"
        tabIndex={0}
        className="flex items-center gap-2 w-full px-2 py-2 hover:bg-surface rounded-lg transition-colors text-left cursor-pointer"
        style={{ paddingLeft: `${8 + indent}px` }}
        onClick={() => onToggle(node.path)}
        onKeyDown={e => {
          if (e.key === 'Enter' || e.key === ' ') {
            e.preventDefault()
            onToggle(node.path)
          }
        }}
        title={node.name}
      >
        {expanded ? (
          <ChevronDown className="w-3 h-3 text-text-muted flex-shrink-0" />
        ) : (
          <ChevronRight className="w-3 h-3 text-text-muted flex-shrink-0" />
        )}
        {expanded ? (
          <FolderOpen className="w-3.5 h-3.5 text-amber-500 flex-shrink-0" />
        ) : (
          <Folder className="w-3.5 h-3.5 text-amber-500 flex-shrink-0" />
        )}
        <span className="text-xs font-medium text-text-primary truncate">{node.name}</span>
        <span className="text-[10px] text-text-muted flex-shrink-0">
          {t('document.folder.docCount', { count: node.documentCount })}
        </span>
        {folderActions}
      </div>
    )
  }

  return (
    <div
      className="flex items-center gap-3 px-4 py-3 bg-surface/50 hover:bg-surface transition-colors cursor-pointer border-b border-border min-w-[880px]"
      style={{ paddingLeft: `${16 + indent}px` }}
      onClick={() => onToggle(node.path)}
    >
      {expanded ? (
        <ChevronDown className="w-4 h-4 text-text-muted flex-shrink-0" />
      ) : (
        <ChevronRight className="w-4 h-4 text-text-muted flex-shrink-0" />
      )}
      {expanded ? (
        <FolderOpen className="w-4 h-4 text-amber-500 flex-shrink-0" />
      ) : (
        <Folder className="w-4 h-4 text-amber-500 flex-shrink-0" />
      )}
      <span className="text-sm font-medium text-text-primary">{node.name}</span>
      <span className="text-xs text-text-muted">
        {t('document.folder.docCount', { count: node.documentCount })}
      </span>
      {folderActions}
    </div>
  )
}

interface FolderTreeNodeProps {
  node: TreeNode
  depth: number
  compact: boolean
  expandedFolders: Set<string>
  onToggleFolder: (path: string) => void
  // Folder handlers
  onCreateFolder?: (parentId: number) => void
  onRenameFolder?: (folderId: number, currentName: string) => void
  onDeleteFolder?: (folderId: number, folderName: string) => void
  canManageFolders?: boolean
  // DocumentItem props
  onViewDetail?: (doc: KnowledgeDocument) => void
  onEdit?: (doc: KnowledgeDocument) => void
  onDelete?: (doc: KnowledgeDocument) => void
  onRefresh?: (doc: KnowledgeDocument) => void
  onReindex?: (doc: KnowledgeDocument) => void
  onMove?: (doc: KnowledgeDocument) => void
  isRefreshing?: (docId: number) => boolean
  isReindexing?: (docId: number) => boolean
  canManage?: (doc: KnowledgeDocument) => boolean
  canSelect?: (doc: KnowledgeDocument) => boolean
  selected?: (docId: number) => boolean
  onSelect?: (doc: KnowledgeDocument, selected: boolean) => void
  ragConfigured?: boolean
  nameColumnWidth?: number
}

function FolderTreeNode({
  node,
  depth,
  compact,
  expandedFolders,
  onToggleFolder,
  onCreateFolder,
  onRenameFolder,
  onDeleteFolder,
  canManageFolders,
  onViewDetail,
  onEdit,
  onDelete,
  onRefresh,
  onReindex,
  onMove,
  isRefreshing,
  isReindexing,
  canManage,
  canSelect,
  selected,
  onSelect,
  ragConfigured,
  nameColumnWidth,
}: FolderTreeNodeProps) {
  if (node.type === 'document') {
    const doc = node.document
    const docWithDisplayName = { ...doc, name: node.displayName }

    if (compact) {
      return (
        <div style={{ paddingLeft: `${depth * 12}px` }}>
          <DocumentItem
            document={docWithDisplayName}
            onViewDetail={onViewDetail ? () => onViewDetail(doc) : undefined}
            onEdit={onEdit ? () => onEdit(doc) : undefined}
            onDelete={onDelete ? () => onDelete(doc) : undefined}
            onRefresh={onRefresh ? () => onRefresh(doc) : undefined}
            onReindex={onReindex ? () => onReindex(doc) : undefined}
            onMove={onMove ? () => onMove(doc) : undefined}
            isRefreshing={isRefreshing?.(doc.id) ?? false}
            isReindexing={isReindexing?.(doc.id) ?? false}
            canManage={canManage?.(doc) ?? true}
            canSelect={canSelect?.(doc) ?? false}
            showBorder={false}
            selected={selected?.(doc.id) ?? false}
            onSelect={onSelect}
            compact={true}
            ragConfigured={ragConfigured}
          />
        </div>
      )
    }

    const indent = depth * 16
    return (
      <DocumentItem
        document={docWithDisplayName}
        indent={indent}
        onViewDetail={onViewDetail ? () => onViewDetail(doc) : undefined}
        onEdit={onEdit ? () => onEdit(doc) : undefined}
        onDelete={onDelete ? () => onDelete(doc) : undefined}
        onRefresh={onRefresh ? () => onRefresh(doc) : undefined}
        onReindex={onReindex ? () => onReindex(doc) : undefined}
        onMove={onMove ? () => onMove(doc) : undefined}
        isRefreshing={isRefreshing?.(doc.id) ?? false}
        isReindexing={isReindexing?.(doc.id) ?? false}
        canManage={canManage?.(doc) ?? true}
        canSelect={canSelect?.(doc) ?? false}
        showBorder={true}
        selected={selected?.(doc.id) ?? false}
        onSelect={onSelect}
        compact={false}
        ragConfigured={ragConfigured}
        nameColumnWidth={nameColumnWidth}
      />
    )
  }

  // Folder node
  const isExpanded = expandedFolders.has(node.path)
  return (
    <div>
      <FolderRow
        node={node}
        depth={depth}
        compact={compact}
        expanded={isExpanded}
        onToggle={onToggleFolder}
        onCreateFolder={onCreateFolder}
        onRenameFolder={onRenameFolder}
        onDeleteFolder={onDeleteFolder}
        canManageFolders={canManageFolders}
      />
      {isExpanded && (
        <div>
          {node.children.map(child => (
            <FolderTreeNode
              key={treeNodeKey(child)}
              node={child}
              depth={depth + 1}
              compact={compact}
              expandedFolders={expandedFolders}
              onToggleFolder={onToggleFolder}
              onCreateFolder={onCreateFolder}
              onRenameFolder={onRenameFolder}
              onDeleteFolder={onDeleteFolder}
              canManageFolders={canManageFolders}
              onViewDetail={onViewDetail}
              onEdit={onEdit}
              onDelete={onDelete}
              onRefresh={onRefresh}
              onReindex={onReindex}
              onMove={onMove}
              isRefreshing={isRefreshing}
              isReindexing={isReindexing}
              canManage={canManage}
              canSelect={canSelect}
              selected={selected}
              onSelect={onSelect}
              ragConfigured={ragConfigured}
              nameColumnWidth={nameColumnWidth}
            />
          ))}
        </div>
      )}
    </div>
  )
}

/**
 * FolderTree renders documents grouped into folders.
 *
 * When folders are provided from the API, it uses the real folder hierarchy.
 * When no folders exist, it falls back to building virtual folders from
 * document name paths (backward compatible '/' splitting).
 */
export function FolderTree({
  folders = [],
  documents,
  compact = false,
  withBorder = true,
  onViewDetail,
  onEdit,
  onDelete,
  onRefresh,
  onReindex,
  onMove,
  refreshingDocId,
  reindexingDocId,
  canManage,
  canSelect,
  selectedIds,
  onSelect,
  ragConfigured,
  nameColumnWidth,
  onCreateFolder,
  onRenameFolder,
  onDeleteFolder,
  canManageFolders = false,
  sortField,
  sortOrder,
}: FolderTreeProps) {
  const tree = useMemo(
    () => buildMergedTree(folders, documents, sortField, sortOrder),
    [folders, documents, sortField, sortOrder]
  )

  // Collect all folder paths for default-expand (from source data, independent of sort)
  const allFolderPaths = useMemo(() => {
    const paths: string[] = []

    // API folder paths: folder:${id}
    const collectApiPaths = (items: KnowledgeFolder[]) => {
      for (const f of items) {
        paths.push(`folder:${f.id}`)
        if (f.children) collectApiPaths(f.children)
      }
    }
    collectApiPaths(folders)

    // Fallback virtual folder paths from document names containing '/'
    if (folders.length === 0) {
      const seen = new Set<string>()
      for (const doc of documents) {
        const parts = doc.name.split('/')
        for (let i = 1; i < parts.length; i++) {
          const path = parts.slice(0, i).join('/')
          if (!seen.has(path)) {
            seen.add(path)
            paths.push(path)
          }
        }
      }
    }

    return paths
  }, [folders, documents])

  // Default: all folders expanded; sync when new folders are added
  const [expandedFolders, setExpandedFolders] = useState<Set<string>>(new Set())

  useEffect(() => {
    setExpandedFolders(prev => {
      const next = new Set(prev)
      for (const path of allFolderPaths) {
        next.add(path)
      }
      return next
    })
  }, [allFolderPaths])

  const handleToggleFolder = (path: string) => {
    setExpandedFolders(prev => {
      const next = new Set(prev)
      if (next.has(path)) {
        next.delete(path)
      } else {
        next.add(path)
      }
      return next
    })
  }

  if (compact) {
    return (
      <div className="space-y-0.5">
        {tree.map(node => (
          <FolderTreeNode
            key={node.type === 'document' ? `doc:${node.document.id}` : `folder:${node.path}`}
            node={node}
            depth={0}
            compact={true}
            expandedFolders={expandedFolders}
            onToggleFolder={handleToggleFolder}
            onCreateFolder={onCreateFolder}
            onRenameFolder={onRenameFolder}
            onDeleteFolder={onDeleteFolder}
            canManageFolders={canManageFolders}
            onViewDetail={onViewDetail}
            onEdit={onEdit}
            onDelete={onDelete}
            onRefresh={onRefresh}
            onReindex={onReindex}
            onMove={onMove}
            isRefreshing={id => refreshingDocId === id}
            isReindexing={id => reindexingDocId === id}
            canManage={canManage}
            canSelect={canSelect}
            selected={id => selectedIds?.has(id) ?? false}
            onSelect={onSelect}
            ragConfigured={ragConfigured}
          />
        ))}
      </div>
    )
  }

  // Normal (table) mode
  const treeNodes = tree.map(node => (
    <FolderTreeNode
      key={node.type === 'document' ? `doc:${node.document.id}` : `folder:${node.path}`}
      node={node}
      depth={0}
      compact={false}
      expandedFolders={expandedFolders}
      onToggleFolder={handleToggleFolder}
      onCreateFolder={onCreateFolder}
      onRenameFolder={onRenameFolder}
      onDeleteFolder={onDeleteFolder}
      canManageFolders={canManageFolders}
      onViewDetail={onViewDetail}
      onEdit={onEdit}
      onDelete={onDelete}
      onRefresh={onRefresh}
      onReindex={onReindex}
      onMove={onMove}
      isRefreshing={id => refreshingDocId === id}
      isReindexing={id => reindexingDocId === id}
      canManage={canManage}
      canSelect={canSelect}
      selected={id => selectedIds?.has(id) ?? false}
      onSelect={onSelect}
      ragConfigured={ragConfigured}
      nameColumnWidth={nameColumnWidth}
    />
  ))

  if (withBorder) {
    return <div className="border border-border rounded-lg overflow-x-auto">{treeNodes}</div>
  }

  return <>{treeNodes}</>
}
