// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
// Licensed under the Apache License, Version 2.0

import fs from "node:fs/promises";
import path from "node:path";

import type { IAiLogger, IToolArguments, IToolDefinition } from "@gadget/ai";
import { DroneTool } from "../tool.ts";
import { getProjectRoot, resolveProjectPath, toolError } from "./common.ts";

const MAX_RESULTS = 1000;

export class GlobTool extends DroneTool {
  get name(): string {
    return "glob";
  }

  get category(): string {
    return "system";
  }

  public definition: IToolDefinition = {
    type: "function",
    function: {
      name: this.name,
      description: "Find project files by name using glob pattern matching. Supports patterns like **/*.ts, *.js, src/**/*.tsx.",
      parameters: {
        type: "object",
        properties: {
          pattern: {
            type: "string",
            description: "Glob pattern to match files (e.g., '**/*.ts', 'src/**/*.tsx', '*.json').",
          },
          root: {
            type: "string",
            description: "Root directory to search from, relative to the project root. Defaults to project root.",
          },
        },
        required: ["pattern"],
      },
    },
  };

  public async execute(args: IToolArguments, logger: IAiLogger): Promise<string> {
    const pattern = typeof args.pattern === "string" ? args.pattern : undefined;
    if (!pattern || pattern.trim().length === 0) {
      return toolError({
        code: "MISSING_PARAMETER",
        message: "pattern is required.",
        parameter: "pattern",
        recoveryHint: "Provide a glob pattern like '**/*.ts' or 'src/**/*.tsx'.",
      });
    }

    const projectRoot = getProjectRoot(this.toolbox);
    if (!projectRoot) {
      return toolError({
        code: "OPERATION_NOT_ALLOWED",
        message: "No active project workspace is configured for file tools.",
        recoveryHint: "Run this tool during an active Agent work order.",
      });
    }

    let root: string;
    if (args.root !== undefined && typeof args.root === "string") {
      const resolved = resolveProjectPath(this.toolbox, args.root);
      if (typeof resolved === "string") return resolved;
      root = resolved.absolutePath;
    } else {
      root = path.resolve(projectRoot);
    }

    try {
      const matches = await this.globMatch(pattern, root, projectRoot);
      const limited = matches.slice(0, MAX_RESULTS);

      if (matches.length > MAX_RESULTS) {
        return [
          `Found ${matches.length} files matching "${pattern}" (showing first ${MAX_RESULTS}):`,
          "",
          ...limited,
          "",
          `... and ${matches.length - MAX_RESULTS} more files`,
        ].join("\n");
      }

      return [
        `Found ${matches.length} file(s) matching "${pattern}":`,
        "",
        ...matches,
      ].join("\n");
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      logger.error("failed to glob", { pattern, root, error: errorMessage });
      return toolError({
        code: "OPERATION_FAILED",
        message: `Failed to search: ${errorMessage}`,
      });
    }
  }

  private async globMatch(pattern: string, root: string, projectRoot: string): Promise<string[]> {
    const results: string[] = [];
    const parts = pattern.split("/");
    const globStarIndex = parts.indexOf("**");

    let isRecursive: boolean;
    let searchPattern: string;
    let resolvedRoot: string;

    if (globStarIndex !== -1) {
      isRecursive = true;
      const beforeParts = parts.slice(0, globStarIndex);
      const afterParts = parts.slice(globStarIndex + 1);

      if (beforeParts.length > 0) {
        const prefixPath = beforeParts.join("/");
        resolvedRoot = path.resolve(root, prefixPath);
        const rel = path.relative(projectRoot, resolvedRoot);
        if (rel.startsWith("..") || path.isAbsolute(rel)) {
          return results;
        }
      } else {
        resolvedRoot = root;
      }

      searchPattern = afterParts.length > 0 ? afterParts.join("/") : "*";
    } else {
      isRecursive = false;
      searchPattern = pattern;
      resolvedRoot = root;
    }

    const regexPattern = this.globToRegex(searchPattern);
    await this.recurseDir(resolvedRoot, regexPattern, isRecursive, results, 0, 20, projectRoot);
    return results.sort();
  }

  private globToRegex(pattern: string): RegExp {
    let regexStr = pattern
      .replace(/[.+^${}()|[\]\\]/g, "\\$&")
      .replace(/\*/g, ".*")
      .replace(/\?/g, ".");
    return new RegExp("^" + regexStr + "$");
  }

  private async recurseDir(
    dir: string,
    pattern: RegExp,
    recursive: boolean,
    results: string[],
    depth: number,
    maxDepth: number,
    projectRoot: string,
  ): Promise<void> {
    if (depth > maxDepth) return;

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

    for (const entry of entries) {
      const fullPath = path.join(dir, entry.name);
      const relativePath = path.relative(projectRoot, fullPath);

      if (entry.isFile()) {
        if (pattern.test(entry.name)) {
          results.push(relativePath);
        }
      } else if (entry.isDirectory()) {
        if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist" || entry.name === "build") {
          continue;
        }
        if (recursive) {
          await this.recurseDir(fullPath, pattern, recursive, results, depth + 1, maxDepth, projectRoot);
        } else if (pattern.test(entry.name)) {
          results.push(relativePath + "/");
        }
      }
    }
  }
}
