import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  type Tool
} from "@modelcontextprotocol/sdk/types.js";
import { loadConfig } from "./config.js";
import {
  grepFilesTool,
  listDirTool,
  makeToolError,
  readFileTool,
  readManyTool,
  statTool
} from "./fs-tools.js";

const SERVER_NAME = "projectfs-mcp-server";
const SERVER_VERSION = "0.1.0";

const TOOL_ANNOTATIONS = {
  title: "Project Filesystem (Read-Only)",
  readOnlyHint: true,
  destructiveHint: false,
  idempotentHint: true,
  openWorldHint: false
} as const;

const TOOLS: Tool[] = [
  {
    name: "read_file",
    description: "Reads a text file inside allowedRoots. Supports 1-based line slicing and byte caps.",
    annotations: TOOL_ANNOTATIONS,
    inputSchema: {
      type: "object",
      properties: {
        path: { type: "string", description: "Absolute or allowed-root-relative path to the file." },
        project_path: {
          type: "string",
          description: "Optional project root used to load a local .env whose PROJECTFS_* overrides win for this call."
        },
        startLine: { type: "integer", description: "Optional 1-based inclusive start line." },
        endLine: { type: "integer", description: "Optional 1-based inclusive end line." },
        maxBytes: { type: "integer", description: "Optional per-call byte cap; cannot exceed config maxFileBytes." }
      },
      required: ["path"]
    }
  },
  {
    name: "list_dir",
    description: "Lists accessible filesystem entries under an allowed directory, with optional depth and glob filters.",
    annotations: TOOL_ANNOTATIONS,
    inputSchema: {
      type: "object",
      properties: {
        path: { type: "string", description: "Absolute or allowed-root-relative directory path." },
        project_path: {
          type: "string",
          description: "Optional project root used to load a local .env whose PROJECTFS_* overrides win for this call."
        },
        depth: { type: "integer", description: "Optional recursion depth, capped by config maxDepth." },
        include: {
          type: "array",
          description: "Optional include globs matched against relative path and basename.",
          items: { type: "string" }
        },
        exclude: {
          type: "array",
          description: "Optional exclude globs matched against relative path and basename.",
          items: { type: "string" }
        },
        includeHidden: { type: "boolean", description: "When true, include dotfiles and dot-directories." }
      },
      required: ["path"]
    }
  },
  {
    name: "grep_files",
    description: "Searches literal text inside accessible files under a directory using Node filesystem APIs only.",
    annotations: TOOL_ANNOTATIONS,
    inputSchema: {
      type: "object",
      properties: {
        root: { type: "string", description: "Absolute or allowed-root-relative directory path." },
        project_path: {
          type: "string",
          description: "Optional project root used to load a local .env whose PROJECTFS_* overrides win for this call."
        },
        pattern: { type: "string", description: "Literal text pattern to search for. Regex is not supported." },
        include: {
          type: "array",
          description: "Optional include globs matched against relative path and basename.",
          items: { type: "string" }
        },
        exclude: {
          type: "array",
          description: "Optional exclude globs matched against relative path and basename.",
          items: { type: "string" }
        },
        caseSensitive: { type: "boolean", description: "When true, match with case sensitivity." },
        maxResults: { type: "integer", description: "Optional per-call match cap, capped by config maxSearchResults." }
      },
      required: ["root", "pattern"]
    }
  },
  {
    name: "stat",
    description: "Returns metadata for an accessible file or directory.",
    annotations: TOOL_ANNOTATIONS,
    inputSchema: {
      type: "object",
      properties: {
        path: { type: "string", description: "Absolute or allowed-root-relative path." },
        project_path: {
          type: "string",
          description: "Optional project root used to load a local .env whose PROJECTFS_* overrides win for this call."
        }
      },
      required: ["path"]
    }
  },
  {
    name: "read_many",
    description: "Reads multiple files with per-file success or error reporting. Paths must stay inside allowedRoots.",
    annotations: TOOL_ANNOTATIONS,
    inputSchema: {
      type: "object",
      properties: {
        paths: {
          type: "array",
          description: "List of absolute or allowed-root-relative file paths.",
          items: { type: "string" }
        },
        project_path: {
          type: "string",
          description: "Optional project root used to load a local .env whose PROJECTFS_* overrides win for this call."
        },
        maxBytesPerFile: { type: "integer", description: "Optional per-file byte cap, capped by config maxFileBytes." }
      },
      required: ["paths"]
    }
  }
];

async function main() {
  const startupConfig = loadConfig();

  const server = new Server(
    {
      name: SERVER_NAME,
      version: SERVER_VERSION
    },
    {
      capabilities: {
        tools: {}
      }
    }
  );

  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));

  server.setRequestHandler(CallToolRequestSchema, async (request) => {
    const toolName = request.params.name;
    const args = (request.params.arguments || {}) as Record<string, unknown>;
    const projectPath = typeof args.project_path === "string" ? args.project_path : undefined;
    const config = loadConfig(process.env, { projectPath });

    try {
      if (toolName === "read_file") {
        return readFileTool(config, {
          path: String(args.path ?? ""),
          startLine: args.startLine as number | undefined,
          endLine: args.endLine as number | undefined,
          maxBytes: args.maxBytes as number | undefined
        });
      }

      if (toolName === "list_dir") {
        return listDirTool(config, {
          path: String(args.path ?? ""),
          depth: args.depth as number | undefined,
          include: args.include as string[] | undefined,
          exclude: args.exclude as string[] | undefined,
          includeHidden: args.includeHidden === true
        });
      }

      if (toolName === "grep_files") {
        return grepFilesTool(config, {
          root: String(args.root ?? ""),
          pattern: String(args.pattern ?? ""),
          include: args.include as string[] | undefined,
          exclude: args.exclude as string[] | undefined,
          caseSensitive: args.caseSensitive === true,
          maxResults: args.maxResults as number | undefined
        });
      }

      if (toolName === "stat") {
        return statTool(config, { path: String(args.path ?? "") });
      }

      if (toolName === "read_many") {
        return readManyTool(config, {
          paths: (args.paths as string[] | undefined) || [],
          maxBytesPerFile: args.maxBytesPerFile as number | undefined
        });
      }

      throw new Error(`Unknown tool: ${toolName}`);
    } catch (error) {
      return makeToolError(error, {
        tool: toolName
      });
    }
  });

  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error(
    `${SERVER_NAME} running on stdio with ${startupConfig.allowedRoots.length} allowed root(s). Restart required after config/code changes.`
  );
}

main().catch((error) => {
  console.error("Fatal error in projectfs-node:", error);
  process.exit(1);
});
