import { success, failure, isFailure, isSuccess } from "./result.js";
import { AgencyFunction } from "./agencyFunction.js";
async function callValidator(v, value) {
    if (AgencyFunction.isAgencyFunction(v)) {
        return (await v.invoke({
            type: "positional",
            args: [value],
        }));
    }
    return v(value);
}
/**
 * Run Zod parse then thread the result through validators in order,
 * stopping on the first failure. Validators may transform: the value
 * passed to validator N+1 is whatever validator N returned with success.
 *
 * Why outside Zod? See spec § "Why run validators outside Zod" —
 * keeping validators out of the sync Zod path lets them be async,
 * call other Agency functions, hit the network, etc.
 */
export async function __validateChain(value, schema, validators) {
    // Don't validate failures — surface the original error unchanged.
    if (isFailure(value))
        return value;
    const parsed = schema.safeParse(value);
    if (!parsed.success) {
        return failure(parsed.error.message);
    }
    let raw = parsed.data;
    // If the parsed value is itself a Result, unwrap on success / forward
    // failure unchanged, matching the convention in __validateType.
    if (isSuccess(raw))
        raw = raw.value;
    else if (isFailure(raw))
        return raw;
    let current = success(raw);
    for (const v of validators) {
        if (!isSuccess(current))
            return current;
        current = await callValidator(v, current.value);
    }
    return current;
}
/**
 * Walk `value` alongside `descriptor`, running per-node validator chains
 * and recursing into nested types. Bails with a failure once depth
 * exceeds `opts.maxDepth ?? 64`.
 */
export async function __validateChainRecursive(value, descriptor, opts) {
    const maxDepth = opts?.maxDepth ?? 64;
    return walk(value, descriptor, 0, maxDepth);
}
async function walk(value, descriptor, depth, maxDepth) {
    if (depth > maxDepth) {
        return failure({
            reason: "validation recursion depth exceeded",
            limit: maxDepth,
            kind: descriptor.kind,
            valuePreview: previewValue(value),
        });
    }
    if (isFailure(value))
        return value;
    // Step 1: parse + own validators at this node.
    const own = await __validateChain(value, descriptor.schema, descriptor.validators);
    if (!isSuccess(own))
        return own;
    const parsed = own.value;
    // Step 2: structural recursion.
    switch (descriptor.kind) {
        case "leaf":
            return success(parsed);
        case "nullable": {
            if (parsed === null || parsed === undefined) {
                return success(parsed);
            }
            const inner = await walk(parsed, descriptor.inner, depth + 1, maxDepth);
            return inner;
        }
        case "array": {
            if (!Array.isArray(parsed))
                return success(parsed);
            const out = [];
            for (const el of parsed) {
                const r = await walk(el, descriptor.element, depth + 1, maxDepth);
                if (!isSuccess(r))
                    return r;
                out.push(r.value);
            }
            return success(out);
        }
        case "object": {
            if (parsed === null || typeof parsed !== "object") {
                return success(parsed);
            }
            const out = { ...parsed };
            for (const [key, childDesc] of Object.entries(descriptor.properties)) {
                const r = await walk(parsed[key], childDesc, depth + 1, maxDepth);
                if (!isSuccess(r))
                    return r;
                out[key] = r.value;
            }
            return success(out);
        }
        case "union": {
            const branch = descriptor.branches.find((b) => b.test(parsed));
            if (!branch)
                return success(parsed);
            return walk(parsed, branch.descriptor, depth + 1, maxDepth);
        }
    }
}
/**
 * Return a short, cycle-safe, JSON-friendly preview of a value for use
 * inside error payloads. We don't want huge or self-referential structures
 * to blow up the failure message.
 */
function previewValue(value, maxLen = 200) {
    const seen = new WeakSet();
    const replacer = (_k, v) => {
        if (typeof v === "object" && v !== null) {
            if (seen.has(v))
                return "[cycle]";
            seen.add(v);
        }
        return v;
    };
    let str;
    try {
        str = JSON.stringify(value, replacer);
    }
    catch {
        return String(value).slice(0, maxLen);
    }
    if (str === undefined)
        return String(value).slice(0, maxLen);
    return str.length > maxLen ? `${str.slice(0, maxLen)}…` : str;
}
