/**
 * VFS Tree Utilities
 * Provides safe tree operations that prevent common recursion issues
 */

import { VFSEntity, VFSDirent } from './types.js'

export interface TreeNode {
  name: string
  path: string
  type: 'file' | 'directory'
  entityId?: string
  children?: TreeNode[]
  metadata?: any
}

export interface TreeOptions {
  maxDepth?: number
  includeHidden?: boolean
  filter?: (node: VFSEntity) => boolean
  sort?: 'name' | 'modified' | 'size'
  expandAll?: boolean
}

/**
 * Tree utility functions for VFS
 * These functions ensure proper tree structure without recursion issues
 */
export class VFSTreeUtils {
  /**
   * Build a safe tree structure from VFS entities
   * Guarantees no directory appears as its own child
   */
  static buildTree(
    entities: VFSEntity[],
    rootPath: string = '/',
    options: TreeOptions = {}
  ): TreeNode {
    const pathToEntity = new Map<string, VFSEntity>()
    const pathToNode = new Map<string, TreeNode>()

    // First pass: index all entities by path
    for (const entity of entities) {
      const path = entity.metadata.path

      // Critical: Skip if entity IS the root we're building from
      if (path === rootPath) {
        continue
      }

      pathToEntity.set(path, entity)
    }

    // Create root node
    const rootNode: TreeNode = {
      name: rootPath === '/' ? 'root' : rootPath.split('/').pop()!,
      path: rootPath,
      type: 'directory',
      children: []
    }
    pathToNode.set(rootPath, rootNode)

    // Second pass: build tree structure
    const sortedPaths = Array.from(pathToEntity.keys()).sort()

    for (const path of sortedPaths) {
      const entity = pathToEntity.get(path)!

      // Apply filter if provided
      if (options.filter && !options.filter(entity)) {
        continue
      }

      // Skip hidden files if requested
      if (!options.includeHidden && entity.metadata.name.startsWith('.')) {
        continue
      }

      // Create node for this entity
      const node: TreeNode = {
        name: entity.metadata.name,
        path: entity.metadata.path,
        type: entity.metadata.vfsType === 'directory' ? 'directory' : 'file',
        entityId: entity.id,
        metadata: entity.metadata
      }

      if (entity.metadata.vfsType === 'directory') {
        node.children = []
      }

      pathToNode.set(path, node)

      // Find parent and attach
      const parentPath = this.getParentPath(path)
      const parentNode = pathToNode.get(parentPath)

      if (parentNode && parentNode.children) {
        parentNode.children.push(node)
      }
    }

    // Sort children if requested
    if (options.sort) {
      this.sortTreeNodes(rootNode, options.sort)
    }

    // Apply depth limit if specified
    if (options.maxDepth !== undefined) {
      this.limitDepth(rootNode, options.maxDepth)
    }

    return rootNode
  }

  /**
   * Get direct children only - guaranteed no self-inclusion
   */
  static getDirectChildren(
    entities: VFSEntity[],
    parentPath: string
  ): VFSEntity[] {
    const children: VFSEntity[] = []
    const parentDepth = parentPath === '/' ? 0 : parentPath.split('/').length - 1

    for (const entity of entities) {
      const path = entity.metadata.path

      // Critical check 1: Skip if this IS the parent
      if (path === parentPath) {
        continue
      }

      // Check if entity is a direct child
      if (path.startsWith(parentPath)) {
        const relativePath = parentPath === '/'
          ? path.substring(1)
          : path.substring(parentPath.length + 1)

        // Direct child has no additional slashes
        if (!relativePath.includes('/')) {
          children.push(entity)
        }
      }
    }

    return children
  }

  /**
   * Get all descendants (recursive children)
   */
  static getDescendants(
    entities: VFSEntity[],
    ancestorPath: string,
    includeAncestor: boolean = false
  ): VFSEntity[] {
    const descendants: VFSEntity[] = []

    for (const entity of entities) {
      const path = entity.metadata.path

      // Include ancestor only if explicitly requested
      if (path === ancestorPath) {
        if (includeAncestor) {
          descendants.push(entity)
        }
        continue
      }

      // Check if entity is under ancestor path
      const prefix = ancestorPath === '/' ? '/' : ancestorPath + '/'
      if (path.startsWith(prefix)) {
        descendants.push(entity)
      }
    }

    return descendants
  }

  /**
   * Flatten a tree structure back to a list
   */
  static flattenTree(node: TreeNode): TreeNode[] {
    const result: TreeNode[] = [node]

    if (node.children) {
      for (const child of node.children) {
        result.push(...this.flattenTree(child))
      }
    }

    return result
  }

  /**
   * Find a node in the tree by path
   */
  static findNode(root: TreeNode, targetPath: string): TreeNode | null {
    if (root.path === targetPath) {
      return root
    }

    if (root.children) {
      for (const child of root.children) {
        const found = this.findNode(child, targetPath)
        if (found) return found
      }
    }

    return null
  }

  /**
   * Calculate tree statistics
   */
  static getTreeStats(node: TreeNode): {
    totalNodes: number
    files: number
    directories: number
    maxDepth: number
    totalSize?: number
  } {
    let stats = {
      totalNodes: 0,
      files: 0,
      directories: 0,
      maxDepth: 0,
      totalSize: 0
    }

    function traverse(n: TreeNode, depth: number) {
      stats.totalNodes++
      stats.maxDepth = Math.max(stats.maxDepth, depth)

      if (n.type === 'file') {
        stats.files++
        if (n.metadata?.size) {
          stats.totalSize += n.metadata.size
        }
      } else {
        stats.directories++
      }

      if (n.children) {
        for (const child of n.children) {
          traverse(child, depth + 1)
        }
      }
    }

    traverse(node, 0)
    return stats
  }

  // Helper methods

  private static getParentPath(path: string): string {
    if (path === '/') return '/'
    const parts = path.split('/')
    parts.pop()
    return parts.length === 1 ? '/' : parts.join('/')
  }

  private static sortTreeNodes(node: TreeNode, sortBy: 'name' | 'modified' | 'size'): void {
    if (!node.children) return

    node.children.sort((a, b) => {
      // Directories first, then files
      if (a.type !== b.type) {
        return a.type === 'directory' ? -1 : 1
      }

      switch (sortBy) {
        case 'name':
          return a.name.localeCompare(b.name)
        case 'modified':
          const aTime = a.metadata?.modified || 0
          const bTime = b.metadata?.modified || 0
          return bTime - aTime
        case 'size':
          const aSize = a.metadata?.size || 0
          const bSize = b.metadata?.size || 0
          return bSize - aSize
        default:
          return 0
      }
    })

    // Recursively sort children
    for (const child of node.children) {
      this.sortTreeNodes(child, sortBy)
    }
  }

  private static limitDepth(node: TreeNode, maxDepth: number, currentDepth: number = 0): void {
    if (currentDepth >= maxDepth) {
      delete node.children
      return
    }

    if (node.children) {
      for (const child of node.children) {
        this.limitDepth(child, maxDepth, currentDepth + 1)
      }
    }
  }

  /**
   * Validate tree structure - ensures no recursion
   */
  static validateTree(node: TreeNode, visited: Set<string> = new Set()): {
    valid: boolean
    errors: string[]
  } {
    const errors: string[] = []

    // Check for cycles
    if (visited.has(node.path)) {
      errors.push(`Cycle detected at path: ${node.path}`)
      return { valid: false, errors }
    }

    visited.add(node.path)

    // Check children
    if (node.children) {
      const childPaths = new Set<string>()

      for (const child of node.children) {
        // Check for duplicate children
        if (childPaths.has(child.path)) {
          errors.push(`Duplicate child path: ${child.path}`)
        }
        childPaths.add(child.path)

        // Check child is not parent
        if (child.path === node.path) {
          errors.push(`Directory contains itself: ${node.path}`)
        }

        // Check child is actually under parent
        if (node.path !== '/' && !child.path.startsWith(node.path + '/')) {
          errors.push(`Child ${child.path} not under parent ${node.path}`)
        }

        // Recursively validate children
        const childValidation = this.validateTree(child, new Set(visited))
        errors.push(...childValidation.errors)
      }
    }

    return {
      valid: errors.length === 0,
      errors
    }
  }
}