/**
 * Quint → TypeScript Transpiler
 *
 * Transpiles Quint `pure def` functions and `val` invariants into
 * TypeScript guard functions for runtime validation.
 *
 * Handles a subset of Quint syntax:
 * - pure def name(params): type = { body }
 * - val name: bool = expression
 * - if/else expressions
 * - and/or/not/implies operators
 * - comparison operators (==, !=, >=, <=, >, <)
 * - arithmetic (+, -, *, /, %)
 * - forall/exists quantifiers (transpiled to .every/.some)
 * - set operations (.contains, .size, .filter, .map, .union)
 */
import { readFileSync, existsSync, readdirSync } from 'fs';
import { join } from 'path';
/**
 * Transpile all Quint files from entity modules into TypeScript guards.
 */
export function transpileEntityGuards(entitiesDir) {
    const guards = [];
    for (const category of ['core', 'extensions']) {
        const categoryDir = join(entitiesDir, category);
        if (!existsSync(categoryDir))
            continue;
        for (const entity of readdirSync(categoryDir)) {
            const behaviourDir = join(categoryDir, entity, 'behaviour');
            if (!existsSync(behaviourDir))
                continue;
            for (const file of readdirSync(behaviourDir).filter(f => f.endsWith('.qnt'))) {
                const filePath = join(behaviourDir, file);
                const content = readFileSync(filePath, 'utf8');
                const fileGuards = transpileQuintFile(content, entity, file);
                guards.push(...fileGuards);
            }
        }
    }
    return guards;
}
/**
 * Scan Quint behaviour files for module-level `var X: ...` declarations.
 * These are the state variables guards read from; the runtime host
 * populates them via `setSpecSnapshot` before invoking any `check_*`.
 */
export function extractStateVariables(entitiesDir) {
    const found = new Set();
    const varRe = /^\s*var\s+(\w+)\s*:/gm;
    for (const category of ['core', 'extensions']) {
        const categoryDir = join(entitiesDir, category);
        if (!existsSync(categoryDir))
            continue;
        for (const entity of readdirSync(categoryDir)) {
            const behaviourDir = join(categoryDir, entity, 'behaviour');
            if (!existsSync(behaviourDir))
                continue;
            for (const file of readdirSync(behaviourDir).filter(f => f.endsWith('.qnt'))) {
                const content = readFileSync(join(behaviourDir, file), 'utf8');
                let m;
                varRe.lastIndex = 0;
                while ((m = varRe.exec(content)) !== null) {
                    // Skip `fullyExpanded` and similar pure-inference state vars
                    // — they're booleans, not array-of-entity collections.
                    if (m[1] === 'fullyExpanded')
                        continue;
                    found.add(m[1]);
                }
            }
        }
    }
    return [...found].sort();
}
/**
 * Transpile a single Quint file into TypeScript guards.
 */
export function transpileQuintFile(content, entity, filename) {
    const guards = [];
    const lines = content.split('\n');
    const moduleName = extractModuleName(content) || entity;
    // Collect file-level `var X: ...` declarations so we know which symbols
    // to treat as dependencies (vs local parameters) when scanning guard
    // bodies.
    const fileStateVars = new Set();
    for (const line of lines) {
        const vm = line.match(/^\s*var\s+(\w+)\s*:/);
        if (vm && vm[1] !== 'fullyExpanded')
            fileStateVars.add(vm[1]);
    }
    let i = 0;
    while (i < lines.length) {
        const line = lines[i].trim();
        // Match: pure def name(params): returnType = {
        const pureDef = line.match(/^pure\s+def\s+(\w+)\(([^)]*)\)\s*:\s*(\w+)\s*=\s*\{/);
        if (pureDef) {
            const [, name, params, returnType] = pureDef;
            const body = extractBlock(lines, i);
            const tsParams = transpileParams(params);
            const tsReturn = mapType(returnType);
            const tsBody = transpileBody(body);
            guards.push({
                name,
                kind: 'function',
                quint: `pure def ${name}(${params}): ${returnType} = { ${body} }`,
                typescript: `export function ${name}(${tsParams}): ${tsReturn} {\n  ${tsBody}\n}`,
                module: moduleName,
                entity,
                dependencies: extractGuardDependencies(body, fileStateVars),
            });
            i = skipBlock(lines, i);
            continue;
        }
        // Match: val name: bool = expression (single line or multiline)
        const valDef = line.match(/^val\s+(\w+)\s*:\s*bool\s*=\s*$/);
        if (valDef) {
            // Multiline val — collect until we find a line that's not indented
            const [, name] = valDef;
            let body = '';
            i++;
            while (i < lines.length && (lines[i].startsWith('    ') || lines[i].trim() === '')) {
                body += lines[i].trim() + ' ';
                i++;
            }
            const bodyTrim = body.trim();
            const tsBody = transpileExpression(bodyTrim);
            guards.push({
                name,
                kind: 'invariant',
                quint: `val ${name}: bool = ${bodyTrim}`,
                typescript: `export function check_${name}(_data?: any): boolean {\n  return ${tsBody};\n}`,
                module: moduleName,
                entity,
                dependencies: extractGuardDependencies(bodyTrim, fileStateVars),
            });
            continue;
        }
        // Match: val name: bool = single-line-expression
        const valDefSingle = line.match(/^val\s+(\w+)\s*:\s*bool\s*=\s*(.+)$/);
        if (valDefSingle) {
            const [, name, expr] = valDefSingle;
            const tsExpr = transpileExpression(expr.trim());
            guards.push({
                name,
                kind: 'invariant',
                quint: `val ${name}: bool = ${expr}`,
                typescript: `export function check_${name}(data: any): boolean {\n  return ${tsExpr};\n}`,
                module: moduleName,
                entity,
                dependencies: extractGuardDependencies(expr.trim(), fileStateVars),
            });
        }
        i++;
    }
    return guards;
}
/**
 * Scan a Quint expression (pre-transpile) and return which file-level
 * state variables it reads. Used to decide whether a guard is applicable
 * to a given spec snapshot: if the snapshot doesn't populate one of the
 * guard's dependencies, verify skips the guard.
 */
function extractGuardDependencies(body, fileStateVars) {
    if (!fileStateVars.size)
        return [];
    const deps = new Set();
    // Match bare identifiers not preceded by a word char or dot (so
    // `m.models` — field access on a lambda param — doesn't count, but
    // `models.forall(...)` does).
    for (const v of fileStateVars) {
        const re = new RegExp(`(?<![A-Za-z0-9_.])${v}\\b`);
        if (re.test(body))
            deps.add(v);
    }
    return [...deps].sort();
}
/**
 * Generate a TypeScript guards module from transpiled guards.
 *
 * `stateVars` is the list of Quint module-level `var` declarations the
 * guards reference. When omitted, falls back to a minimal hard-coded
 * set — but callers should pass the real list from `extractStateVariables`.
 */
export function generateGuardsModule(guards, stateVars) {
    if (guards.length === 0)
        return '// No guards generated\nexport {};\n';
    // Deduplicate by name — multiple modules may define the same invariant
    // (e.g. invariants.qnt and rules.qnt). First occurrence wins.
    const seen = new Set();
    guards = guards.filter(g => {
        const key = `${g.kind}:${g.name}`;
        if (seen.has(key))
            return false;
        seen.add(key);
        return true;
    });
    const functions = guards.filter(g => g.kind === 'function');
    const invariants = guards.filter(g => g.kind === 'invariant');
    const candidateStateVars = stateVars && stateVars.length > 0
        ? stateVars
        : ['models', 'controllers', 'services', 'views', 'events', 'deployments', 'commands', 'conventions', 'measures'];
    // Only declare state vars the surviving guards actually reference —
    // otherwise `noUnusedLocals` flags declarations for vars whose only
    // consumer was a deduplicated guard. Exclude dot-prefixed matches
    // (`d.channels` is a field access on a lambda parameter, not the
    // module-level `channels` state variable).
    const guardBodies = guards.map(g => g.typescript).join('\n');
    const resolvedStateVars = candidateStateVars.filter(v => new RegExp(`(?<![\\w.])${v}\\b`).test(guardBodies));
    const declarations = resolvedStateVars.map(v => `let ${v}: any[] = [];`);
    const assignments = resolvedStateVars.map(v => `  ${v} = snapshot?.${v} ?? [];`);
    const lines = [
        '/**',
        ' * Runtime Guards — Transpiled from Quint Specifications',
        ' *',
        ' * These functions are automatically generated from the formal Quint invariants',
        ' * and pure functions defined in the entity modules. They provide runtime',
        ' * validation that mirrors the formal specification.',
        ' *',
        ` * Source: ${guards.length} guards from ${new Set(guards.map(g => g.entity)).size} entity modules`,
        ' * Generated by: @specverse/engine-inference quint-transpiler',
        ' */',
        '',
        '// Quint module-level state variables — initialized via setSpecSnapshot().',
        '// The guards read from these; a host caller populates them before',
        '// invoking any check_* function. Variable set is auto-discovered from',
        '// the guard bodies.',
        '/* eslint-disable @typescript-eslint/no-unused-vars, prefer-const */',
        ...declarations,
        '',
        'export function setSpecSnapshot(snapshot: any): void {',
        ...assignments,
        '}',
        '',
        '/**',
        ' * Cardinality helper for Quint .size() — handles arrays, plain',
        ' * objects (Quint Maps), Sets, and undefined safely.',
        ' */',
        'function _size(x: any): number {',
        '  if (x == null) return 0;',
        '  if (Array.isArray(x)) return x.length;',
        '  if (typeof x === "string") return x.length;',
        '  if (x instanceof Set || x instanceof Map) return x.size;',
        '  if (typeof x === "object") return Object.keys(x).length;',
        '  return 0;',
        '}',
        '',
    ];
    // Group by entity
    const byEntity = new Map();
    for (const g of guards) {
        if (!byEntity.has(g.entity))
            byEntity.set(g.entity, []);
        byEntity.get(g.entity).push(g);
    }
    for (const [entity, entityGuards] of byEntity) {
        lines.push(`// ═══════════════════════════════════════════════════`);
        lines.push(`// ${entity} guards`);
        lines.push(`// ═══════════════════════════════════════════════════`);
        lines.push('');
        for (const guard of entityGuards) {
            lines.push(`/** ${guard.kind === 'function' ? 'Pure function' : 'Invariant'}: ${guard.name} (from ${guard.module}) */`);
            lines.push(guard.typescript);
            lines.push('');
        }
    }
    return lines.join('\n');
}
/**
 * Transpile Quint action definitions into async TypeScript methods.
 *
 * Quint actions follow the pattern:
 *   action name(params): bool = all {
 *     guard1,                          // → precondition check
 *     guard2,                          // → precondition check
 *     model' = { ...model, field: x }, // → prisma update
 *     emit(EventName, payload),        // → event emission
 *   }
 */
export function transpileActions(content, entity, filename) {
    const actions = [];
    const lines = content.split('\n');
    const moduleName = extractModuleName(content) || entity;
    let i = 0;
    while (i < lines.length) {
        const line = lines[i].trim();
        // Match: action name(params): returnType = all {
        const actionDef = line.match(/^action\s+(\w+)\(([^)]*)\)\s*:\s*\w+\s*=\s*all\s*\{/);
        if (actionDef) {
            const [, name, params] = actionDef;
            // Skip inference actions (generate*, expand*, init, step)
            if (/^(generate|expand|init$|step$)/.test(name)) {
                i = skipBlock(lines, i);
                continue;
            }
            const body = extractBlock(lines, i);
            const action = transpileAction(name, params, body, moduleName, entity);
            if (action)
                actions.push(action);
            i = skipBlock(lines, i);
            continue;
        }
        i++;
    }
    return actions;
}
function transpileAction(name, params, body, moduleName, entity) {
    const statements = splitActionStatements(body);
    const guards = [];
    const effects = [];
    const events = [];
    for (const stmt of statements) {
        const trimmed = stmt.trim();
        if (!trimmed)
            continue;
        // State update: model' = { ...model, field: value }
        const updateMatch = trimmed.match(/^(\w+)'\s*=\s*\{\s*\.\.\.(\w+),\s*(.+)\}/);
        if (updateMatch) {
            const [, , , fieldsStr] = updateMatch;
            const fields = parseFieldUpdates(fieldsStr);
            effects.push(generatePrismaUpdate(entity, fields));
            continue;
        }
        // State update: model' = model (no-op, skip)
        if (trimmed.match(/^\w+'\s*=\s*\w+$/))
            continue;
        // Primed variable set union: collection' = collection.union(...)
        if (trimmed.match(/^\w+'\s*=\s*\w+\.union/))
            continue;
        // Event emission: emit(EventName, { payload })
        const emitMatch = trimmed.match(/^emit\((\w+),\s*\{([^}]*)\}\)/);
        if (emitMatch) {
            const [, eventName, payload] = emitMatch;
            events.push(`    this.emit('${eventName}', { ${transpileExpression(payload)} });`);
            continue;
        }
        // Boolean primed: fullyExpanded' = false (skip inference state)
        if (trimmed.match(/^\w+'\s*=\s*(true|false)$/))
            continue;
        // Everything else is a guard condition
        guards.push(trimmed);
    }
    // Generate TypeScript
    const tsParams = transpileParams(params);
    const tsGuards = guards.map(g => {
        const tsExpr = transpileExpression(g);
        return `    if (!(${tsExpr})) {\n      throw new Error('Guard failed: ${g.replace(/'/g, "\\'")}');\n    }`;
    });
    const tsEffects = effects;
    const tsEvents = events;
    const tsBody = [
        ...(tsGuards.length > 0 ? ['    // === GUARDS ===', ...tsGuards, ''] : []),
        ...(tsEffects.length > 0 ? ['    // === EFFECTS ===', ...tsEffects, ''] : []),
        ...(tsEvents.length > 0 ? ['    // === EVENTS ===', ...tsEvents] : []),
    ].join('\n');
    const typescript = `  async ${name}(${tsParams}): Promise<void> {\n${tsBody}\n  }`;
    return {
        name,
        params,
        guards,
        effects,
        events,
        quint: `action ${name}(${params}) = all { ... }`,
        typescript,
        module: moduleName,
        entity,
    };
}
function splitActionStatements(body) {
    // Split on commas at the top level (not inside braces or parens)
    const statements = [];
    let current = '';
    let depth = 0;
    for (const char of body) {
        if (char === '{' || char === '(')
            depth++;
        if (char === '}' || char === ')')
            depth--;
        if (char === ',' && depth === 0) {
            statements.push(current.trim());
            current = '';
        }
        else {
            current += char;
        }
    }
    if (current.trim())
        statements.push(current.trim());
    return statements;
}
function parseFieldUpdates(fieldsStr) {
    const fields = [];
    const parts = fieldsStr.split(',');
    for (const part of parts) {
        const match = part.trim().match(/(\w+)\s*:\s*(.+)/);
        if (match) {
            fields.push({ field: match[1], value: match[2].trim() });
        }
    }
    return fields;
}
function generatePrismaUpdate(entity, fields) {
    const dataEntries = fields.map(f => {
        let val = f.value;
        // Map Quint expressions to TypeScript
        if (val === 'now()')
            val = 'new Date()';
        else if (val === 'true' || val === 'false')
            val = val;
        else if (/^\d+$/.test(val))
            val = val;
        else if (val.startsWith('"') || val.startsWith("'"))
            val = val;
        else
            val = `'${val}'`; // Assume string literal for state names
        return `${f.field}: ${val}`;
    });
    const modelVar = entity.charAt(0).toLowerCase() + entity.slice(1);
    return `    await this.prisma.${modelVar}.update({\n      where: { id: params.id },\n      data: { ${dataEntries.join(', ')} },\n    });`;
}
/**
 * Generate a TypeScript module from transpiled actions.
 */
export function generateActionsModule(actions) {
    if (actions.length === 0)
        return '';
    const lines = [
        '// ═══════════════════════════════════════════════════',
        '// Business Logic Actions — Transpiled from Quint',
        '// ═══════════════════════════════════════════════════',
        '',
    ];
    for (const action of actions) {
        lines.push(`  /** Action: ${action.name} (from ${action.module}) */`);
        lines.push(action.typescript);
        lines.push('');
    }
    return lines.join('\n');
}
// ─── Internal Helpers ────────────────────────────────────────────────
function extractModuleName(content) {
    const match = content.match(/module\s+(\w+)/);
    return match ? match[1] : null;
}
function extractBlock(lines, startLine) {
    let depth = 0;
    let body = '';
    let capturing = false;
    for (let i = startLine; i < lines.length; i++) {
        for (let j = 0; j < lines[i].length; j++) {
            const char = lines[i][j];
            if (char === '{') {
                depth++;
                if (depth === 1) {
                    capturing = true;
                    continue;
                } // skip opening brace
            }
            if (char === '}') {
                depth--;
                if (depth === 0)
                    return body.trim();
            }
            if (capturing && depth >= 1) {
                body += char;
            }
        }
        if (capturing && depth >= 1)
            body += '\n';
    }
    return body.trim();
}
function skipBlock(lines, startLine) {
    let depth = 0;
    for (let i = startLine; i < lines.length; i++) {
        for (const char of lines[i]) {
            if (char === '{')
                depth++;
            if (char === '}')
                depth--;
            if (depth === 0 && i > startLine)
                return i + 1;
        }
    }
    return lines.length;
}
function transpileParams(params) {
    if (!params.trim())
        return '';
    return params.split(',').map(p => {
        const [name, type] = p.trim().split(':').map(s => s.trim());
        return `${name}: ${mapType(type)}`;
    }).join(', ');
}
function mapType(quintType) {
    switch (quintType?.trim()) {
        case 'int': return 'number';
        case 'str': return 'string';
        case 'bool': return 'boolean';
        case 'Set': return 'Set<any>';
        default: return quintType || 'any';
    }
}
function transpileBody(body) {
    let ts = body;
    // Strip the function signature line if present (pure def ... = {)
    ts = ts.replace(/^pure\s+def\s+\w+\([^)]*\)\s*:\s*\w+\s*=\s*\{\s*/m, '');
    // Remove trailing }
    ts = ts.replace(/\}\s*$/, '');
    // Normalize — join multiline into single line, collapse whitespace
    ts = ts.replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
    ts = applyQuintRewrites(ts);
    // Clean up comments
    ts = ts.replace(/\/\/.*?(?=\bif\b|\belse\b|$)/g, '');
    // Replace if/else chains → ternary (from innermost out)
    let prev = '';
    let safety = 10;
    while (ts !== prev && safety-- > 0) {
        prev = ts;
        // Match: if (cond) thenExpr else elseExpr
        // thenExpr/elseExpr can be multi-word but stop at 'else' or end
        ts = ts.replace(/if\s*\(([^)]+)\)\s+(.+?)\s+else\s+(.+?)(?=\s*;|\s*$)/g, '(($1) ? ($2) : ($3))');
    }
    return `return ${ts.trim()};`;
}
/**
 * Apply the full sequence of Quint → TypeScript rewrites to an expression
 * string. Ordering matters: method-name rewrites (forall, exists, keys, get)
 * must run BEFORE `transpileImplies`, because the implies walker scans
 * backward through method chains to find the left operand and needs to see
 * JS-shaped method names.
 */
function applyQuintRewrites(ts) {
    // 1. Boolean operators
    ts = ts.replace(/\band\b/g, '&&');
    ts = ts.replace(/\bor\b/g, '||');
    ts = ts.replace(/\bnot\b/g, '!');
    // 2. Local `val` bindings inside blocks
    ts = transpileValBindings(ts);
    // 3. Quint collection operations → JS equivalents
    //    These run before implies so the walker sees valid JS method names.
    // forall / exists with lambda → .every / .some
    ts = ts.replace(/\.forall\(\s*(\w+)\s*=>\s*/g, '.every(($1: any) => ');
    ts = ts.replace(/\.exists\(\s*(\w+)\s*=>\s*/g, '.some(($1: any) => ');
    // filter / map / flatMap: keep the method name but type the lambda param
    ts = ts.replace(/\.filter\(\s*(\w+)\s*=>\s*/g, '.filter(($1: any) => ');
    ts = ts.replace(/\.map\(\s*(\w+)\s*=>\s*/g, '.map(($1: any) => ');
    ts = ts.replace(/\.flatMap\(\s*(\w+)\s*=>\s*/g, '.flatMap(($1: any) => ');
    // .indices() — returns the indices of a list. In Quint this is a Set
    // of positions; in JS the equivalent is an array of 0..length-1.
    ts = ts.replace(/\.indices\(\s*\)/g, '.map((_: any, __i: number) => __i)');
    // Set literal: Set("a", "b") → ["a", "b"]
    ts = ts.replace(/\bSet\(([^)]+)\)/g, '[$1]');
    // subseteq (subset of) → runtime check via .every + .includes.
    // `X.subseteq(Y)` becomes `X.every((__v: any) => Y.includes(__v))`.
    // Uses a balanced walker because X can be an array literal or method chain.
    ts = rewriteSubseteq(ts);
    // Map operations on plain-object data:
    //   X.keys() → Object.keys(X)
    //   X.get(k) → X[k]
    // Quint maps use .keys()/.get(); when the runtime data is a plain object
    // (which is what inference produces), these need JS-equivalent forms.
    ts = rewriteKeysGet(ts);
    // .size() — Quint cardinality for Sets/Lists/Maps. In our runtime
    // snapshot a collection can be an array or a keyed object, so rewrite
    // `X.size()` → `_size(X)` (helper injected into the generated module).
    ts = rewriteSize(ts);
    // .contains → .includes
    ts = ts.replace(/\.contains\(/g, '.includes(');
    // != → !==, == → ===
    ts = ts.replace(/([^!<>])==(?!=)/g, '$1===');
    ts = ts.replace(/!=(?!=)/g, '!==');
    // 4. implies → (!A || B). Runs LAST so the walker sees JS-shaped methods.
    ts = transpileImplies(ts);
    return ts;
}
/**
 * Transpile `A implies B` → `(!(A) || (B))`.
 * A is the boolean expression immediately to the left of `implies`, which may
 * be parenthesized or a bare comparison/member-access chain.
 * B is the rest of the expression up to the next enclosing boundary.
 */
function transpileImplies(ts) {
    let out = ts;
    let guard = 50;
    while (guard-- > 0) {
        const m = out.match(/\bimplies\b/);
        if (!m || m.index === undefined)
            break;
        const idx = m.index;
        // --- find left operand start ---
        let i = idx - 1;
        while (i >= 0 && /\s/.test(out[i]))
            i--;
        if (i < 0)
            break;
        let leftStart;
        if (out[i] === ')') {
            // Balanced parenthesized left operand
            let depth = 1;
            let k = i - 1;
            while (k >= 0 && depth > 0) {
                const ch = out[k];
                if (ch === ')')
                    depth++;
                else if (ch === '(')
                    depth--;
                if (depth === 0)
                    break;
                k--;
            }
            if (depth !== 0)
                break;
            // If the paren is a call paren (preceded by identifier or '.'), keep
            // walking backward through the method chain so the left operand
            // includes the receiver as well.
            let kk = k - 1;
            while (kk >= 0 && /[A-Za-z0-9_.]/.test(out[kk]))
                kk--;
            // If we consumed any chars, it's a method/receiver chain — extend.
            // The consumed range may start with '.' (e.g. `.exists` after `.keys()`)
            // so we can't restrict the first char to a letter.
            if (kk + 1 < k) {
                // Extend through chained receivers: `a.b.c(args)`, `a.b(x).c(y)`
                // (parens), and `[...].c(y)` (array literals).
                let prev = kk;
                while (prev >= 0) {
                    const ch = out[prev];
                    if (/[A-Za-z0-9_.]/.test(ch)) {
                        prev--;
                        continue;
                    }
                    if (ch === ')' || ch === ']') {
                        const close = ch;
                        const open = close === ')' ? '(' : '[';
                        let d = 1;
                        prev--;
                        while (prev >= 0 && d > 0) {
                            if (out[prev] === close)
                                d++;
                            else if (out[prev] === open)
                                d--;
                            if (d === 0)
                                break;
                            prev--;
                        }
                        if (d !== 0)
                            break;
                        prev--;
                        continue;
                    }
                    break;
                }
                leftStart = prev + 1;
            }
            else {
                leftStart = k;
            }
        }
        else {
            // Bare expression — walk backward until we hit a boundary at depth 0
            let depth = 0;
            let k = i;
            while (k >= 0) {
                const ch = out[k];
                if (ch === ')' || ch === ']' || ch === '}')
                    depth++;
                else if (ch === '(' || ch === '[' || ch === '{') {
                    if (depth === 0) {
                        k++;
                        break;
                    }
                    depth--;
                }
                else if (depth === 0) {
                    if (ch === ',' || ch === ';' || ch === '?') {
                        k++;
                        break;
                    }
                    if (k > 0) {
                        const prev = out[k - 1];
                        if ((ch === '&' && prev === '&') || (ch === '|' && prev === '|') || (ch === '>' && prev === '=')) {
                            k++;
                            break;
                        }
                    }
                }
                k--;
            }
            if (k < 0)
                k = 0;
            leftStart = k;
        }
        const left = out.substring(leftStart, i + 1).trim();
        // --- find right operand end ---
        let j = idx + 'implies'.length;
        while (j < out.length && /\s/.test(out[j]))
            j++;
        const rightStart = j;
        let rDepth = 0;
        while (j < out.length) {
            const ch = out[j];
            if (ch === '(' || ch === '[' || ch === '{')
                rDepth++;
            else if (ch === ')' || ch === ']' || ch === '}') {
                if (rDepth === 0)
                    break;
                rDepth--;
            }
            else if (rDepth === 0) {
                if (ch === ',' || ch === ';')
                    break;
                if (j + 1 < out.length) {
                    const next = out[j + 1];
                    if ((ch === '&' && next === '&') || (ch === '|' && next === '|'))
                        break;
                }
            }
            j++;
        }
        const right = out.substring(rightStart, j).trim();
        out = out.substring(0, leftStart) + `(!(${left}) || (${right}))` + out.substring(j);
    }
    return out;
}
/**
 * Rewrite `X.size()` → `_size(X)` where `_size` is a runtime helper
 * that handles both arrays and plain objects (Quint Sets and Maps).
 */
function rewriteSize(ts) {
    let out = ts;
    let guard = 100;
    while (guard-- > 0) {
        const m = out.match(/\.\s*size\s*\(\s*\)/);
        if (!m || m.index === undefined)
            break;
        const dotIdx = m.index;
        const endIdx = dotIdx + m[0].length;
        const receiverStart = findReceiverStart(out, dotIdx);
        if (receiverStart === -1)
            break;
        const receiver = out.substring(receiverStart, dotIdx);
        out = out.substring(0, receiverStart) + `_size(${receiver})` + out.substring(endIdx);
    }
    return out;
}
/**
 * Rewrite `X.subseteq(Y)` → `X.every((__v: any) => Y.includes(__v))`.
 * X can be an array literal (including spaces before the dot) or a method
 * chain; walk backward from `.subseteq(` to find its receiver with balanced
 * brackets.
 */
function rewriteSubseteq(ts) {
    let out = ts;
    let guard = 50;
    while (guard-- > 0) {
        const m = out.match(/\.\s*subseteq\s*\(/);
        if (!m || m.index === undefined)
            break;
        const dotIdx = m.index;
        const openIdx = dotIdx + m[0].length; // position of `(`
        // Walk backward from dotIdx to find receiver start
        let i = dotIdx - 1;
        // skip whitespace
        while (i >= 0 && /\s/.test(out[i]))
            i--;
        if (i < 0)
            break;
        let receiverStart;
        if (out[i] === ']') {
            // Array literal receiver — balance brackets
            let depth = 1;
            let k = i - 1;
            while (k >= 0 && depth > 0) {
                if (out[k] === ']')
                    depth++;
                else if (out[k] === '[')
                    depth--;
                if (depth === 0)
                    break;
                k--;
            }
            if (depth !== 0)
                break;
            receiverStart = k;
        }
        else if (out[i] === ')') {
            // Call-expression receiver — balance parens
            let depth = 1;
            let k = i - 1;
            while (k >= 0 && depth > 0) {
                if (out[k] === ')')
                    depth++;
                else if (out[k] === '(')
                    depth--;
                if (depth === 0)
                    break;
                k--;
            }
            if (depth !== 0)
                break;
            // Then walk back through any identifier chain preceding the `(`
            let kk = k - 1;
            while (kk >= 0 && /[A-Za-z0-9_.]/.test(out[kk]))
                kk--;
            receiverStart = kk + 1;
        }
        else if (/[A-Za-z0-9_]/.test(out[i])) {
            // Bare identifier receiver
            let k = i;
            while (k >= 0 && /[A-Za-z0-9_.]/.test(out[k]))
                k--;
            receiverStart = k + 1;
        }
        else {
            break;
        }
        const receiver = out.substring(receiverStart, i + 1);
        // Find argument end (balanced parens from openIdx)
        let depth = 1;
        let j = openIdx;
        while (j < out.length && depth > 0) {
            const ch = out[j];
            if (ch === '(')
                depth++;
            else if (ch === ')') {
                depth--;
                if (depth === 0)
                    break;
            }
            j++;
        }
        if (depth !== 0)
            break;
        const argStr = out.substring(openIdx, j).trim();
        const replacement = `${receiver}.every((__v: any) => ${argStr}.includes(__v))`;
        out = out.substring(0, receiverStart) + replacement + out.substring(j + 1);
    }
    return out;
}
/**
 * Rewrite `X.keys()` → `Object.keys(X)` and `X.get(k)` → `X[k]`.
 * Quint Maps use `.keys()`/`.get()`; when the runtime data is a plain object
 * (which is what the inferred SpecVerse AST produces), these need JS forms.
 * Walks backward from the method to capture the receiver (identifier chain
 * or nested call).
 */
function rewriteKeysGet(ts) {
    let out = ts;
    // Handle .keys() — no arguments
    let guard = 100;
    while (guard-- > 0) {
        const m = out.match(/\.\s*keys\s*\(\s*\)/);
        if (!m || m.index === undefined)
            break;
        const dotIdx = m.index;
        const endIdx = dotIdx + m[0].length; // one past closing `)`
        // Walk back to receiver start
        const receiverStart = findReceiverStart(out, dotIdx);
        if (receiverStart === -1)
            break;
        const receiver = out.substring(receiverStart, dotIdx);
        out = out.substring(0, receiverStart) + `Object.keys(${receiver})` + out.substring(endIdx);
    }
    // Handle .get(k) — one argument
    guard = 100;
    while (guard-- > 0) {
        const m = out.match(/\.\s*get\s*\(/);
        if (!m || m.index === undefined)
            break;
        const dotIdx = m.index;
        const openIdx = dotIdx + m[0].length; // position after `(`
        // Find closing paren (balanced)
        let depth = 1;
        let j = openIdx;
        while (j < out.length && depth > 0) {
            const ch = out[j];
            if (ch === '(')
                depth++;
            else if (ch === ')') {
                depth--;
                if (depth === 0)
                    break;
            }
            j++;
        }
        if (depth !== 0)
            break;
        const argStr = out.substring(openIdx, j).trim();
        // Walk back to receiver start
        const receiverStart = findReceiverStart(out, dotIdx);
        if (receiverStart === -1)
            break;
        const receiver = out.substring(receiverStart, dotIdx);
        out = out.substring(0, receiverStart) + `${receiver}[${argStr}]` + out.substring(j + 1);
    }
    return out;
}
/**
 * Walk backward from `dotIdx` (the position of `.`) to find where the
 * receiver expression starts. Returns the index of the first char of the
 * receiver, or -1 if it can't be parsed.
 */
function findReceiverStart(s, dotIdx) {
    let i = dotIdx - 1;
    // Skip whitespace
    while (i >= 0 && /\s/.test(s[i]))
        i--;
    if (i < 0)
        return -1;
    if (s[i] === ')' || s[i] === ']') {
        // Balanced close-bracket — walk back to matching open
        const close = s[i];
        const open = close === ')' ? '(' : '[';
        let depth = 1;
        let k = i - 1;
        while (k >= 0 && depth > 0) {
            if (s[k] === close)
                depth++;
            else if (s[k] === open)
                depth--;
            if (depth === 0)
                break;
            k--;
        }
        if (depth !== 0)
            return -1;
        // Then walk back through any identifier chain
        let kk = k - 1;
        while (kk >= 0 && /[A-Za-z0-9_.]/.test(s[kk]))
            kk--;
        return kk + 1;
    }
    if (/[A-Za-z0-9_]/.test(s[i])) {
        let k = i;
        while (k >= 0 && /[A-Za-z0-9_.]/.test(s[k]))
            k--;
        return k + 1;
    }
    return -1;
}
/**
 * Handle Quint local `val` bindings inside a block expression.
 * `{ val X = CALL REST }` → `{ const X = CALL; return REST; }`
 * Only supports a simple call-expression RHS; the block may contain multiple
 * bindings followed by a single result expression.
 */
function transpileValBindings(ts) {
    return ts.replace(/\{\s*val\s+(\w+)\s*=\s*([\w.]+\([^()]*\))\s+(.+?)\s*\}/g, '{ const $1 = $2; return $3; }');
}
function transpileExpression(expr) {
    return applyQuintRewrites(expr);
}
//# sourceMappingURL=quint-transpiler.js.map