export function validateJsonSchemaArg(expr, scope) {
    switch (expr.type) {
        case "string":
        case "multiLineString":
            return validateStringLiteral(expr, scope);
        case "number":
        case "boolean":
        case "null":
        case "regex":
        case "unitLiteral":
            // Atomic literals — no inner expressions to validate. These can
            // appear after value-arg substitution (e.g. `Timeout(30s)`
            // substituting a unit literal into `{ maximum: ms }`) and must be
            // accepted to stay in sync with `staticTagArgParser`.
            return { ok: true };
        case "agencyObject":
            return validateObjectLiteral(expr, scope);
        case "agencyArray":
            return validateArrayLiteral(expr, scope);
        case "functionCall":
            return validateFunctionCall(expr, scope);
        case "valueAccess":
            return validateValueAccess(expr, scope);
        case "variableName":
            return validateIdentifier(expr, scope);
        default:
            return {
                ok: false,
                reason: `expressions of type "${expr.type}" are not allowed inside @jsonSchema(...)`,
                loc: expr.loc,
            };
    }
}
/**
 * Alias for `validateJsonSchemaArg` — the same restriction set applies
 * to `@validate(...)` and `@jsonSchema(...)` tag arguments. Use this
 * name in new call sites; the legacy export stays for compatibility.
 */
export const validateTagArg = validateJsonSchemaArg;
/**
 * Validate a PFA expression: a `valueAccess` whose chain contains at
 * least one method-call element. A bare `valueAccess` with only
 * property accesses (e.g. `foo.bar`) is rejected — member access is
 * not in the restricted subset.
 */
function validateValueAccess(va, scope) {
    const hasMethodCall = (va.chain ?? []).some((el) => el && el.kind === "methodCall");
    if (!hasMethodCall) {
        return {
            ok: false,
            reason: "tag arguments must be a literal, identifier, object literal, or PFA expression (e.g. min.partial(n: 0))",
            loc: va.loc,
        };
    }
    // The base must be a plain identifier (typically a top-level
    // validator function or imported function). Reject anything else —
    // `foo(1).partial(...)` and `(get()).partial(...)` are NOT static
    // expressions because they call a function at runtime to compute the
    // receiver. This mirrors `_identOrPfaParser` in the parser layer.
    if (va.base?.type !== "variableName") {
        return {
            ok: false,
            reason: "PFA base must be a plain identifier (e.g. `min.partial(n: 0)`, not `min(1).partial(...)`)",
            loc: va.loc,
        };
    }
    const baseRes = validateIdentifier(va.base, scope);
    if (!baseRes.ok)
        return baseRes;
    // Validate each method-call argument as a restricted expression.
    for (const el of va.chain) {
        if (el?.kind !== "methodCall")
            continue;
        const args = el.functionCall?.arguments ?? [];
        for (const arg of args) {
            const inner = arg && arg.type === "namedArgument"
                ? arg.value
                : arg && arg.type === "splat"
                    ? arg.value
                    : arg;
            const r = validateJsonSchemaArg(inner, scope);
            if (!r.ok)
                return r;
        }
    }
    return { ok: true };
}
function validateStringLiteral(str, scope) {
    const segments = str.segments ?? [];
    for (const seg of segments) {
        if (seg.type === "text")
            continue;
        if (seg.type !== "interpolation") {
            return {
                ok: false,
                reason: `unexpected string segment type "${seg.type}" in @jsonSchema(...)`,
                loc: str.loc,
            };
        }
        // Identifier-only interpolation: the segment expression must be a
        // bare `variableName` that resolves to one of the alias's value
        // parameters. Top-level static consts are intentionally rejected
        // here: tag-arg strings are emitted into the generated schema's
        // `.meta(...)` chain, which is built inside node bodies where
        // module-level Agency consts are not bound to JS identifiers
        // (they live in `__ctx.globals`). Forwarding them would require
        // an extra codegen lowering. Value-param identifiers are folded
        // away at substitution time so the post-substitution string is
        // always a plain literal.
        const expr = seg.expression;
        if (!expr || expr.type !== "variableName") {
            return {
                ok: false,
                reason: "tag-arg string interpolation only accepts a value-param identifier (e.g. `${divisor}`); arbitrary expressions are not allowed",
                loc: str.loc,
            };
        }
        const name = expr.value;
        if (!scope.valueParamNames || !scope.valueParamNames.has(name)) {
            return {
                ok: false,
                reason: `'${name}' is not a value parameter of this alias — only value-param identifiers may appear in tag-arg string interpolation`,
                loc: str.loc,
            };
        }
    }
    return { ok: true };
}
function validateArrayLiteral(arr, scope) {
    for (const item of arr.items ?? []) {
        const inner = item.type === "splat"
            ? item.value
            : item;
        const r = validateJsonSchemaArg(inner, scope);
        if (!r.ok)
            return r;
    }
    return { ok: true };
}
function validateObjectLiteral(obj, scope) {
    for (const entry of obj.entries) {
        if ("key" in entry) {
            const kv = entry;
            if (kv.computedKey) {
                return {
                    ok: false,
                    reason: "computed object keys are not allowed inside @jsonSchema(...)",
                    loc: kv.computedKey.loc,
                };
            }
            const r = validateJsonSchemaArg(kv.value, scope);
            if (!r.ok)
                return r;
        }
        else {
            const splat = entry;
            const r = validateJsonSchemaArg(splat.value, scope);
            if (!r.ok)
                return r;
        }
    }
    return { ok: true };
}
function validateFunctionCall(call, scope) {
    // The callee must be a known top-level / imported function.
    const name = call.functionName;
    if (!name) {
        return {
            ok: false,
            reason: "function call inside @jsonSchema(...) must be a direct call by name",
            loc: call.loc,
        };
    }
    if (!scope.topLevelFunctionNames.has(name) && !scope.importedNames.has(name)) {
        return {
            ok: false,
            reason: `${name} is not a top-level def or imported function; cannot be used inside @jsonSchema(...)`,
            loc: call.loc,
        };
    }
    // Validate each argument as a restricted expression.
    for (const arg of call.arguments ?? []) {
        const r = validateJsonSchemaArg(arg, scope);
        if (!r.ok)
            return r;
    }
    return { ok: true };
}
function validateIdentifier(id, scope) {
    const name = id.value;
    if (scope.topLevelConstNames.has(name) ||
        scope.importedNames.has(name) ||
        scope.topLevelFunctionNames.has(name) ||
        (scope.valueParamNames && scope.valueParamNames.has(name))) {
        return { ok: true };
    }
    return {
        ok: false,
        reason: `${name} is not a static const global; only top-level const bindings or imports may be used inside @jsonSchema(...)`,
        loc: id.loc,
    };
}
/**
 * Build the scope view needed for validation from a CompilationUnit
 * and the original AgencyProgram (we need the program to walk top-level
 * `const` assignments — CompilationUnit doesn't retain them directly).
 */
export function buildJsonSchemaArgScope(program, unit) {
    const topLevelConstNames = new Set();
    const topLevelFunctionNames = new Set();
    for (const node of program.nodes) {
        if (node.type === "assignment") {
            const n = node;
            if (n.declKind === "const" || n.static === true) {
                const name = n.variableName ?? n.name;
                if (typeof name === "string")
                    topLevelConstNames.add(name);
            }
        }
        if (node.type === "function") {
            const name = node.functionName;
            if (name)
                topLevelFunctionNames.add(name);
        }
    }
    const importedNames = new Set(Object.keys(unit.importedFunctions));
    for (const stmt of unit.importStatements) {
        collectImportedNames(stmt, importedNames);
    }
    return { topLevelConstNames, importedNames, topLevelFunctionNames };
}
function collectImportedNames(stmt, out) {
    const items = stmt?.importedNames;
    if (!Array.isArray(items))
        return;
    for (const n of items) {
        if (typeof n === "string") {
            out.add(n);
        }
        else if (n && typeof n === "object") {
            addNamedImportEntry(n, out);
        }
    }
}
function addNamedImportEntry(entry, out) {
    const arr = entry.importedNames;
    const aliases = entry.aliases;
    if (Array.isArray(arr)) {
        for (const raw of arr) {
            if (typeof raw === "string") {
                out.add((aliases && aliases[raw]) ?? raw);
            }
        }
        return;
    }
    if (typeof arr === "string") {
        out.add((aliases && aliases[arr]) ?? arr);
        return;
    }
    const local = entry.local ??
        entry.name ??
        entry.originalName ??
        entry.alias;
    if (typeof local === "string")
        out.add(local);
}
