import { scopeKey } from "../../compilationUnit.js";
/**
 * Owns the lexical scope stack and answers scope-related queries.
 *
 * The TypeScriptBuilder pushes a new scope every time it descends into a
 * function, graph node, or block body, and pops on exit. This class
 * centralises that bookkeeping plus the read-only queries that depend on
 * the current scope (return type, visible type aliases, scope-key for
 * symbol lookups, …) so callers can use a declarative API instead of
 * touching a raw array.
 *
 * `inSafeFunction` is tracked here because "safety" is a property of the
 * function currently being emitted, which conceptually belongs to the
 * scope stack.
 */
export class ScopeManager {
    compilationUnit;
    stack = [{ type: "global" }];
    _inSafeFunction = false;
    constructor(compilationUnit) {
        this.compilationUnit = compilationUnit;
    }
    // ---- Scope stack ----
    push(scope) {
        this.stack.push(scope);
    }
    pop() {
        this.stack.pop();
    }
    current() {
        return this.stack[this.stack.length - 1];
    }
    /** Stable string key for the current scope (used for symbol/type-alias lookups). */
    currentKey() {
        return scopeKey(this.current());
    }
    /** Function, node, or block name; empty string for global. */
    currentName() {
        const scope = this.current();
        if (scope.type === "function")
            return scope.functionName;
        if (scope.type === "node")
            return scope.nodeName;
        if (scope.type === "block")
            return scope.blockName;
        return "";
    }
    // ---- Safe-function flag ----
    get inSafeFunction() {
        return this._inSafeFunction;
    }
    set inSafeFunction(value) {
        this._inSafeFunction = value;
    }
    // ---- Compilation-unit-backed queries ----
    visibleTypeAliases() {
        const entries = this.compilationUnit.typeAliases.visibleIn(this.currentKey());
        // Renderers (typeToString, typeToZodSchema) only need the alias body,
        // not its type parameters. Flatten TypeAliasEntry → VariableType here
        // so those modules keep their existing signature.
        const out = {};
        for (const [name, entry] of Object.entries(entries)) {
            out[name] = entry.body;
        }
        return out;
    }
    /**
     * Full TypeAliasEntry-form alias registry for callers (resolveTypeDeep)
     * that need type-parameter metadata to substitute user-defined generic
     * aliases.
     */
    visibleTypeAliasesFull() {
        return this.compilationUnit.typeAliases.visibleIn(this.currentKey());
    }
    /**
     * Declared return type of the currently-executing function or graph node,
     * or `undefined` for global scope, a function with no annotation, or a
     * block scope.
     *
     * Block scopes (`fork(...) as item { ... }`, `guard(cost: $X) as { ... }`,
     * any `as { ... }` body) currently return `undefined` because we don't
     * carry the block's declared return type onto `Scope.block`. The only
     * caller that matters is `processLlmCall` for a `return llm(...)` inside
     * a block — and `processLlmCall` already defaults `undefined` to
     * `string` for structured-output inference. So today, a bare
     * `return llm(...)` from a block is always typed as `string`.
     *
     * Propagating the block's declared return type (from the enclosing
     * function's block-parameter signature) into the builder would require
     * either threading the parameter type through every `scopes.push({
     * type: "block", ... })` site or looking it up via the typechecker —
     * both substantial. Documented in docs/site/guide/llm.md as a known
     * limitation; users who want a non-string structured type should
     * assign the call: `const x: Foo = llm(...); return x`.
     */
    returnType() {
        const scope = this.current();
        switch (scope.type) {
            case "global":
                return undefined;
            case "function": {
                const funcDef = this.compilationUnit.functionDefinitions[scope.functionName];
                return funcDef?.returnType ?? undefined;
            }
            case "node": {
                const graphNode = this.compilationUnit.graphNodes.find((n) => n.nodeName === scope.nodeName);
                return graphNode?.returnType ?? undefined;
            }
            case "block":
                return undefined;
            default:
                throw new Error(`Unknown scope type: ${scope.type}`);
        }
    }
    /** Whether the surrounding function/node opted-in to runtime return-type validation. */
    returnTypeValidated() {
        const scope = this.current();
        switch (scope.type) {
            case "function": {
                const funcDef = this.compilationUnit.functionDefinitions[scope.functionName];
                return !!funcDef?.returnTypeValidated;
            }
            case "node": {
                const graphNode = this.compilationUnit.graphNodes.find((n) => n.nodeName === scope.nodeName);
                return !!graphNode?.returnTypeValidated;
            }
            default:
                return false;
        }
    }
}
