/**
 * ORM model relationship graph extraction.
 * Supports Django (ForeignKey, ManyToManyField, OneToOneField) and
 * SQLAlchemy (relationship, ForeignKey Column).
 */
import { getCodeIndex } from "./index-tools.js";
// Django relationship field patterns
const DJANGO_FK_RE = /(\w+)\s*=\s*(?:models\.)?ForeignKey\s*\(\s*['"]?(\w+)['"]?/g;
const DJANGO_O2O_RE = /(\w+)\s*=\s*(?:models\.)?OneToOneField\s*\(\s*['"]?(\w+)['"]?/g;
const DJANGO_M2M_RE = /(\w+)\s*=\s*(?:models\.)?ManyToManyField\s*\(\s*['"]?(\w+)['"]?/g;
// SQLAlchemy relationship patterns
const SQLA_REL_RE = /(\w+)\s*=\s*relationship\s*\(\s*['"](\w+)['"]/g;
const SQLA_FK_COL_RE = /(\w+)\s*=\s*Column\s*\([^)]*ForeignKey\s*\(\s*['"](\w+)\.(\w+)['"]/g;
// Django field type extraction (non-relationship fields)
const DJANGO_FIELD_RE = /(\w+)\s*=\s*(?:models\.)?(\w+Field)\s*\(/g;
// SQLAlchemy Column type extraction
const SQLA_COL_RE = /(\w+)\s*=\s*Column\s*\(\s*(\w+)/g;
/**
 * Extract ORM model relationships from a repository.
 * Scans model classes for Django and SQLAlchemy relationship declarations.
 */
export async function getModelGraph(repo, options) {
    const index = await getCodeIndex(repo);
    if (!index)
        throw new Error(`Repository "${repo}" not found.`);
    const filePattern = options?.file_pattern;
    // Find model classes — Python classes that look like ORM models.
    // Strict detection: must have ORM-specific field patterns in source,
    // not just a generic base class name like "Base" (which Flask uses).
    const modelSymbols = index.symbols.filter((s) => {
        if (s.kind !== "class")
            return false;
        if (!s.file.endsWith(".py"))
            return false;
        if (filePattern && !s.file.includes(filePattern))
            return false;
        const source = s.source ?? "";
        // Strong Django signals: explicit models.Model import + Field types
        const isDjangoModel = source.includes("models.Model")
            || /models\.(CharField|IntegerField|ForeignKey|ManyToManyField|OneToOneField|DateTimeField|BooleanField|TextField)/.test(source);
        // Strong SQLAlchemy signals: Column(Type) declaration or relationship()
        const isSQLAlchemy = /=\s*Column\s*\(/.test(source)
            || /=\s*relationship\s*\(/.test(source)
            || /__tablename__\s*=/.test(source);
        return isDjangoModel || isSQLAlchemy;
    });
    const models = [];
    const edges = [];
    let framework = "unknown";
    for (const sym of modelSymbols) {
        const source = sym.source ?? "";
        const fields = [];
        // Detect framework
        if (source.includes("models.Model") || (source.includes("ForeignKey") && source.includes("on_delete"))) {
            framework = "django";
        }
        else if (source.includes("Column(") || source.includes("relationship(")) {
            framework = "sqlalchemy";
        }
        // Extract Django relationships
        for (const [re, relType] of [
            [DJANGO_FK_RE, "fk"],
            [DJANGO_O2O_RE, "o2o"],
            [DJANGO_M2M_RE, "m2m"],
        ]) {
            re.lastIndex = 0;
            let match;
            while ((match = re.exec(source)) !== null) {
                const fieldName = match[1];
                const targetModel = match[2];
                edges.push({
                    from: sym.name,
                    to: targetModel,
                    field: fieldName,
                    relationship: relType,
                });
                fields.push({ name: fieldName, type: `${relType}(${targetModel})` });
            }
        }
        // Extract SQLAlchemy relationships
        SQLA_REL_RE.lastIndex = 0;
        let relMatch;
        while ((relMatch = SQLA_REL_RE.exec(source)) !== null) {
            edges.push({
                from: sym.name,
                to: relMatch[2],
                field: relMatch[1],
                relationship: "relationship",
            });
            fields.push({ name: relMatch[1], type: `relationship(${relMatch[2]})` });
        }
        // Extract SQLAlchemy FK columns
        SQLA_FK_COL_RE.lastIndex = 0;
        let fkMatch;
        while ((fkMatch = SQLA_FK_COL_RE.exec(source)) !== null) {
            const targetTable = fkMatch[2];
            // Convert table name to model name (best effort: capitalize)
            const targetModel = targetTable.charAt(0).toUpperCase() + targetTable.slice(1);
            edges.push({
                from: sym.name,
                to: targetModel,
                field: fkMatch[1],
                relationship: "fk",
            });
        }
        // Extract non-relationship fields
        DJANGO_FIELD_RE.lastIndex = 0;
        let fieldMatch;
        while ((fieldMatch = DJANGO_FIELD_RE.exec(source)) !== null) {
            // Skip if already captured as a relationship
            if (!fields.some((f) => f.name === fieldMatch[1])) {
                fields.push({ name: fieldMatch[1], type: fieldMatch[2] });
            }
        }
        SQLA_COL_RE.lastIndex = 0;
        while ((fieldMatch = SQLA_COL_RE.exec(source)) !== null) {
            if (!fields.some((f) => f.name === fieldMatch[1])) {
                fields.push({ name: fieldMatch[1], type: fieldMatch[2] });
            }
        }
        models.push({ name: sym.name, file: sym.file, fields });
    }
    const graph = { models, edges, framework };
    if (options?.output_format === "mermaid") {
        return { mermaid: graphToMermaid(graph) };
    }
    return graph;
}
function graphToMermaid(graph) {
    const lines = ["erDiagram"];
    for (const model of graph.models) {
        lines.push(`  ${model.name} {`);
        for (const field of model.fields) {
            lines.push(`    ${field.type} ${field.name}`);
        }
        lines.push("  }");
    }
    for (const edge of graph.edges) {
        const rel = edge.relationship === "m2m" ? "}o--o{" :
            edge.relationship === "o2o" ? "||--||" : "||--o{";
        lines.push(`  ${edge.from} ${rel} ${edge.to} : "${edge.field}"`);
    }
    return lines.join("\n");
}
//# sourceMappingURL=model-tools.js.map