import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import path from "path";
import {
  buildAgentApplyReport,
  buildAgentContext,
  buildAgentPlan,
  buildAgentRepairReport,
  buildAgentSyncReport,
  buildAgentVerifyReport,
  writeAgentApplyReport,
  writeAgentManifest,
  writeAgentPlan,
  writeAgentRepairReport,
  writeAgentVerifyReport,
} from "@mandujs/core/agent";

interface AgentContextInput {
  cwd?: unknown;
  includeDiagnose?: unknown;
  includeGit?: unknown;
  writeManifest?: unknown;
}

interface AgentVerifyInput {
  cwd?: unknown;
  changedOnly?: unknown;
  includeDiagnose?: unknown;
  includeGit?: unknown;
  includeGuard?: unknown;
  includeContract?: unknown;
  staged?: unknown;
  base?: unknown;
  writeReport?: unknown;
}

interface AgentPlanInput {
  cwd?: unknown;
  intent?: unknown;
  writePlan?: unknown;
}

interface AgentApplyInput {
  cwd?: unknown;
  from?: unknown;
  dryRun?: unknown;
  writeReport?: unknown;
}

interface AgentSyncInput {
  cwd?: unknown;
  target?: unknown;
  dryRun?: unknown;
}

interface AgentRepairInput {
  cwd?: unknown;
  from?: unknown;
  apply?: unknown;
  writeReport?: unknown;
}

function boolOrDefault(value: unknown, fallback: boolean): boolean {
  if (value === undefined) return fallback;
  if (typeof value === "boolean") return value;
  return fallback;
}

function resolveCwd(projectRoot: string, value: unknown): string {
  const root = path.resolve(projectRoot);
  const resolved =
    typeof value === "string" && value.length > 0
      ? path.resolve(root, value)
      : root;
  if (resolved !== root && !resolved.startsWith(root + path.sep)) {
    throw new Error("cwd must stay inside the MCP project root");
  }
  return resolved;
}

async function runAgentContext(
  projectRoot: string,
  input: AgentContextInput,
) {
  const cwd = resolveCwd(projectRoot, input.cwd);
  const context = await buildAgentContext(cwd, {
    includeDiagnose: boolOrDefault(input.includeDiagnose, true),
    includeGit: boolOrDefault(input.includeGit, true),
  });

  if (boolOrDefault(input.writeManifest, false)) {
    const { path, manifest } = await writeAgentManifest(cwd, context);
    return {
      ...context,
      manifest: {
        path,
        written: true,
        schemaVersion: manifest.schemaVersion,
      },
    };
  }

  return context;
}

async function runAgentPlan(
  projectRoot: string,
  input: AgentPlanInput,
) {
  const cwd = resolveCwd(projectRoot, input.cwd);
  const intent = typeof input.intent === "string" ? input.intent.trim() : "";
  if (!intent) {
    throw new Error("intent is required for mandu.agent.plan");
  }

  const plan = buildAgentPlan({ intent });

  if (boolOrDefault(input.writePlan, false)) {
    const { path, plan: written } = await writeAgentPlan(cwd, plan);
    return {
      ...written,
      plan: {
        path,
        written: true,
        schemaVersion: written.schemaVersion,
      },
    };
  }

  return plan;
}

async function runAgentApply(
  projectRoot: string,
  input: AgentApplyInput,
) {
  const cwd = resolveCwd(projectRoot, input.cwd);
  const report = await buildAgentApplyReport(cwd, {
    from: typeof input.from === "string" && input.from.length > 0 ? input.from : undefined,
    dryRun: boolOrDefault(input.dryRun, true),
  });

  if (boolOrDefault(input.writeReport, false)) {
    const { path, report: written } = await writeAgentApplyReport(cwd, report);
    return {
      ...written,
      report: {
        path,
        written: true,
        schemaVersion: written.schemaVersion,
      },
    };
  }

  return report;
}

async function runAgentVerify(
  projectRoot: string,
  input: AgentVerifyInput,
) {
  const cwd = resolveCwd(projectRoot, input.cwd);
  const report = await buildAgentVerifyReport(cwd, {
    changedOnly: boolOrDefault(input.changedOnly, true),
    includeDiagnose: boolOrDefault(input.includeDiagnose, true),
    includeGit: boolOrDefault(input.includeGit, true),
    includeGuard: boolOrDefault(input.includeGuard, true),
    includeContract: boolOrDefault(input.includeContract, true),
    staged: boolOrDefault(input.staged, false),
    base: typeof input.base === "string" && input.base.length > 0 ? input.base : undefined,
  });

  if (boolOrDefault(input.writeReport, false)) {
    const { path, report: written } = await writeAgentVerifyReport(cwd, report);
    return {
      ...written,
      report: {
        path,
        written: true,
        schemaVersion: written.schemaVersion,
      },
    };
  }

  return report;
}

async function runAgentRepair(
  projectRoot: string,
  input: AgentRepairInput,
) {
  const cwd = resolveCwd(projectRoot, input.cwd);
  const report = await buildAgentRepairReport(cwd, {
    from: typeof input.from === "string" && input.from.length > 0 ? input.from : undefined,
    apply: boolOrDefault(input.apply, false),
  });

  if (boolOrDefault(input.writeReport, false)) {
    const { path, report: written } = await writeAgentRepairReport(cwd, report);
    return {
      ...written,
      report: {
        path,
        written: true,
        schemaVersion: written.schemaVersion,
      },
    };
  }

  return report;
}

async function runAgentSync(
  projectRoot: string,
  input: AgentSyncInput,
) {
  const cwd = resolveCwd(projectRoot, input.cwd);
  const target =
    input.target === "codex" ||
    input.target === "claude" ||
    input.target === "gemini" ||
    input.target === "all"
      ? input.target
      : "all";
  return buildAgentSyncReport(cwd, {
    target,
    dryRun: boolOrDefault(input.dryRun, false),
  });
}

export const agentToolDefinitions: Tool[] = [
  {
    name: "mandu.agent.context",
    description:
      "Official agent-core entry point. Returns one structured project context for Codex/Claude/Gemini: project metadata, routes/APIs, partials/islands, slots/contracts, guard summary, diagnostics, git state, and the canonical context -> plan -> apply -> verify -> repair workflow.",
    annotations: {
      readOnlyHint: true,
    },
    inputSchema: {
      type: "object",
      properties: {
        cwd: {
          type: "string",
          description: "Project directory to inspect. Defaults to the MCP project root.",
        },
        includeDiagnose: {
          type: "boolean",
          description: "Include diagnose summary and normalized diagnostics. Defaults to true.",
        },
        includeGit: {
          type: "boolean",
          description: "Include git branch and changed-file summary. Defaults to true.",
        },
        writeManifest: {
          type: "boolean",
          description:
            "Write .mandu/agent-manifest.json while building context. Defaults to false.",
        },
      },
      required: [],
    },
  },
  {
    name: "mandu.agent.plan",
    description:
      "Official agent-core planning entry point. Converts a natural-language Mandu task into a conservative domain plan with files to inspect, files likely to create, preferred MCP/domain tools, risks, and verification commands.",
    annotations: {
      readOnlyHint: true,
    },
    inputSchema: {
      type: "object",
      properties: {
        cwd: {
          type: "string",
          description: "Project directory to plan against. Defaults to the MCP project root.",
        },
        intent: {
          type: "string",
          description: "Natural-language task request, for example: add authenticated dashboard.",
        },
        writePlan: {
          type: "boolean",
          description: "Write .mandu/agent-plan.json while planning. Defaults to false.",
        },
      },
      required: ["intent"],
    },
  },
  {
    name: "mandu.agent.apply",
    description:
      "Official agent-core apply preview. Reads an agent plan and returns ordered intent-level actions. This initial implementation is dry-run only and does not mutate project files.",
    annotations: {
      readOnlyHint: true,
    },
    inputSchema: {
      type: "object",
      properties: {
        cwd: {
          type: "string",
          description: "Project directory. Defaults to the MCP project root.",
        },
        from: {
          type: "string",
          description: "Plan path relative to cwd. Defaults to .mandu/agent-plan.json.",
        },
        dryRun: {
          type: "boolean",
          description: "Return a dry-run action report. Defaults to true.",
        },
        writeReport: {
          type: "boolean",
          description: "Write .mandu/agent-apply.json. Defaults to false.",
        },
      },
      required: [],
    },
  },
  {
    name: "mandu.agent.verify",
    description:
      "Official agent-core verification entry point. Returns one structured report that combines changed files, diagnose, route manifest, guard, contract consistency, normalized diagnostics, and suggested follow-up commands.",
    annotations: {
      readOnlyHint: true,
    },
    inputSchema: {
      type: "object",
      properties: {
        cwd: {
          type: "string",
          description: "Project directory to verify. Defaults to the MCP project root.",
        },
        changedOnly: {
          type: "boolean",
          description: "Filter guard/contract findings to changed files when git data exists. Defaults to true.",
        },
        includeDiagnose: {
          type: "boolean",
          description: "Run diagnose and include normalized diagnose diagnostics. Defaults to true.",
        },
        includeGit: {
          type: "boolean",
          description: "Collect git changed files. Defaults to true.",
        },
        includeGuard: {
          type: "boolean",
          description: "Run architecture guard if the routes manifest is available. Defaults to true.",
        },
        includeContract: {
          type: "boolean",
          description: "Run contract/slot consistency checks if the routes manifest is available. Defaults to true.",
        },
        staged: {
          type: "boolean",
          description: "Use staged changes only. Defaults to false.",
        },
        base: {
          type: "string",
          description: "Optional git base ref for changed-file detection.",
        },
        writeReport: {
          type: "boolean",
          description: "Write .mandu/agent-verify.json while verifying. Defaults to false.",
        },
      },
      required: [],
    },
  },
  {
    name: "mandu.agent.repair",
    description:
      "Official agent-core repair entry point. Reads an agent verify report and returns structured next actions. It does not execute shell commands; apply mode only handles safe typed patch candidates.",
    annotations: {
      readOnlyHint: false,
    },
    inputSchema: {
      type: "object",
      properties: {
        cwd: {
          type: "string",
          description: "Project directory. Defaults to the MCP project root.",
        },
        from: {
          type: "string",
          description: "Verify report path relative to cwd. Defaults to .mandu/agent-verify.json.",
        },
        apply: {
          type: "boolean",
          description: "Apply safe typed patch candidates only. Defaults to false.",
        },
        writeReport: {
          type: "boolean",
          description: "Write .mandu/agent-repair.json. Defaults to false.",
        },
      },
      required: [],
    },
  },
  {
    name: "mandu.agent.sync",
    description:
      "Official agent sync helper. Writes or previews Codex, Claude Code, and Gemini CLI workflow artifacts under .mandu/agent-sync so each agent follows the same Mandu agent loop and MCP profile guidance.",
    annotations: {
      readOnlyHint: false,
    },
    inputSchema: {
      type: "object",
      properties: {
        cwd: {
          type: "string",
          description: "Project directory. Defaults to the MCP project root.",
        },
        target: {
          type: "string",
          enum: ["codex", "claude", "gemini", "all"],
          description: "Agent target to sync. Defaults to all.",
        },
        dryRun: {
          type: "boolean",
          description: "Preview files without writing. Defaults to false.",
        },
      },
      required: [],
    },
  },
];

export function agentTools(projectRoot: string) {
  const handlers: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
    "mandu.agent.context": async (args) =>
      runAgentContext(projectRoot, args as AgentContextInput),
    "mandu.agent.plan": async (args) =>
      runAgentPlan(projectRoot, args as AgentPlanInput),
    "mandu.agent.apply": async (args) =>
      runAgentApply(projectRoot, args as AgentApplyInput),
    "mandu.agent.verify": async (args) =>
      runAgentVerify(projectRoot, args as AgentVerifyInput),
    "mandu.agent.repair": async (args) =>
      runAgentRepair(projectRoot, args as AgentRepairInput),
    "mandu.agent.sync": async (args) =>
      runAgentSync(projectRoot, args as AgentSyncInput),
  };
  return handlers;
}
