/**
 * Map a Prisma `@default(...)` expression to a SpecVerse `auto=` token, so
 * id-generation semantics survive analyse (Phase 2 of the identity proposal)
 * instead of being discarded + re-guessed as uuid. Value-literal defaults
 * (`"active"`, `true`, `0`) are NOT id-gen autos → undefined.
 */
export function autoFromPrismaDefault(def) {
    if (!def)
        return undefined;
    const d = def.toLowerCase();
    if (d.includes('uuid'))
        return 'uuid4';
    if (d.includes('cuid'))
        return 'cuid';
    if (d.includes('autoincrement'))
        return 'autoincrement';
    if (d.includes('now'))
        return 'now';
    return undefined;
}
/**
 * Map a Prisma datasource provider to a SpecVerse storage profile.
 * Prisma's SQL providers (postgresql / mysql / sqlite / sqlserver /
 * cockroachdb) are `relational`; `mongodb` is `document`. `postgres` is
 * normalised to `postgresql`. Exported for direct unit testing of the
 * mapping (incl. the Mongo branch) without a fixture per provider.
 */
export function storageProfileForPrismaProvider(provider) {
    const p = provider.trim().toLowerCase();
    if (p === 'mongodb')
        return { type: 'document', provider: 'mongodb' };
    return { type: 'relational', provider: p === 'postgres' ? 'postgresql' : p };
}
/**
 * Extract the `datasource { provider = "…" }` value, ignoring the
 * `generator { provider = "prisma-client-js" }` block (which also has a
 * `provider` key). Returns null if no datasource block is present.
 */
function parseDatasourceProvider(text) {
    const block = text.match(/datasource\s+\w+\s*\{([^}]*)\}/);
    if (!block)
        return null;
    const m = block[1].match(/provider\s*=\s*["']([^"']+)["']/);
    return m ? m[1] : null;
}
/**
 * Run the Prisma adapter against a structural pre-pass.
 *
 * The adapter relies on `prepass.fileSourceText()` to read schema.prisma
 * directly — it doesn't need any of the backend's call-graph or symbol
 * indexing for Prisma extraction. Prisma's schema syntax is small enough
 * that regex over the raw text is the right tool.
 *
 * Discovers Prisma sources via `**\/*.prisma` to cover both layouts:
 *   - Single-file: `schema.prisma` (the legacy default).
 *   - Multi-file: Prisma 5.18+ `prismaSchemaFolder` previewFeature, where
 *     models split across `prisma/schema/*.prisma`.
 * Files lacking model/enum declarations (e.g. files containing only
 * `generator` + `datasource` blocks) contribute nothing and are harmless.
 * Duplicate model names across files emit a warning and the FIRST
 * declaration wins (Prisma itself rejects duplicates at validate-time).
 */
export async function extractTypeScriptPrisma(prepass) {
    const schemaFiles = await prepass.listFiles({ glob: '**/*.prisma' });
    if (schemaFiles.length === 0) {
        return { entities: [], relationships: [], enums: {}, schemaFiles: [], datasourceProvider: null, storageProfile: null };
    }
    const enums = {};
    const entities = [];
    const seenModels = new Set();
    const relationships = [];
    let datasourceProvider = null;
    for (const file of schemaFiles) {
        const text = await prepass.fileSourceText(file);
        // Datasource provider (first one wins) — drives the storage profile.
        if (!datasourceProvider)
            datasourceProvider = parseDatasourceProvider(text);
        // Extract enum declarations
        for (const match of text.matchAll(/enum\s+(\w+)\s*\{([^}]+)\}/g)) {
            const name = match[1];
            const body = match[2];
            const values = body
                .split(/\r?\n/)
                .map((l) => l.trim())
                .filter((l) => l && !l.startsWith('//'))
                .map((l) => l.split(/\s+/)[0])
                .filter(Boolean);
            enums[name] = values;
        }
        // Extract model declarations
        for (const match of text.matchAll(/model\s+(\w+)\s*\{([^}]+)\}/g)) {
            const modelName = match[1];
            if (seenModels.has(modelName)) {
                // Prisma rejects duplicates at validate-time; we mirror that by
                // taking the first declaration we encountered. Worth surfacing as
                // a console warning so a misconfigured schema doesn't fail silently.
                console.warn(`[prisma-adapter] duplicate model "${modelName}" in ${file} — first declaration wins`);
                continue;
            }
            seenModels.add(modelName);
            const body = match[2];
            const fields = parseFields(body);
            // Detect lifecycle: a field of an enum type — the enum's values are
            // candidate states. Common SpecVerse pattern: `status SomeStatus` field
            // with a corresponding `enum SomeStatus { ... }`.
            let lifecycleStates;
            let lifecycleField;
            for (const f of fields) {
                // The field type may itself be an enum we extracted above
                const typeName = f.type.replace(/\?$/, ''); // strip optional marker
                if (enums[typeName]) {
                    lifecycleStates = enums[typeName];
                    lifecycleField = f.name;
                    break; // Take first enum-typed field; canonical is `status`
                }
            }
            entities.push({
                name: modelName,
                filePath: file,
                fields,
                lifecycleStates,
                lifecycleField,
            });
            // Extract relationships from this model's fields
            for (const rel of parseRelationships(modelName, body, fields)) {
                relationships.push(rel);
            }
        }
    }
    // Second pass: derive `hasMany` from `belongsTo` declarations on the inverse
    // side (Prisma schemas typically declare both sides explicitly). Crucially,
    // we PROPAGATE cascade flags from the belongsTo side (where Prisma's
    // `onDelete: Cascade` syntactically lives) up to the hasMany side (where
    // SpecVerse semantics expect the cascade decision to live — "deleting the
    // parent cascades to its children").
    const declaredHasMany = new Set(relationships.filter((r) => r.type === 'hasMany').map((r) => `${r.fromModel}→${r.toModel}`));
    for (const ent of entities) {
        for (const f of ent.fields) {
            if (!f.isList)
                continue;
            // Is this a list-typed field whose type is another model?
            const typeName = f.type.replace(/\[\]$/, '').replace(/\?$/, '');
            const isModel = entities.some((e) => e.name === typeName);
            if (!isModel)
                continue;
            const key = `${ent.name}→${typeName}`;
            if (!declaredHasMany.has(key)) {
                // Find the inverse belongsTo (if declared) — it carries the cascade flag
                // that we want to surface on the hasMany side.
                const inverse = relationships.find((r) => r.type === 'belongsTo' && r.fromModel === typeName && r.toModel === ent.name);
                relationships.push({
                    fromModel: ent.name,
                    toModel: typeName,
                    type: 'hasMany',
                    cascade: inverse?.cascade ?? false,
                    fieldName: f.name,
                });
                declaredHasMany.add(key);
            }
        }
    }
    // Third pass: clear cascade from belongsTo entries (in SpecVerse vocabulary,
    // cascade is a property of the parent→child edge, not the child→parent FK
    // edge). The flag has already been propagated to hasMany above.
    for (const r of relationships) {
        if (r.type === 'belongsTo')
            r.cascade = false;
    }
    return {
        entities,
        relationships,
        enums,
        schemaFiles,
        datasourceProvider,
        storageProfile: datasourceProvider ? storageProfileForPrismaProvider(datasourceProvider) : null,
    };
}
// ────────────────────────────────────────────────────────────────────
// Prisma-syntax parsers (regex-based; sufficient for schema.prisma)
// ────────────────────────────────────────────────────────────────────
function parseFields(modelBody) {
    const out = [];
    for (const rawLine of modelBody.split(/\r?\n/)) {
        const line = rawLine.trim();
        if (!line || line.startsWith('//') || line.startsWith('@@'))
            continue;
        // A field line: `name Type[?] [modifiers...]`
        // Skip relation-only lines (they have @relation annotation typically on a separate line — handled below)
        const match = line.match(/^(\w+)\s+(\w+(?:\[\])?(?:\?)?)\s*(.*)$/);
        if (!match)
            continue;
        const [, name, fullType, rest] = match;
        const isList = fullType.endsWith('[]');
        const isOptional = fullType.endsWith('?');
        const baseType = fullType.replace(/\[\]/, '').replace(/\?$/, '');
        const def = extractDefault(rest);
        out.push({
            name,
            type: fullType,
            required: !isOptional && !isList, // List fields are intrinsically optional in Prisma semantics
            unique: /@unique\b/.test(rest),
            default: def,
            auto: autoFromPrismaDefault(def),
            isId: /@id\b/.test(rest),
            isList,
        });
        // Use baseType here to detect what we're actually pointing at — it's a hint for callers
        void baseType;
    }
    return out;
}
function extractDefault(rest) {
    const match = rest.match(/@default\(([^)]+)\)/);
    return match ? match[1].trim() : undefined;
}
/**
 * Detect `belongsTo` relationships declared via `@relation(fields: [...], references: [...])`.
 * The model containing such a declaration owns the foreign key — it `belongsTo` the target.
 */
function parseRelationships(modelName, modelBody, fields) {
    const out = [];
    // Match: `field TargetType[?] @relation(fields: [...], references: [...], onDelete: Cascade)`
    const relationRegex = /^\s*(\w+)\s+(\w+)(\?)?\s+@relation\(([^)]+)\)/gm;
    for (const match of modelBody.matchAll(relationRegex)) {
        const [, fieldName, targetType] = match;
        const annotation = match[4];
        // It's a belongsTo — the field's type is the target model
        const cascade = /onDelete:\s*Cascade/.test(annotation);
        // Skip self-referential or array-typed entries (those are hasMany, not belongsTo)
        const f = fields.find((x) => x.name === fieldName);
        if (f?.isList)
            continue;
        out.push({
            fromModel: modelName,
            toModel: targetType,
            type: 'belongsTo',
            cascade,
            fieldName,
        });
    }
    return out;
}
//# sourceMappingURL=typescript-prisma.js.map