// src/tools/search/list.ts
// Copyright (C) 2025 DTP Technologies, LLC
// All Rights Reserved

import fs from "node:fs/promises";
import path from "node:path";
import type { ToolDefinition } from "../../lib/ai-client.js";

import {
  DtpTool,
  type ToolArguments,
  type ToolContext,
} from "../../lib/tool.js";
import { ChatSessionMode } from "../../models/chat-session.js";
import HostMonitorService from "../../services/host-monitor.js";

const MAX_ENTRIES = 1000;

class ListTool extends DtpTool {
  get name(): string {
    return "ListTool";
  }
  get slug(): string {
    return "list";
  }
  get metadata() {
    return {
      name: this.definition.function.name || "list",
      category: "search",
      tags: ["search", "find", "directory", "ls", "list"],
      modes: [
        ChatSessionMode.Plan,
        ChatSessionMode.Build,
        ChatSessionMode.Test,
        ChatSessionMode.Ship,
        ChatSessionMode.Develop,
      ],
    };
  }

  public definition: ToolDefinition = {
    type: "function",
    function: {
      name: "list",
      description:
        "List directory contents with optional filtering. Can show file types, sizes, and modification dates. Supports filtering by glob pattern.",
      parameters: {
        type: "object",
        properties: {
          path: {
            type: "string",
            description:
              "Directory path to list. Defaults to current working directory.",
          },
          pattern: {
            type: "string",
            description:
              "Optional glob pattern to filter results (e.g., '*.ts', 'src/**/*').",
          },
          recursive: {
            type: "boolean",
            description: "List subdirectories recursively (default: false).",
          },
          showHidden: {
            type: "boolean",
            description:
              "Show hidden files (files starting with dot) (default: false).",
          },
          maxDepth: {
            type: "number",
            description:
              "Maximum directory depth for recursive listing (default: 3).",
          },
        },
        required: [],
      },
    },
  };

  public async execute(
    _context: ToolContext,
    args: ToolArguments,
  ): Promise<string> {
    const targetPath = (args.path as string | undefined) || process.cwd();
    const patternStr = args.pattern as string | undefined;
    let pattern: RegExp | undefined;
    if (patternStr) {
      try {
        const globPattern = patternStr
          .replace(/[.+^${}()|[\]\\]/g, "\\$&")
          .replace(/\*/g, ".*")
          .replace(/\?/g, ".");
        pattern = new RegExp("^" + globPattern + "$");
      } catch {
        // Invalid regex, ignore
      }
    }
    const recursive = (args.recursive as boolean) || false;
    const showHidden = (args.showHidden as boolean) || false;
    const maxDepth = (args.maxDepth as number) || 3;

    let resolvedPath: string;
    try {
      resolvedPath = path.resolve(targetPath);
    } catch {
      return this.error("INVALID_PARAMETER", "Invalid path.", {
        parameter: "path",
      });
    }

    try {
      const stat = await fs.stat(resolvedPath);

      if (!stat.isDirectory()) {
        return this.error(
          "INVALID_PARAMETER",
          `"${targetPath}" is not a directory.`,
          {
            parameter: "path",
            recoveryHint: "Provide a directory path to list.",
          },
        );
      }

      const entries = await this.listDirectory(
        resolvedPath,
        pattern || undefined,
        recursive,
        showHidden,
        0,
        maxDepth,
      );

      const limited = entries.slice(0, MAX_ENTRIES);

      let output = "";
      if (entries.length === 0) {
        output = `No entries found in "${targetPath}"`;
      } else {
        const truncated = entries.length > MAX_ENTRIES;
        output = `Contents of "${targetPath}" (${
          truncated ? `showing first ${MAX_ENTRIES} of ` : ""
        }${entries.length} entries):\n\n`;

        for (const entry of limited) {
          const typeIndicator = entry.isDirectory
            ? "d"
            : entry.isSymlink
              ? "l"
              : "-";
          const size = entry.isDirectory ? "-" : entry.size.toString();
          const modified = entry.modified
            ? new Date(entry.modified).toISOString().split("T")[0]
            : "-";
          output += `${typeIndicator} ${size.padStart(10)} ${modified} ${entry.name}\n`;
        }

        if (truncated) {
          output += `\n... and ${entries.length - MAX_ENTRIES} more entries`;
        }
      }

      const byteCount = Buffer.byteLength(output, "utf-8");
      HostMonitorService.toolCall(byteCount);
      return this.success(
        {
          entries: limited,
          total: entries.length,
          truncated: entries.length > MAX_ENTRIES,
        },
        output,
      );
    } catch (error) {
      const errorMessage =
        error instanceof Error ? error.message : String(error);
      return this.error(
        "OPERATION_FAILED",
        `Failed to list directory: ${errorMessage}`,
      );
    }
  }

  private async listDirectory(
    dir: string,
    pattern: RegExp | undefined,
    recursive: boolean,
    showHidden: boolean,
    depth: number,
    maxDepth: number,
  ): Promise<
    Array<{
      name: string;
      isDirectory: boolean;
      isSymlink: boolean;
      size: number;
      modified: Date | null;
    }>
  > {
    const results: Array<{
      name: string;
      isDirectory: boolean;
      isSymlink: boolean;
      size: number;
      modified: Date | null;
    }> = [];

    let entries;
    try {
      entries = await fs.readdir(dir, { withFileTypes: true });
    } catch {
      return results;
    }

    for (const entry of entries) {
      if (!showHidden && entry.name.startsWith(".")) {
        continue;
      }

      if (entry.name === "node_modules" || entry.name === ".git") {
        continue;
      }

      const fullPath = path.join(dir, entry.name);
      let relativePath = fullPath;

      try {
        relativePath = path.relative(process.cwd(), fullPath);
      } catch {
        // ignore
      }

      if (pattern && !pattern.test(relativePath) && !pattern.test(entry.name)) {
        continue;
      }

      let stat;
      try {
        stat = await fs.stat(fullPath);
      } catch {
        continue;
      }

      results.push({
        name: relativePath,
        isDirectory: entry.isDirectory(),
        isSymlink: entry.isSymbolicLink(),
        size: stat.size,
        modified: stat.mtime,
      });

      if (recursive && entry.isDirectory() && depth < maxDepth) {
        const subResults = await this.listDirectory(
          fullPath,
          pattern,
          recursive,
          showHidden,
          depth + 1,
          maxDepth,
        );
        results.push(...subResults);
      }
    }

    return results;
  }
}

export default new ListTool();
