import fs from 'fs/promises'
import path from 'path'
import * as z from 'zod'

import { logger, MAX_FILES_LIMIT, validatePath } from '../types'

// Schema definition
export const LsToolSchema = z.object({
  path: z.string().optional().describe('The directory to list (must be absolute path). Defaults to the base directory'),
  recursive: z.boolean().optional().describe('Whether to list directories recursively (default: false)')
})

// Tool definition with detailed description
export const lsToolDefinition = {
  name: 'ls',
  description: `Lists files and directories in a specified path.

- Returns a tree-like structure with icons (📁 directories, 📄 files)
- Shows the absolute directory path in the header
- Entries are sorted alphabetically with directories first
- Can list recursively with recursive=true (up to 5 levels deep)
- Common directories (node_modules, dist, .git) are excluded
- Hidden files (starting with .) are excluded except .env.example
- Results are limited to 100 entries
- The path parameter must resolve within the configured workspace root if specified
- If path is not specified, defaults to the base directory`,
  inputSchema: z.toJSONSchema(LsToolSchema)
}

// Handler implementation
export async function handleLsTool(args: unknown, baseDir: string) {
  const parsed = LsToolSchema.safeParse(args)
  if (!parsed.success) {
    throw new Error(`Invalid arguments for ls: ${parsed.error}`)
  }

  const targetPath = parsed.data.path || baseDir
  const validPath = await validatePath(targetPath, baseDir)
  const recursive = parsed.data.recursive || false

  interface TreeNode {
    name: string
    type: 'file' | 'directory'
    children?: TreeNode[]
  }

  let fileCount = 0
  let truncated = false

  async function buildTree(dirPath: string, depth: number = 0): Promise<TreeNode[]> {
    if (fileCount >= MAX_FILES_LIMIT) {
      truncated = true
      return []
    }

    try {
      const entries = await fs.readdir(dirPath, { withFileTypes: true })
      const nodes: TreeNode[] = []

      // Sort entries: directories first, then files, alphabetically
      entries.sort((a, b) => {
        if (a.isDirectory() && !b.isDirectory()) return -1
        if (!a.isDirectory() && b.isDirectory()) return 1
        return a.name.localeCompare(b.name)
      })

      for (const entry of entries) {
        if (fileCount >= MAX_FILES_LIMIT) {
          truncated = true
          break
        }

        // Skip hidden files and common ignore patterns
        if (entry.name.startsWith('.') && entry.name !== '.env.example') {
          continue
        }
        if (['node_modules', 'dist', 'build', '__pycache__'].includes(entry.name)) {
          continue
        }

        fileCount++
        const node: TreeNode = {
          name: entry.name,
          type: entry.isDirectory() ? 'directory' : 'file'
        }

        if (entry.isDirectory() && recursive && depth < 5) {
          const childPath = path.join(dirPath, entry.name)
          // Validate symlinked directories to prevent escaping the workspace root
          try {
            await validatePath(childPath, baseDir)
            node.children = await buildTree(childPath, depth + 1)
          } catch {
            logger.debug('Skipping directory outside workspace root', { path: childPath })
          }
        }

        nodes.push(node)
      }

      return nodes
    } catch (error) {
      return []
    }
  }

  // Build the tree
  const tree = await buildTree(validPath)

  // Format as text output
  function formatTree(nodes: TreeNode[], prefix: string = ''): string[] {
    const lines: string[] = []

    nodes.forEach((node, index) => {
      const isLastNode = index === nodes.length - 1
      const connector = isLastNode ? '└── ' : '├── '
      const icon = node.type === 'directory' ? '📁 ' : '📄 '

      lines.push(prefix + connector + icon + node.name)

      if (node.children && node.children.length > 0) {
        const childPrefix = prefix + (isLastNode ? '    ' : '│   ')
        lines.push(...formatTree(node.children, childPrefix))
      }
    })

    return lines
  }

  // Generate output
  const output: string[] = []
  output.push(`Directory: ${validPath}`)
  output.push('')

  if (tree.length === 0) {
    output.push('(empty directory)')
  } else {
    const treeLines = formatTree(tree, '')
    output.push(...treeLines)

    if (truncated) {
      output.push('')
      output.push(`(Results truncated to ${MAX_FILES_LIMIT} files. Consider listing a more specific directory.)`)
    }
  }

  return {
    content: [
      {
        type: 'text',
        text: output.join('\n')
      }
    ]
  }
}
