import { existsSync, readdirSync, statSync } from "node:fs";
import { join } from "node:path";
import { Type } from "@sinclair/typebox";
import type { AgentTool } from "@mariozechner/pi-ai";
import { resolveToCwd, ensureWorkspacePath } from "./path-utils.js";
import { DEFAULT_MAX_BYTES, formatSize, truncateHead } from "../../util/truncate.js";

const DEFAULT_LIMIT = 500;

const LsSchema = Type.Object({
  path: Type.Optional(Type.String({ description: "Directory to list (default: current directory)" })),
  limit: Type.Optional(Type.Number({ description: "Maximum number of entries to return (default: 500)" })),
});

export const createLsTool = (workspacePath: string): AgentTool => ({
  name: "ls",
  label: "List Directory",
  description: `List directory contents. Returns up to ${DEFAULT_LIMIT} entries and ${DEFAULT_MAX_BYTES / 1024} KB of output.`,
  parameters: LsSchema as any,
  execute: async (_toolCallId: string, params: any) => {
    const resolved = resolveToCwd(params.path || ".", workspacePath);
    ensureWorkspacePath(workspacePath, resolved);

    if (!existsSync(resolved)) {
      throw new Error(`Path does not exist: ${params.path || "."}`);
    }

    const stats = statSync(resolved);
    if (!stats.isDirectory()) {
      throw new Error(`Not a directory: ${params.path || "."}`);
    }

    const entries = readdirSync(resolved);
    entries.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }));

    const effectiveLimit = params.limit ?? DEFAULT_LIMIT;
    const limited = entries.slice(0, effectiveLimit);
    const entryLimitReached = entries.length > effectiveLimit;

    const lines = limited.map((entry) => {
      const entryPath = join(resolved, entry);
      try {
        const entryStat = statSync(entryPath);
        return entryStat.isDirectory() ? `${entry}/` : entry;
      } catch {
        return entry;
      }
    });

    if (lines.length === 0) {
      return {
        content: [{ type: "text", text: "(empty directory)" }],
        details: {},
      };
    }

    const rawOutput = lines.join("\n");
    const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });

    const notices: string[] = [];
    if (entryLimitReached) {
      notices.push(`Entry limit reached: showing ${effectiveLimit} of ${entries.length} entries.`);
    }
    if (truncation.truncated) {
      notices.push(
        `Output truncated: showing ${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}.`,
      );
    }

    let output = truncation.content;
    if (notices.length > 0) {
      output += `\n\n[${notices.join(" ")}]`;
    }

    return {
      content: [{ type: "text", text: output }],
      details: {
        ...(truncation.truncated ? { truncation } : {}),
        ...(entryLimitReached ? { entryLimitReached: true } : {}),
      },
    };
  },
});
