import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import { generateManifest, loadManifest, parseResourceSchema, generateResourceArtifacts } from "@mandujs/core";
import { generateRoutes, type GeneratedMap } from "@mandujs/core/generator";
import { GENERATED_RELATIVE_PATHS } from "@mandujs/core/paths";
import { getProjectPaths, readJsonFile } from "../utils/project.js";
import path from "path";
import fs from "fs/promises";

export const generateToolDefinitions: Tool[] = [
  {
    name: "mandu.generate",
    description:
      "Generate all framework artifacts (server handlers, web components, resources) from the manifest. " +
      "Run after adding routes or modifying slots/contracts. Use dryRun=true to preview.",
    annotations: {
      destructiveHint: true,
      readOnlyHint: false,
      idempotentHint: true,
    },
    inputSchema: {
      type: "object",
      properties: {
        dryRun: {
          type: "boolean",
          description: "Preview what would be generated without writing files (default: false)",
        },
        resources: {
          type: "boolean",
          description: "Include resource artifact generation from spec/resources/ (default: true)",
        },
      },
      required: [],
    },
  },
  {
    name: "mandu.generate.status",
    description:
      "Show the current state of all generated artifacts from .mandu/generated.map.json.",
    annotations: {
      readOnlyHint: true,
    },
    inputSchema: {
      type: "object",
      properties: {},
      required: [],
    },
  },
];

export function generateTools(projectRoot: string) {
  const paths = getProjectPaths(projectRoot);

  const handlers: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
    "mandu.generate": async (args: Record<string, unknown>) => {
      const { dryRun, resources = true } = args as { dryRun?: boolean; resources?: boolean };

      // Regenerate manifest from FS Routes first
      const _fsResult = await generateManifest(projectRoot);

      // Load the freshly generated manifest
      const manifestResult = await loadManifest(paths.manifestPath);
      if (!manifestResult.success || !manifestResult.data) {
        return { error: manifestResult.errors };
      }

      if (dryRun) {
        // Dry run - just show what would be generated
        const routes = manifestResult.data.routes;
        const wouldCreate: string[] = [];
        const wouldSkip: string[] = [];

        for (const route of routes) {
          // Server handler
          wouldCreate.push(`${GENERATED_RELATIVE_PATHS.serverRoutes}/${route.id}.route.ts`);

          // Page component (for page kind)
          if (route.kind === "page") {
            wouldCreate.push(`${GENERATED_RELATIVE_PATHS.webRoutes}/${route.id}.route.tsx`);
          }

          // Slot file (only if not exists)
          if (route.slotModule) {
            const slotFile = Bun.file(`${projectRoot}/${route.slotModule}`);
            if (await slotFile.exists()) {
              wouldSkip.push(route.slotModule);
            } else {
              wouldCreate.push(route.slotModule);
            }
          }
        }

        return {
          dryRun: true,
          wouldCreate,
          wouldSkip,
          routeCount: routes.length,
        };
      }

      // Actually generate routes
      const result = await generateRoutes(manifestResult.data, projectRoot);

      // Generate resources if enabled
      let resourceResults: {
        created: string[];
        skipped: string[];
        errors: string[];
      } = {
        created: [],
        skipped: [],
        errors: [],
      };

      if (resources) {
        try {
          const resourcesDir = path.join(projectRoot, "spec", "resources");
          const entries = await fs.readdir(resourcesDir, { withFileTypes: true });
          const resourceDirs = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);

          for (const resourceName of resourceDirs) {
            const schemaPath = path.join(resourcesDir, resourceName, "schema.ts");
            try {
              const parsed = await parseResourceSchema(schemaPath);
              const resourceResult = await generateResourceArtifacts(parsed, {
                rootDir: projectRoot,
                force: false,
              });

              resourceResults.created.push(...resourceResult.created);
              resourceResults.skipped.push(...resourceResult.skipped);
              resourceResults.errors.push(...resourceResult.errors);
            } catch (err) {
              resourceResults.errors.push(`Failed to generate resource '${resourceName}': ${err instanceof Error ? err.message : String(err)}`);
            }
          }
        } catch {
          // spec/resources/ doesn't exist or is empty - not an error
        }
      }

      return {
        success: result.success && resourceResults.errors.length === 0,
        routes: {
          created: result.created,
          deleted: result.deleted,
          skipped: result.skipped,
          errors: result.errors,
        },
        resources: resources
          ? {
              created: resourceResults.created,
              skipped: resourceResults.skipped,
              errors: resourceResults.errors,
            }
          : undefined,
        summary: {
          routesCreated: result.created.length,
          routesDeleted: result.deleted.length,
          routesSkipped: result.skipped.length,
          resourcesCreated: resourceResults.created.length,
          resourcesSkipped: resourceResults.skipped.length,
          totalErrors: result.errors.length + resourceResults.errors.length,
        },
      };
    },

    "mandu.generate.status": async () => {
      // Read generated map
      const generatedMap = await readJsonFile<GeneratedMap>(paths.generatedMapPath);

      if (!generatedMap) {
        return {
          hasGeneratedFiles: false,
          message: "No generated.map.json found. Run mandu.generate first.",
        };
      }

      const fileCount = Object.keys(generatedMap.files).length;
      const routeIds = Object.values(generatedMap.files).map((f) => f.routeId);
      const uniqueRoutes = [...new Set(routeIds)];

      return {
        hasGeneratedFiles: true,
        version: generatedMap.version,
        generatedAt: generatedMap.generatedAt,
        specSource: generatedMap.specSource,
        fileCount,
        routeCount: uniqueRoutes.length,
        files: Object.entries(generatedMap.files).map(([path, info]) => ({
          path,
          routeId: info.routeId,
          kind: info.kind,
          hasSlot: !!info.slotMapping,
        })),
      };
    },
  };

  // Backward-compatible aliases
  handlers["mandu_generate"] = handlers["mandu.generate"];
  handlers["mandu_generate_status"] = handlers["mandu.generate.status"];

  return handlers;
}
