import { inferHttpMethod as sharedInferHttpMethod, inferPath as sharedInferPath, curedToEndpoints as sharedCuredToEndpoints } from "@specverse/types/spec-rules";
function generateFastifyRoutes(context) {
  const { controller, model, spec, implType } = context;
  if (!controller) {
    throw new Error("Controller is required in template context");
  }
  const modelName = model?.name || controller.modelReference;
  const controllerName = controller.name;
  const routeName = controllerName || `${modelName}Controller`;
  const isModelController = !!modelName;
  const handlerName = isModelController ? `${modelName}Controller` : controllerName;
  let imports;
  let endpoints = controller.endpoints;
  if (!endpoints || endpoints.length === 0) {
    if (controller.cured) {
      endpoints = curedToEndpoints(controller.cured, modelName);
    }
  }
  if (controller.actions) {
    if (!endpoints) endpoints = [];
    for (const [actionName, action] of Object.entries(controller.actions)) {
      const params = action.parameters || {};
      const hasIdParam = Object.keys(params).some((p) => p === "id" || p === `${modelName?.charAt(0).toLowerCase()}${modelName?.slice(1)}Id`);
      const explicitPath = action.path;
      const explicitMethod = action.method;
      const isExternal = !!(explicitPath && /^\/api\//.test(explicitPath));
      endpoints.push({
        operation: actionName,
        method: explicitMethod || "POST",
        path: explicitPath || (hasIdParam ? `/:id/${actionName}` : `/${actionName}`),
        external: isExternal,
        parameters: params,
        description: action.description || `Custom action: ${actionName}`
      });
    }
  }
  if (!endpoints || endpoints.length === 0) {
    console.warn(`Warning: Controller ${controllerName} has no endpoints. Generating empty routes file.`);
  }
  const internalEndpoints = endpoints?.filter((e) => !e.external) || [];
  const externalEndpoints = endpoints?.filter((e) => e.external) || [];
  const internalHandlers = internalEndpoints.map((endpoint) => generateRouteHandler(endpoint, modelName, handlerName, isModelController, implType, controllerName)).join("\n\n");
  const externalHandlers = externalEndpoints.map((endpoint) => generateRouteHandler(endpoint, modelName, handlerName, isModelController, implType, controllerName)).join("\n\n");
  const hasInternalHandlers = internalEndpoints.length > 0;
  const hasExternalHandlers = externalEndpoints.length > 0;
  const handlersText = internalHandlers + "\n" + externalHandlers;
  const usesFastifyRequest = /\bFastifyRequest\b/.test(handlersText);
  const usesFastifyReply = /\bFastifyReply\b/.test(handlersText);
  imports = generateImports(
    controller,
    modelName,
    handlerName,
    isModelController,
    implType,
    { usesFastifyRequest, usesFastifyReply }
  );
  const externalRoutesExport = hasExternalHandlers ? `
/**
 * Mount the external (root-prefix) routes for this controller.
 *
 * Called by the generated main.ts at root scope (no prefix) so action.path
 * declarations like '/api/v2/auth/register' land at exactly that URL,
 * bypassing the controller's '/api/<prefix>' prefix.
 */
export async function registerExternalRoutes(
  fastify: FastifyInstance,
  options: any
) {
  const handler = ${isModelController ? "options.controllers" : "options.services"}.${handlerName};

${externalHandlers.split("\n").map((line) => "  " + line).join("\n")}
}
` : "";
  const mainBody = hasInternalHandlers ? `  const handler = ${isModelController ? "options.controllers" : "options.services"}.${handlerName};

${internalHandlers.split("\n").map((line) => "  " + line).join("\n")}` : "  // No endpoints declared on this controller \u2014 empty routes plugin.";
  const mainFastifyParam = hasInternalHandlers ? "fastify" : "_fastify";
  const mainOptionsParam = hasInternalHandlers ? "options" : "_options";
  return `${imports}

/**
 * ${routeName} Routes
 * Generated from SpecVerse specification
 *
 * ${isModelController ? `Model: ${modelName}` : `Service: ${controllerName}`}
 * Operations: ${controller.endpoints?.map((e) => e.operation).join(", ") || "CURED"}
 */
export default async function ${routeName.replace("Controller", "")}Routes(
  ${mainFastifyParam}: FastifyInstance,
  ${mainOptionsParam}: any
) {
${mainBody}
}
${externalRoutesExport}`;
}
function generateImports(_controller, modelName, _handlerName, _isModelController, implType, usage = { usesFastifyRequest: true, usesFastifyReply: true }) {
  const fastifyTypes = ["FastifyInstance"];
  if (usage.usesFastifyRequest) fastifyTypes.push("FastifyRequest");
  if (usage.usesFastifyReply) fastifyTypes.push("FastifyReply");
  const imports = [
    `import { ${fastifyTypes.join(", ")} } from 'fastify';`
  ];
  if (implType?.technology?.validation === "zod" && modelName) {
    imports.push(`import { ${modelName}Schema } from '../validation/${modelName}.schema.js';`);
  }
  return imports.join("\n");
}
function generateRouteHandler(endpoint, modelName, handlerName, isModelController, implType, controllerName) {
  let operation = endpoint.operation || endpoint.name;
  if (!operation || operation === "custom") {
    const serviceOp = endpoint.serviceOperation?.type;
    if (serviceOp === "custom" && endpoint.path) {
      const pathParts = endpoint.path.split("/").filter((p) => p);
      const lastPart = pathParts[pathParts.length - 1];
      operation = lastPart.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
    } else if (!operation) {
      operation = serviceOp;
    }
  }
  if (!operation) {
    operation = inferOperationFromMethodAndPath(endpoint.method, endpoint.path);
  }
  if (!operation || operation === "unknown") {
    console.warn(`Warning: Could not determine operation for endpoint in ${controllerName || "controller"}. Endpoint data:`, {
      operation: endpoint.operation,
      name: endpoint.name,
      method: endpoint.method,
      path: endpoint.path,
      serviceOperation: endpoint.serviceOperation
    });
  }
  const method = endpoint.method?.toLowerCase() || inferHttpMethod(operation);
  const path = inferPath(operation, endpoint);
  const handler = generateHandlerBody(operation, modelName, handlerName, isModelController, implType, endpoint);
  let route = `// ${operation} ${modelName}
`;
  route += `fastify.${method}('${path}', {
`;
  if (implType?.technology?.validation === "zod") {
    route += `  schema: {
`;
    if (operation === "create" || operation === "update" || operation === "evolve") {
      route += `    body: ${modelName}Schema.${operation},
`;
    }
    if (operation === "retrieve" || operation === "update" || operation === "delete") {
      route += `    params: ${modelName}Schema.params,
`;
    }
    route += `    response: {
`;
    route += `      200: ${modelName}Schema.response
`;
    route += `    }
`;
    route += `  },
`;
  }
  const usesRequest = ["create", "retrieve", "update", "evolve", "delete", "validate"].includes(operation) || !["list"].includes(operation);
  const requestParam = usesRequest ? "request" : "_request";
  route += `  handler: async (${requestParam}: FastifyRequest, reply: FastifyReply) => {
`;
  route += handler.split("\n").map((line) => "    " + line).join("\n") + "\n";
  route += `  }
`;
  route += `});`;
  return route;
}
function generateHandlerBody(operation, modelName, handlerName, isModelController, implType, endpoint) {
  const rawLowerModel = modelName?.toLowerCase() || "item";
  const RESERVED_WORDS = /* @__PURE__ */ new Set(["import", "export", "default", "class", "function", "return", "delete", "new", "this", "switch", "case", "break", "continue", "for", "while", "do", "if", "else", "try", "catch", "finally", "throw", "typeof", "instanceof", "in", "of", "let", "const", "var", "void", "with", "yield", "async", "await", "enum", "implements", "interface", "package", "private", "protected", "public", "static", "super", "extends"]);
  const lowerModel = RESERVED_WORDS.has(rawLowerModel) ? `${rawLowerModel}Item` : rawLowerModel;
  const operationMap = {
    "custom": "create",
    // POST without ID -> create
    "findUnique": "retrieve",
    // GET with ID -> retrieve
    "findMany": "list",
    // GET without ID -> list
    "updateUnique": "update",
    // PUT with ID -> update
    "deleteUnique": "delete"
    // DELETE with ID -> delete
  };
  const mappedOperation = operationMap[operation] || operation;
  switch (mappedOperation) {
    case "create":
      return `try {
  // Phase 2 actor wiring \u2014 pulled from request.user (decorated by auth
  // middleware like @fastify/jwt). null when no auth wired; constraint
  // guards that reference actor.* paths fail-open (logged, treated as pass).
  const _actor = (request as any).user ?? null;
  const ${lowerModel} = await handler.create(request.body as any, _actor);
  return reply.status(201).send(${lowerModel});
} catch (error) {
  return reply.status(400).send({
    error: 'Failed to create ${lowerModel}',
    message: error instanceof Error ? error.message : String(error)
  });
}`;
    case "retrieve":
      return `try {
  const { id } = request.params as { id: string };
  const ${lowerModel} = await handler.retrieve(id);

  if (!${lowerModel}) {
    return reply.status(404).send({ error: '${modelName} not found' });
  }

  return reply.send(${lowerModel});
} catch (error) {
  return reply.status(500).send({
    error: 'Failed to retrieve ${lowerModel}',
    message: error instanceof Error ? error.message : String(error)
  });
}`;
    case "update":
    case "evolve":
      return `try {
  const { id } = request.params as { id: string };
  const _actor = (request as any).user ?? null;
  const ${lowerModel} = await handler.${operation}(id, request.body as any, _actor);
  return reply.send(${lowerModel});
} catch (error) {
  return reply.status(400).send({
    error: 'Failed to ${operation} ${lowerModel}',
    message: error instanceof Error ? error.message : String(error)
  });
}`;
    case "delete":
      return `try {
  const { id } = request.params as { id: string };
  const _actor = (request as any).user ?? null;
  await handler.delete(id, _actor);
  return reply.status(204).send();
} catch (error) {
  // Prisma's P2003 is the foreign-key-constraint-violated error. When
  // a ${lowerModel} has children (hasMany relationships not declared
  // with 'cascade'), SQLite / Postgres refuse the delete and we land
  // here. Return a 409 Conflict with a specific hint rather than a 500
  // with a raw Prisma dump \u2014 the frontend shows the message verbatim.
  const msg = error instanceof Error ? error.message : String(error);
  if (msg.includes('P2003') || msg.toLowerCase().includes('foreign key constraint')) {
    return reply.status(409).send({
      error: 'Cannot delete ${lowerModel}',
      message: \`This ${lowerModel} has related records. Delete those first, or declare the relationship with 'cascade' in your spec to delete them automatically.\`
    });
  }
  return reply.status(500).send({
    error: 'Failed to delete ${lowerModel}',
    message: msg
  });
}`;
    case "list":
      return `try {
  const ${lowerModel}s = await handler.retrieveAll();
  return reply.send(${lowerModel}s);
} catch (error) {
  return reply.status(500).send({
    error: 'Failed to list ${lowerModel}s',
    message: error instanceof Error ? error.message : String(error)
  });
}`;
    case "validate":
      return `try {
  const { data, operation: op } = request.body as { data: any; operation: string };
  const _actor = (request as any).user ?? null;
  // Slice 15b \u2014 validate() is async (awaits guard ctx subqueries).
  const result = await handler.validate(data, { operation: op }, _actor);
  return reply.send(result);
} catch (error) {
  return reply.status(400).send({
    error: 'Validation failed',
    message: error instanceof Error ? error.message : String(error)
  });
}`;
    default: {
      const path = endpoint?.path || "";
      const urlParamNames = /* @__PURE__ */ new Set();
      const urlParamRegex = /:([A-Za-z_][\w]*)/g;
      let urlMatch;
      while ((urlMatch = urlParamRegex.exec(path)) !== null) {
        urlParamNames.add(urlMatch[1]);
      }
      const params = endpoint?.parameters || {};
      const validations = [];
      for (const [pName, pDef] of Object.entries(params)) {
        if (urlParamNames.has(pName)) continue;
        const required = typeof pDef === "string" ? pDef.includes("required") : pDef?.required;
        const typeStr = typeof pDef === "string" ? pDef.split(" ")[0] : pDef?.type || "String";
        if (required) {
          validations.push(`  if (args.${pName} === undefined || args.${pName} === null) errors.push({ field: '${pName}', message: '${pName} is required' });`);
        }
        if (typeStr === "UUID" || typeStr === "String" || typeStr === "Email") {
          validations.push(`  if (args.${pName} !== undefined && typeof args.${pName} !== 'string') errors.push({ field: '${pName}', message: '${pName} must be a string' });`);
        } else if (typeStr === "Integer" || typeStr === "Number" || typeStr === "Float") {
          validations.push(`  if (args.${pName} !== undefined && typeof args.${pName} !== 'number') errors.push({ field: '${pName}', message: '${pName} must be a number' });`);
        } else if (typeStr === "Boolean") {
          validations.push(`  if (args.${pName} !== undefined && typeof args.${pName} !== 'boolean') errors.push({ field: '${pName}', message: '${pName} must be a boolean' });`);
        }
      }
      const validationBlock = validations.length > 0 ? `  const errors: Array<{ field: string; message: string }> = [];
${validations.join("\n")}
  if (errors.length > 0) return reply.status(400).send({ error: 'Validation failed', details: errors });` : "";
      return `try {
  const params = (request.params || {}) as Record<string, any>;
  const body = (request.body || {}) as Record<string, any>;
  const args = { ...params, ...body };
  const _actor = (request as any).user ?? null;
${validationBlock}
  const result = await handler.${operation}(args, _actor);
  return reply.send(result || { success: true });
} catch (error) {
  const msg = error instanceof Error ? error.message : String(error);
  // 501 for TODO-stubbed action bodies (#43F); 400 for real failures.
  if (msg.includes('is not implemented')) {
    return reply.status(501).send({ error: 'Not Implemented', message: msg });
  }
  return reply.status(400).send({
    error: 'Failed to execute ${operation}',
    message: msg
  });
}`;
    }
  }
}
function inferOperationFromMethodAndPath(method, path) {
  if (!method || !path) return "unknown";
  const methodLower = method.toLowerCase();
  const hasIdParam = path.includes(":id");
  if (methodLower === "post" && !hasIdParam) return "create";
  if (methodLower === "get" && hasIdParam) return "retrieve";
  if (methodLower === "get" && !hasIdParam) return "list";
  if (methodLower === "put" && hasIdParam) return "update";
  if (methodLower === "patch" && hasIdParam) return "evolve";
  if (methodLower === "delete" && hasIdParam) return "delete";
  return "unknown";
}
function inferHttpMethod(operation) {
  return sharedInferHttpMethod(operation);
}
function inferPath(operation, endpoint) {
  if (endpoint?.path) {
    return endpoint.path;
  }
  return sharedInferPath(operation);
}
function curedToEndpoints(cured, _modelName) {
  return sharedCuredToEndpoints(cured);
}
export {
  generateFastifyRoutes as default
};
