/**
 * Method-body walker — shared infrastructure for #27 (behavior step
 * extraction) and #28-lifecycle Tier B (transition graph extraction).
 *
 * Given a class body string, produces per-method fact sheets: each
 * method's `this.X = Y` assignments, `if (this.X op Y) throw` guards,
 * external calls, throws, and explicit returns. The walker itself is
 * pattern-matching only — interpretation (which call is a "step", how
 * a guard pairs with an assignment to form a transition) is the
 * caller's responsibility.
 *
 * Language assumption: TypeScript / JavaScript. Relies on regex-based
 * parsing rather than a full AST walker — pragmatic for the patterns
 * we care about (decorator-style classes with method bodies that
 * follow the realize-stack discipline). When this stops being enough
 * (genuinely complex flow control, ternary state mutations, etc.) we
 * upgrade to tree-sitter via the CodeGraph backend.
 *
 * NOT intended to be exhaustive. The "soft oracle" stance from
 * 2026-04-26-BEHAVIOR-EXTRACTION-FOR-ANALYSE.md applies: extract what
 * the patterns clearly say, fall through to LLM for the rest.
 */
/**
 * Walk all methods in a class body, returning per-method facts.
 *
 * Method extraction is pattern-based: we match either
 *   - explicit `<methodName>(<params>): <ret> {` declarations, or
 *   - shorthand `<methodName>(<params>) {` (no return type)
 *   - `async <methodName>` and `public/private/protected` modifiers
 *
 * Constructor + property initialisers are skipped — only "real"
 * methods. Tightening the body extraction (matching the exact closing
 * brace via brace counting) keeps multi-method classes from crossing
 * boundaries.
 */
export function walkMethodBodies(classBody) {
    const out = [];
    // Find candidate method-declaration lines. Decorator-style params
    // like `@Param('id') id: string` carry nested parens, so a naive
    // `\([^)]*\)` regex stops at the first `)` and misses the method.
    // Walk the source: match the START of a declaration, then balance
    // parens forward to find the param-list close, then scan to `{`.
    //
    // The optional `(?:[\w$]+(?:<[^>]*>)?(?:\[\])?\s+)?` group consumes
    // a Java-style return type when present (`public void send(...)`).
    // For TypeScript / NestJS, no return type appears before the name;
    // the optional group fails to match (no whitespace after the
    // would-be type) and falls through.
    const startRe = /^\s*(?:(?:public|private|protected|static|abstract|final|synchronized)\s+)*(async\s+)?(?:(?:[A-Za-z_$][\w$]*(?:<[^>]*>)?(?:\[\])?)\s+)?([a-zA-Z_$][\w$]*)\s*\(/gm;
    let m;
    while ((m = startRe.exec(classBody)) !== null) {
        const isAsync = !!m[1];
        const methodName = m[2];
        if (methodName === 'constructor')
            continue;
        if (/^(if|for|while|switch|catch|return|new|typeof|throw|await|async)$/.test(methodName))
            continue;
        // Find matching close-paren for the param list (nested-paren aware).
        const paramOpenIdx = m.index + m[0].length - 1; // position of `(`
        const paramCloseIdx = findMatchingParen(classBody, paramOpenIdx);
        if (paramCloseIdx < 0)
            continue;
        // After the close-paren, an optional return type annotation `: <type>`
        // and then the opening `{` of the body. Skip whitespace + comments.
        let i = paramCloseIdx + 1;
        // Skip whitespace.
        while (i < classBody.length && /\s/.test(classBody[i]))
            i++;
        // Skip optional return annotation (everything from `:` up to but not
        // including the opening `{` — we tolerate union types, generics, etc.)
        if (classBody[i] === ':') {
            // Walk until we hit the first `{` at the top level (not inside
            // <generic> or [array] or another type expression).
            i++;
            let angleDepth = 0;
            let bracketDepth = 0;
            while (i < classBody.length) {
                const c = classBody[i];
                if (c === '<')
                    angleDepth++;
                else if (c === '>')
                    angleDepth--;
                else if (c === '[')
                    bracketDepth++;
                else if (c === ']')
                    bracketDepth--;
                else if (c === '{' && angleDepth <= 0 && bracketDepth <= 0)
                    break;
                else if (c === ';')
                    break; // type alias-ish — definitely not a method
                i++;
            }
        }
        if (classBody[i] !== '{')
            continue;
        const bodyOpenIdx = i;
        const bodyEnd = findMatchingBrace(classBody, bodyOpenIdx);
        if (bodyEnd < 0)
            continue;
        const body = classBody.slice(bodyOpenIdx + 1, bodyEnd);
        // Source line range — count newlines from start to method-decl start
        // (m.index) and to body-close brace (bodyEnd). 1-based, inclusive.
        const startLine = countNewlines(classBody, 0, m.index) + 1;
        const endLine = countNewlines(classBody, 0, bodyEnd) + 1;
        out.push({
            methodName,
            body,
            isAsync,
            sourceLineRange: [startLine, endLine],
            thisAssignments: extractThisAssignments(body),
            guards: extractGuards(body),
            calls: extractCalls(body),
            throws: extractThrows(body),
            returns: extractReturns(body),
        });
    }
    return out;
}
/** Find the index of the `)` matching the `(` at `openIdx`. -1 if not found.
 *  Same string + comment awareness as `findMatchingBrace`. */
function findMatchingParen(src, openIdx) {
    let depth = 0;
    let i = openIdx;
    let stringChar = null;
    let inLineComment = false;
    let inBlockComment = false;
    while (i < src.length) {
        const c = src[i];
        const n = src[i + 1];
        if (inLineComment) {
            if (c === '\n')
                inLineComment = false;
            i++;
            continue;
        }
        if (inBlockComment) {
            if (c === '*' && n === '/') {
                inBlockComment = false;
                i += 2;
                continue;
            }
            i++;
            continue;
        }
        if (stringChar !== null) {
            if (c === '\\' && i + 1 < src.length) {
                i += 2;
                continue;
            }
            if (c === stringChar)
                stringChar = null;
            i++;
            continue;
        }
        if (c === '/' && n === '/') {
            inLineComment = true;
            i += 2;
            continue;
        }
        if (c === '/' && n === '*') {
            inBlockComment = true;
            i += 2;
            continue;
        }
        if (c === '"' || c === "'" || c === '`') {
            stringChar = c;
            i++;
            continue;
        }
        if (c === '(') {
            depth++;
            i++;
            continue;
        }
        if (c === ')') {
            depth--;
            if (depth === 0)
                return i;
            i++;
            continue;
        }
        i++;
    }
    return -1;
}
/** Find the index of the `}` matching the `{` at `openIdx`. -1 if not found.
 *  Tracks string literals + comments so embedded braces don't fool the count. */
function findMatchingBrace(src, openIdx) {
    let depth = 0;
    let i = openIdx;
    let stringChar = null;
    let inLineComment = false;
    let inBlockComment = false;
    while (i < src.length) {
        const c = src[i];
        const n = src[i + 1];
        if (inLineComment) {
            if (c === '\n')
                inLineComment = false;
            i++;
            continue;
        }
        if (inBlockComment) {
            if (c === '*' && n === '/') {
                inBlockComment = false;
                i += 2;
                continue;
            }
            i++;
            continue;
        }
        if (stringChar !== null) {
            if (c === '\\' && i + 1 < src.length) {
                i += 2;
                continue;
            }
            if (c === stringChar)
                stringChar = null;
            i++;
            continue;
        }
        // Outside string/comment.
        if (c === '/' && n === '/') {
            inLineComment = true;
            i += 2;
            continue;
        }
        if (c === '/' && n === '*') {
            inBlockComment = true;
            i += 2;
            continue;
        }
        if (c === '"' || c === "'" || c === '`') {
            stringChar = c;
            i++;
            continue;
        }
        if (c === '{') {
            depth++;
            i++;
            continue;
        }
        if (c === '}') {
            depth--;
            if (depth === 0)
                return i;
            i++;
            continue;
        }
        i++;
    }
    return -1;
}
/** Strip comments only — string contents are LEFT INTACT so captured
 *  RHS values like `'Sent'` survive the regex pass. The brace-balanced
 *  scan in `findMatchingBrace` is what handles string-aware structural
 *  parsing; this helper just kills comment noise.
 *
 *  Trade-off: a string literal containing JS-syntax-shaped text (e.g.
 *  `const x = "this.status = 'Open';"` inside a string) will be scanned
 *  by the regex extractors. Acceptable for the realize-stack-style code
 *  we target — disciplined entity classes don't carry shape-shifted
 *  literals, and the soft-oracle stance is that the LLM filters
 *  occasional false positives downstream. */
function stripComments(src) {
    let out = '';
    let i = 0;
    let stringChar = null;
    let inLineComment = false;
    let inBlockComment = false;
    while (i < src.length) {
        const c = src[i];
        const n = src[i + 1];
        if (inLineComment) {
            if (c === '\n') {
                inLineComment = false;
                out += '\n';
            }
            else
                out += ' ';
            i++;
            continue;
        }
        if (inBlockComment) {
            if (c === '*' && n === '/') {
                inBlockComment = false;
                out += '  ';
                i += 2;
                continue;
            }
            out += c === '\n' ? '\n' : ' ';
            i++;
            continue;
        }
        // Inside a string — pass through unchanged (this is the diff from
        // the previous "strip strings too" approach). Track escape pairs
        // so a `\"` doesn't close the string early.
        if (stringChar !== null) {
            if (c === '\\' && i + 1 < src.length) {
                out += c + src[i + 1];
                i += 2;
                continue;
            }
            if (c === stringChar)
                stringChar = null;
            out += c;
            i++;
            continue;
        }
        if (c === '/' && n === '/') {
            inLineComment = true;
            out += '  ';
            i += 2;
            continue;
        }
        if (c === '/' && n === '*') {
            inBlockComment = true;
            out += '  ';
            i += 2;
            continue;
        }
        if (c === '"' || c === "'" || c === '`') {
            stringChar = c;
            out += c;
            i++;
            continue;
        }
        out += c;
        i++;
    }
    return out;
}
function extractThisAssignments(body) {
    const cleaned = stripComments(body);
    const out = [];
    const re = /\bthis\.([a-zA-Z_$][\w$]*)\s*=(?!=)\s*([^;]+);/g;
    let m;
    while ((m = re.exec(cleaned)) !== null) {
        const field = m[1];
        const valueExpr = m[2].trim();
        const enumMatch = valueExpr.match(/^([A-Z][\w$]*)\.([A-Z][\w$]*)\b/);
        out.push({
            position: m.index,
            field,
            valueExpr,
            enumName: enumMatch?.[1],
            enumMember: enumMatch?.[2],
        });
    }
    return out;
}
function extractGuards(body) {
    const cleaned = stripComments(body);
    const out = [];
    const re = /\bif\s*\(\s*this\.([a-zA-Z_$][\w$]*)\s*(!==|===|!=|==)\s*([^)]+?)\s*\)\s*([{][^}]*[}]|[^;{}]+;)/g;
    let m;
    while ((m = re.exec(cleaned)) !== null) {
        const field = m[1];
        const op = m[2];
        const valueExpr = m[3].trim();
        const guardBody = m[4];
        const throws = /\bthrow\b/.test(guardBody);
        const enumMatch = valueExpr.match(/^([A-Z][\w$]*)\.([A-Z][\w$]*)\b/);
        out.push({
            position: m.index,
            field,
            op,
            valueExpr,
            enumName: enumMatch?.[1],
            enumMember: enumMatch?.[2],
            throws,
        });
    }
    return out;
}
function extractCalls(body) {
    const cleaned = stripComments(body);
    const out = [];
    const re = /(\bawait\s+)?((?:this|[a-zA-Z_$][\w$]*)(?:\.[a-zA-Z_$][\w$]*)*)\.([a-zA-Z_$][\w$]*)\s*\(([^()]*(?:\([^()]*\)[^()]*)*)\)/g;
    let m;
    while ((m = re.exec(cleaned)) !== null) {
        const isAwait = !!m[1];
        const receiver = m[2];
        const method = m[3];
        out.push({
            position: m.index,
            receiver,
            method,
            argsExpr: m[4].trim(),
            isAwait,
        });
    }
    return out;
}
function extractThrows(body) {
    const cleaned = stripComments(body);
    const out = [];
    const reNew = /\bthrow\s+new\s+([A-Z][\w$]*)\s*\(([^)]*)\)/g;
    let m;
    while ((m = reNew.exec(cleaned)) !== null) {
        out.push({
            position: m.index,
            errorClass: m[1],
            argsExpr: m[2].trim() || undefined,
        });
    }
    return out;
}
function extractReturns(body) {
    const cleaned = stripComments(body);
    const out = [];
    const re = /\breturn\b\s*([^;]*);/g;
    let m;
    while ((m = re.exec(cleaned)) !== null) {
        out.push({
            position: m.index,
            valueExpr: m[1].trim(),
        });
    }
    return out;
}
/** Count newlines in `src.slice(start, end)`. 0 means same line. */
function countNewlines(src, start, end) {
    let count = 0;
    for (let i = start; i < end && i < src.length; i++) {
        if (src.charCodeAt(i) === 10)
            count++;
    }
    return count;
}
//# sourceMappingURL=method-body-walker.js.map