import { pluralize } from "@specverse/types";
function generatePrismaSchema(context) {
  const { spec, models, implType } = context;
  const allModels = models || spec?.models || [];
  if (allModels.length === 0) {
    throw new Error("No models found in context for schema generation");
  }
  const relationMap = buildRelationMap(allModels);
  const backRefs = buildMissingBackRefs(allModels, relationMap);
  const hasOneTargets = /* @__PURE__ */ new Set();
  for (const m of allModels) {
    const rels = Array.isArray(m.relationships) ? m.relationships : Object.values(m.relationships || {});
    for (const r of rels) {
      if (r.type === "hasOne") {
        hasOneTargets.add(`${m.name}->${r.target}`);
      }
    }
  }
  const header = generateHeader(implType);
  const modelSchemas = allModels.map(
    (model) => generateModelSchema(model, relationMap, backRefs, hasOneTargets, allModels)
  ).join("\n\n");
  return `${header}

${modelSchemas}`;
}
function buildRelationMap(allModels) {
  const targetRefs = /* @__PURE__ */ new Map();
  for (const model of allModels) {
    const relationships = Array.isArray(model.relationships) ? model.relationships : Object.values(model.relationships || {});
    for (const rel of relationships) {
      const target = rel.target;
      if (!target) continue;
      const fieldName = rel.name || (rel.type === "hasMany" || rel.type === "manyToMany" ? pluralize(target.toLowerCase()) : target.toLowerCase());
      if (!targetRefs.has(target)) {
        targetRefs.set(target, []);
      }
      targetRefs.get(target).push({
        sourceModel: model.name,
        fieldName,
        relType: rel.type
      });
    }
  }
  const nameMap = /* @__PURE__ */ new Map();
  for (const [target, refs] of targetRefs) {
    for (const ref of refs) {
      const key = `${ref.sourceModel}.${ref.fieldName}`;
      if (ref.relType === "belongsTo") {
        const parentRefs = targetRefs.get(ref.sourceModel) || [];
        const matchingParent = parentRefs.find(
          (p) => p.sourceModel === target && (p.relType === "hasMany" || p.relType === "hasOne")
        );
        if (matchingParent) {
          nameMap.set(key, `${matchingParent.sourceModel}_${matchingParent.fieldName}`);
        }
      }
    }
  }
  return nameMap;
}
function buildMissingBackRefs(allModels, relationMap) {
  const existingBelongsTo = /* @__PURE__ */ new Set();
  for (const model of allModels) {
    const relationships = Array.isArray(model.relationships) ? model.relationships : Object.values(model.relationships || {});
    for (const rel of relationships) {
      if (rel.type === "belongsTo") {
        existingBelongsTo.add(`${model.name}->${rel.target}`);
      }
    }
  }
  const backRefs = /* @__PURE__ */ new Map();
  for (const model of allModels) {
    const relationships = Array.isArray(model.relationships) ? model.relationships : Object.values(model.relationships || {});
    for (const rel of relationships) {
      if (rel.type !== "hasMany" && rel.type !== "hasOne") continue;
      const target = rel.target;
      const backKey = `${target}->${model.name}`;
      if (!existingBelongsTo.has(backKey)) {
        const fieldName = rel.name || (rel.type === "hasMany" ? pluralize(target.toLowerCase()) : target.toLowerCase());
        const relName = `${model.name}_${fieldName}`;
        const parentRelsToSameTarget = relationships.filter(
          (r) => r.target === target && (r.type === "hasMany" || r.type === "hasOne")
        );
        const needsFieldInFk = parentRelsToSameTarget.length > 1;
        const fkSuffix = needsFieldInFk ? camelToSnake(fieldName) + "_id" : camelToSnake(model.name.charAt(0).toLowerCase() + model.name.slice(1)) + "_id";
        const fkName = fkSuffix;
        const fkPadding = " ".repeat(Math.max(1, 15 - fkName.length));
        const refFieldName = needsFieldInFk ? fieldName + model.name : model.name.charAt(0).toLowerCase() + model.name.slice(1);
        const refPadding = " ".repeat(Math.max(1, 15 - refFieldName.length));
        let relDef = `${refFieldName}${refPadding}${model.name}`;
        relDef += ` @relation("${relName}", fields: [${fkName}], references: [id])`;
        if (!backRefs.has(target)) {
          backRefs.set(target, []);
        }
        const uniqueModifier = rel.type === "hasOne" ? " @unique" : "";
        backRefs.get(target).push(`${fkName}${fkPadding}String${uniqueModifier}`);
        backRefs.get(target).push(relDef);
      }
    }
  }
  const existingHasRelation = /* @__PURE__ */ new Set();
  for (const model of allModels) {
    const rels = Array.isArray(model.relationships) ? model.relationships : Object.values(model.relationships || {});
    for (const rel of rels) {
      if (rel.type === "hasMany" || rel.type === "hasOne") {
        existingHasRelation.add(`${model.name}->${rel.target}`);
      }
    }
  }
  for (const model of allModels) {
    const relationships = Array.isArray(model.relationships) ? model.relationships : Object.values(model.relationships || {});
    for (const rel of relationships) {
      if (rel.type !== "belongsTo") continue;
      const parent = rel.target;
      const forwardKey = `${parent}->${model.name}`;
      if (!existingHasRelation.has(forwardKey)) {
        const fieldName = rel.name || parent.toLowerCase();
        const relName = getRelationName(model.name, fieldName, parent, relationMap);
        const arrayFieldName = pluralize(model.name.charAt(0).toLowerCase() + model.name.slice(1));
        const padding = " ".repeat(Math.max(1, 15 - arrayFieldName.length));
        if (!backRefs.has(parent)) {
          backRefs.set(parent, []);
        }
        backRefs.get(parent).push(`${arrayFieldName}${padding}${model.name}[] @relation("${relName}")`);
      }
    }
  }
  return backRefs;
}
function getRelationName(sourceModel, fieldName, target, relationMap) {
  const mapped = relationMap.get(`${sourceModel}.${fieldName}`);
  if (mapped) return mapped;
  return `${sourceModel}_${fieldName}`;
}
function generateHeader(implType) {
  const provider = implType?.configuration?.orm?.provider || "sqlite";
  return `// Generated Prisma schema from SpecVerse specification
// This is your Prisma schema file
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "${provider}"
  url      = ${provider === "sqlite" ? '"file:./dev.db"' : 'env("DATABASE_URL")'}
}`;
}
function generateModelSchema(model, relationMap, backRefs, hasOneTargets, allModels) {
  const modelName = model.name;
  let schema = `model ${modelName} {
`;
  const attributes = Array.isArray(model.attributes) ? model.attributes : Object.values(model.attributes || {});
  const relationships = Array.isArray(model.relationships) ? model.relationships : Object.values(model.relationships || {});
  attributes.forEach((attr) => {
    schema += `  ${generateField(attr, model)}
`;
  });
  const rawLifecycles = model.lifecycles || [];
  const lifecycleList = Array.isArray(rawLifecycles) ? rawLifecycles : Object.entries(rawLifecycles).map(([name, lc]) => ({ name, ...typeof lc === "object" ? lc : {} }));
  for (const lifecycle of lifecycleList) {
    const fieldName = lifecycle.name || "status";
    const exists = attributes.some((a) => a.name === fieldName);
    if (!exists && lifecycle.states?.length > 0) {
      const defaultState = lifecycle.states[0];
      const padding = " ".repeat(Math.max(1, 15 - fieldName.length));
      schema += `  ${fieldName}${padding}String @default("${defaultState}")
`;
    }
  }
  relationships.forEach((rel) => {
    const fields = generateRelationship(rel, model, relationMap, hasOneTargets, allModels);
    fields.forEach((field) => {
      schema += `  ${field}
`;
    });
  });
  const modelBackRefs = backRefs.get(modelName);
  if (modelBackRefs) {
    for (const line of modelBackRefs) {
      schema += `  ${line}
`;
    }
  }
  schema += `}`;
  return schema;
}
function generateField(attr, model) {
  const name = attr.name;
  const prismaType = mapTypeToPrisma(attr.type, attr.dbMapping?.columnType);
  const isOptional = !(attr.required || attr.constraints?.required);
  const isUnique = attr.unique || attr.constraints?.unique;
  const metadata = model?.metadata || {};
  let modifiers = "";
  let hasDefault = false;
  if (name.toLowerCase() === "id") {
    modifiers += " @id";
    if (metadata.id === "uuid" || metadata.id === "auto" || prismaType === "String") {
      modifiers += " @default(uuid())";
      hasDefault = true;
    } else if (metadata.id === "integer" || prismaType === "Int") {
      modifiers += " @default(autoincrement())";
      hasDefault = true;
    }
  } else if (isUnique) {
    modifiers += " @unique";
  }
  if (name === "updatedAt" || name === "updated_at") {
    modifiers += " @updatedAt";
    hasDefault = true;
  }
  if (attr.auto && !hasDefault) {
    const autoValue = typeof attr.auto === "string" ? attr.auto.toLowerCase() : attr.auto;
    if (autoValue === "now" || autoValue === true) {
      if (prismaType === "DateTime") {
        modifiers += " @default(now())";
        hasDefault = true;
      }
    } else if (autoValue === "uuid" || autoValue === "uuid4") {
      if (prismaType === "String") {
        modifiers += " @default(uuid())";
        hasDefault = true;
      }
    } else if (autoValue === "autoincrement") {
      if (prismaType === "Int") {
        modifiers += " @default(autoincrement())";
        hasDefault = true;
      }
    }
  }
  if (!hasDefault && prismaType === "DateTime") {
    const autoTimestampFields = ["joinedAt", "registeredAt", "enrolledAt", "startedAt", "completedAt", "verifiedAt"];
    if (autoTimestampFields.includes(name)) {
      modifiers += " @default(now())";
      hasDefault = true;
    }
    if ((name === "createdAt" || name === "created_at") && metadata.audit) {
      modifiers += " @default(now())";
      hasDefault = true;
    }
  }
  if (metadata.softDelete && isSoftDeleteField(name)) {
    if (name === "isDeleted" || name === "is_deleted") {
      modifiers += " @default(false)";
    }
  }
  if (metadata.version && isVersionField(name)) {
    modifiers += " @default(0)";
  }
  if (attr.dbMapping?.columnType === "TEXT") {
    modifiers += " @db.Text";
  } else if (attr.dbMapping?.columnType?.startsWith("VARCHAR")) {
    const match = attr.dbMapping.columnType.match(/VARCHAR\((\d+)\)/);
    if (match) {
      modifiers += ` @db.VarChar(${match[1]})`;
    }
  }
  const padding = " ".repeat(Math.max(1, 15 - name.length));
  const typeStr = isOptional ? `${prismaType}?` : prismaType;
  return `${name}${padding}${typeStr}${modifiers}`;
}
function generateRelationship(rel, model, relationMap, hasOneTargets, allModels) {
  const fields = [];
  const name = rel.name || rel.target.toLowerCase();
  const padding = " ".repeat(Math.max(1, 15 - name.length));
  const isOptional = rel.optional || false;
  const relName = getRelationName(model.name, name, rel.target, relationMap);
  const relAnnotation = ` @relation("${relName}")`;
  switch (rel.type) {
    case "belongsTo":
      const fkBase = rel.foreignKey || camelToSnake(name) + "_id";
      const fkPadding = " ".repeat(Math.max(1, 15 - fkBase.length));
      const isUniqueFK = hasOneTargets.has(`${rel.target}->${model.name}`);
      let fkType = "String";
      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");
          if (idAttr) {
            const idType = idAttr.type || "String";
            fkType = mapTypeToPrisma(idType);
          }
        }
      }
      fields.push(`${fkBase}${fkPadding}${fkType}${isOptional ? "?" : ""}${isUniqueFK ? " @unique" : ""}`);
      let relationDef = `${name}${padding}${rel.target}${isOptional ? "?" : ""}`;
      relationDef += ` @relation(`;
      if (relName) {
        relationDef += `"${relName}", `;
      }
      relationDef += `fields: [${fkBase}], references: [id]`;
      let onDelete = rel.onDelete;
      if (!onDelete && allModels) {
        const parentModel = allModels.find((m) => m.name === rel.target);
        if (parentModel) {
          const parentRels = Array.isArray(parentModel.relationships) ? parentModel.relationships : Object.values(parentModel.relationships || {});
          const parentRel = parentRels.find((pr) => (pr.type === "hasMany" || pr.type === "hasOne") && (pr.target === model.name || pr.targetModel === model.name));
          if (parentRel?.cascade || parentRel?.cascadeDelete) {
            onDelete = "Cascade";
          }
        }
      }
      if (onDelete) {
        relationDef += `, onDelete: ${onDelete}`;
      }
      if (rel.onUpdate) {
        relationDef += `, onUpdate: ${rel.onUpdate}`;
      }
      relationDef += ")";
      fields.push(relationDef);
      break;
    case "hasOne":
      fields.push(`${name}${padding}${rel.target}?${relAnnotation}`);
      break;
    case "hasMany":
      const pluralName = rel.name || pluralize(rel.target.toLowerCase());
      const manyPadding = " ".repeat(Math.max(1, 15 - pluralName.length));
      fields.push(`${pluralName}${manyPadding}${rel.target}[]${relAnnotation}`);
      break;
    case "manyToMany":
      const manyName = rel.name || pluralize(rel.target.toLowerCase());
      const m2mPadding = " ".repeat(Math.max(1, 15 - manyName.length));
      fields.push(`${manyName}${m2mPadding}${rel.target}[]${relAnnotation}`);
      break;
  }
  return fields;
}
function camelToSnake(str) {
  return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
}
function mapTypeToPrisma(type, dbType) {
  if (dbType) {
    if (dbType.startsWith("VARCHAR") || dbType === "TEXT") return "String";
    const typeMap = {
      "INTEGER": "Int",
      "BIGINT": "BigInt",
      "DECIMAL": "Decimal",
      "BOOLEAN": "Boolean",
      "DATE": "DateTime",
      "TIMESTAMP": "DateTime",
      "UUID": "String",
      "JSONB": "Json",
      "JSON": "Json",
      "FLOAT": "Float"
    };
    if (typeMap[dbType]) return typeMap[dbType];
  }
  const typeLower = type.toLowerCase();
  if (typeLower.includes("string") || typeLower.includes("text")) return "String";
  if (typeLower.includes("int")) return "Int";
  if (typeLower.includes("bool")) return "Boolean";
  if (typeLower.includes("date") || typeLower.includes("time")) return "DateTime";
  if (typeLower.includes("float") || typeLower.includes("decimal")) return "Float";
  if (typeLower.includes("json")) return "Json";
  return "String";
}
function isAuditField(name) {
  const auditFields = [
    "createdAt",
    "updatedAt",
    "createdBy",
    "updatedBy",
    "created_at",
    "updated_at",
    "created_by",
    "updated_by"
  ];
  return auditFields.includes(name);
}
function isSoftDeleteField(name) {
  const softDeleteFields = ["deletedAt", "isDeleted", "deleted_at", "is_deleted"];
  return softDeleteFields.includes(name);
}
function isVersionField(name) {
  const versionFields = ["version", "versionNumber", "version_number"];
  return versionFields.includes(name);
}
export {
  generatePrismaSchema as default
};
