import { buildTransitionMap, isAutoField } from "@specverse/types/spec-rules";
import { generateBehaviorWithHelpers } from "./behavior-generator.js";
function generatePrismaController(context) {
  const { controller, model, spec, models: allModels } = context;
  if (!controller) {
    throw new Error("Controller is required in template context");
  }
  if (!model) {
    throw new Error("Model is required for controller generation");
  }
  const controllerName = controller.name;
  const modelName = model.name;
  const rawModelVar = modelName.charAt(0).toLowerCase() + modelName.slice(1);
  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 modelVar = RESERVED_WORDS.has(rawModelVar) ? `${rawModelVar}Item` : rawModelVar;
  const prismaDelegate = RESERVED_WORDS.has(rawModelVar) ? `prisma['${rawModelVar}']` : `prisma.${rawModelVar}`;
  const curedOps = controller.cured || {};
  const idAttr = (Array.isArray(model.attributes) ? model.attributes : Object.values(model.attributes || {})).find((a) => a.name === "id");
  const idType = idAttr?.type || "UUID";
  const needsIntParse = idType === "Integer" || idType === "Int" || idType === "Number";
  const customActions = generateCustomActions(controller, modelName, modelVar);
  return `/**
 * ${controllerName}
 * Model-specific business logic for ${modelName}
 * ${controller.description || ""}
 */

import { PrismaClient } from '@prisma/client';
${hasEventPublishing(curedOps, controller) ? `import { eventBus } from '../events/eventBus.js';` : ""}
${customActions.needsAiBehaviors ? `import * as aiBehaviors from '../behaviors/${modelName}Controller.ai.js';` : ""}

const prisma = new PrismaClient();

/** Parse ID from string to the correct type for this model */
function parseId(id: string): ${needsIntParse ? "number" : "string"} {
  ${needsIntParse ? "return parseInt(id, 10);" : "return id;"}
}

/**
 * ${controllerName} class
 */
export class ${controllerName} {
  ${generateValidateMethod(model, modelName)}
  ${curedOps.create ? generateCreateMethod(model, modelName, modelVar, prismaDelegate, controller, allModels) : ""}
  ${curedOps.retrieve ? generateRetrieveMethod(model, modelName, modelVar, prismaDelegate) : ""}
  ${curedOps.update ? generateUpdateMethod(model, modelName, modelVar, prismaDelegate, controller, allModels) : ""}
  ${curedOps.evolve ? generateEvolveMethod(model, modelName, modelVar, prismaDelegate, controller) : ""}
  ${curedOps.delete ? generateDeleteMethod(model, modelName, modelVar, prismaDelegate, controller) : ""}
  ${customActions.code}
}

// Export singleton instance
export const ${modelVar}Controller = new ${controllerName}();
export default ${modelVar}Controller;
`;
}
function generateValidateMethod(model, modelName) {
  return `
  /**
   * Validate ${modelName} data
   * Unified validation method for all operations
   */
  public validate(
    _data: any,
    _context: { operation: 'create' | 'update' | 'evolve' }
  ): { valid: boolean; errors: string[] } {
    const errors: string[] = [];

    ${generateValidationLogic(model, "_data", "_context")}

    return {
      valid: errors.length === 0,
      errors
    };
  }
`;
}
function generateValidationLogic(model, dataParam = "_data", contextParam = "_context") {
  if (!model.attributes) return "// No validation rules defined";
  const validations = [];
  const attrList = Array.isArray(model.attributes) ? model.attributes.map((a) => [a.name, a]) : Object.entries(model.attributes);
  attrList.forEach(([name, attr]) => {
    if (attr.required && !isAutoField(name, attr)) {
      validations.push(`
    // ${name} is required
    if (${contextParam}.operation === 'create' && !${dataParam}.${name}) {
      errors.push('${name} is required');
    }`);
    }
    if (attr.type === "String" || attr.type === "string") {
      if (attr.min) {
        validations.push(`
    if (${dataParam}.${name} && ${dataParam}.${name}.length < ${attr.min}) {
      errors.push('${name} must be at least ${attr.min} characters');
    }`);
      }
      if (attr.max) {
        validations.push(`
    if (${dataParam}.${name} && ${dataParam}.${name}.length > ${attr.max}) {
      errors.push('${name} must be at most ${attr.max} characters');
    }`);
      }
    }
    if (attr.values && Array.isArray(attr.values)) {
      const values = attr.values.map((v) => `'${v}'`).join(", ");
      validations.push(`
    if (${dataParam}.${name} && ![${values}].includes(${dataParam}.${name})) {
      errors.push('${name} must be one of: ${attr.values.join(", ")}');
    }`);
    }
    if (attr.format === "email") {
      validations.push(`
    if (${dataParam}.${name} && !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(${dataParam}.${name})) {
      errors.push('${name} must be a valid email address');
    }`);
    }
  });
  return validations.join("\n") || "// No validation rules defined";
}
function generateCreateMethod(model, modelName, modelVar, prismaDelegate, controller, allModels) {
  return `
  /**
   * Create a new ${modelName}
   */
  public async create(data: any): Promise<any> {
    // Validate input
    const validationResult = this.validate(data, { operation: 'create' });
    if (!validationResult.valid) {
      throw new Error(\`Validation failed: \${validationResult.errors.join(', ')}\`);
    }

    // Transform FK fields to Prisma connect format
    const prismaData = { ...data };
    ${generateFKTransform(model, "prismaData", allModels)}

    // Create record
    const ${modelVar} = await ${prismaDelegate}.create({
      data: prismaData${generateIncludeRelationships(model)}
    });

    // Publish CURED event
    await eventBus.publish('${modelName}Created', { ...${modelVar}, timestamp: new Date().toISOString() } as any);

    return ${modelVar};
  }
`;
}
function generateRetrieveMethod(model, modelName, modelVar, prismaDelegate) {
  return `
  /**
   * Retrieve ${modelName} by ID. Returns null when not found so the
   * route handler can translate that into HTTP 404 instead of 500.
   */
  public async retrieve(id: string): Promise<any | null> {
    return await ${prismaDelegate}.findUnique({
      where: { id: parseId(id) }${generateIncludeRelationships(model)}
    });
  }

  /**
   * Retrieve all ${modelName}s
   */
  public async retrieveAll(options: { skip?: number; take?: number } = {}): Promise<any[]> {
    return await ${prismaDelegate}.findMany({
      skip: options.skip,
      take: options.take${generateIncludeRelationships(model)}
    });
  }
`;
}
function generateUpdateMethod(model, modelName, modelVar, prismaDelegate, controller, allModels) {
  return `
  /**
   * Update ${modelName}
   */
  public async update(id: string, data: any): Promise<any> {
    // Validate input
    const validationResult = this.validate(data, { operation: 'update' });
    if (!validationResult.valid) {
      throw new Error(\`Validation failed: \${validationResult.errors.join(', ')}\`);
    }

    // Strip nested relations and id \u2014 only send scalar fields to Prisma
    const updateData: any = {};
    for (const [key, value] of Object.entries(data)) {
      if (key === 'id') continue;
      if (Array.isArray(value)) continue;
      if (value !== null && typeof value === 'object' && !(value instanceof Date)) continue;
      updateData[key] = value;
    }

    // Transform FK fields to Prisma connect format
    ${generateFKTransform(model, "updateData", allModels)}

    // Update record
    const ${modelVar} = await ${prismaDelegate}.update({
      where: { id: parseId(id) },
      data: updateData${generateIncludeRelationships(model)}
    });

    // Publish CURED event
    await eventBus.publish('${modelName}Updated', { ...${modelVar}, timestamp: new Date().toISOString() } as any);

    return ${modelVar};
  }
`;
}
function generateEvolveMethod(model, modelName, modelVar, prismaDelegate, controller) {
  const lifecycles = Array.isArray(model.lifecycles) ? model.lifecycles : model.lifecycles ? Object.entries(model.lifecycles).map(([name, lc]) => ({ name, ...lc })) : [];
  const lifecycle = lifecycles[0];
  const lifecycleName = lifecycle?.name || "status";
  const states = lifecycle?.states || [];
  const validTransitions = lifecycle ? buildTransitionMap(lifecycle) : {};
  return `
  /**
   * Evolve ${modelName} through lifecycle
   * States: ${states.join(" \u2192 ")}
   */
  public async evolve(id: string, data: any): Promise<any> {
    // Validate input
    const validationResult = this.validate(data, { operation: 'evolve' });
    if (!validationResult.valid) {
      throw new Error(\`Validation failed: \${validationResult.errors.join(', ')}\`);
    }

    // Get current record to check lifecycle state
    const current = await ${prismaDelegate}.findUnique({ where: { id: parseId(id) } });
    if (!current) {
      throw new Error('${modelName} not found');
    }

    ${states.length > 0 ? `
    // Validate lifecycle transition
    const currentState = (current as any).${lifecycleName};
    const newState = data.${lifecycleName};
    if (newState) {
      const validTransitions: Record<string, string[]> = ${JSON.stringify(validTransitions)};
      const allowed = validTransitions[currentState] || [];
      if (!allowed.includes(newState)) {
        throw new Error(\`Invalid transition: \${currentState} \u2192 \${newState}. Allowed: \${allowed.join(', ') || 'none'}\`);
      }
    }
    ` : ""}

    // Update record
    const ${modelVar} = await ${prismaDelegate}.update({
      where: { id: parseId(id) },
      data${generateIncludeRelationships(model)}
    });

    // Publish CURED event
    await eventBus.publish('${modelName}Evolved', { ...${modelVar}, timestamp: new Date().toISOString() } as any);

    return ${modelVar};
  }
`;
}
function generateDeleteMethod(model, modelName, modelVar, prismaDelegate, controller) {
  return `
  /**
   * Delete ${modelName}
   */
  public async delete(id: string): Promise<void> {
    // Get record before deletion for event
    const ${modelVar} = await ${prismaDelegate}.findUnique({ where: { id: parseId(id) } });

    await ${prismaDelegate}.delete({
      where: { id: parseId(id) }
    });

    // Publish CURED event
    if (${modelVar}) {
      await eventBus.publish('${modelName}Deleted', { ...${modelVar}, timestamp: new Date().toISOString() } as any);
    }
  }
`;
}
function generateCustomActions(controller, modelName, modelVar) {
  if (!controller.actions || Object.keys(controller.actions).length === 0) {
    return { code: "", unmatchedSteps: [], needsAiBehaviors: false };
  }
  const actions = [];
  const allUnmatchedSteps = [];
  Object.entries(controller.actions).forEach(([actionName, action]) => {
    const behavior = {
      preconditions: action.requires || action.preconditions || [],
      steps: action.steps || [],
      postconditions: action.ensures || action.postconditions || [],
      sideEffects: action.publishes || action.events || [],
      transactional: action.transactional
    };
    const ctx = {
      modelName,
      serviceName: `${modelName}Controller`,
      operationName: actionName,
      prismaModel: modelVar,
      parameterNames: Object.keys(action.parameters || {})
    };
    const result = generateBehaviorWithHelpers(behavior, {}, ctx);
    allUnmatchedSteps.push(...result.unmatchedSteps);
    actions.push(`
  /**
   * ${actionName}
   * ${action.description || ""}
   */
  public async ${actionName}(${generateActionParams(action)}): Promise<any> {
${result.body}
  }`);
    if (result.helperMethods.length > 0) {
      actions.push(...result.helperMethods);
    }
  });
  return {
    code: actions.join("\n"),
    unmatchedSteps: allUnmatchedSteps,
    needsAiBehaviors: allUnmatchedSteps.length > 0
  };
}
function generateActionParams(action) {
  if (!action.parameters || Object.keys(action.parameters).length === 0) {
    return "";
  }
  const params = Object.entries(action.parameters).map(([name, param]) => {
    const optional = !param.required;
    return `${name}${optional ? "?" : ""}: ${mapTypeToTypeScript(param.type)}`;
  });
  return params.join(", ");
}
function mapTypeToTypeScript(type) {
  const typeMap = {
    String: "string",
    Integer: "number",
    Float: "number",
    Boolean: "boolean",
    Date: "Date",
    DateTime: "Date",
    UUID: "string",
    JSON: "any"
  };
  return typeMap[type] || "any";
}
function generateFKTransform(model, varName = "prismaData", allModels) {
  const rels = Array.isArray(model.relationships) ? model.relationships : Object.values(model.relationships || {});
  const belongsToRels = rels.filter((r) => r.type === "belongsTo");
  if (belongsToRels.length === 0) return "";
  return belongsToRels.map((rel) => {
    const relName = rel.name;
    const fkField = `${relName}Id`;
    let parseExpr = `${varName}.${fkField}`;
    if (allModels) {
      const targetModel = allModels.find((m) => m.name === rel.target);
      if (targetModel) {
        const idAttr = (Array.isArray(targetModel.attributes) ? targetModel.attributes : Object.values(targetModel.attributes || {})).find((a) => a.name === "id");
        const idType = idAttr?.type || "String";
        if (idType === "Integer" || idType === "Int" || idType === "Number") {
          parseExpr = `parseInt(${varName}.${fkField}, 10)`;
        }
      }
    }
    return `if (${varName}.${fkField}) {
      ${varName}.${relName} = { connect: { id: ${parseExpr} } };
      delete ${varName}.${fkField};
    }`;
  }).join("\n    ");
}
function generateIncludeRelationships(model) {
  if (!model.relationships) {
    return "";
  }
  let relationshipKeys;
  if (Array.isArray(model.relationships)) {
    relationshipKeys = model.relationships.map((rel) => {
      if (typeof rel === "string") {
        return rel;
      } else if (typeof rel === "object" && rel.name) {
        return rel.name;
      } else {
        return null;
      }
    }).filter((rel) => rel !== null);
  } else if (typeof model.relationships === "object") {
    relationshipKeys = Object.keys(model.relationships);
  } else {
    return "";
  }
  if (relationshipKeys.length === 0) {
    return "";
  }
  const includes = relationshipKeys.map((rel) => `        ${rel}: true`).join(",\n");
  return `,
      include: {
${includes}
      }`;
}
function hasEventPublishing(curedOps, controller) {
  return true;
}
export {
  generatePrismaController as default
};
