import path from "node:path";
export const codeReadDescriptor = {
    tool: "code.read",
    toolId: "code.read",
    route: "agent_executionEngine.basic_toolLayer.baseTools.codeBase.explore",
    purpose: "prepare or run a governed file/code read through an injected reader envelope",
    defaultDispatch: "dry-run",
    unsafeSideEffects: false,
    defaultMaxBytesPerFile: 64 * 1024,
    defaultMaxTotalBytes: 256 * 1024,
};
function isRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function stringValue(value) {
    return typeof value === "string" ? value : undefined;
}
function isBlank(value) {
    return typeof value !== "string" || value.trim().length === 0;
}
function cleanList(values) {
    if (!Array.isArray(values)) {
        return [];
    }
    return [...new Set(values.map((value) => (typeof value === "string" ? value.trim() : "")).filter(Boolean))];
}
function failure(code, message, boundary) {
    return {
        ok: false,
        error: { code, message, boundary, safeForRuntimeInspection: true, internalDetailExposed: false },
        events: ["code.read.rejected"],
    };
}
function toolFailure(code, message, boundary, context) {
    return {
        ok: false,
        toolId: "code.read",
        error: { code, message, boundary, safeForRuntimeInspection: true, internalDetailExposed: false },
        audit: [auditEvent("agentCore.basicTool.code.read.rejected", context, { code, boundary })],
        events: ["code.read.rejected"],
    };
}
function auditEvent(type, context, metadata) {
    return {
        type,
        toolId: "code.read",
        invocationId: stringValue(context?.invocationId)?.trim() || "code.read:dry-run",
        dryRun: context?.dryRun !== false,
        metadata: {
            ...(context?.auditMetadata ?? {}),
            ...(metadata ?? {}),
        },
    };
}
function guardRejected(guard) {
    return guard?.accepted === false || guard?.allowed === false;
}
function normalizeRange(value) {
    if (value === undefined) {
        return undefined;
    }
    if (!isRecord(value)) {
        return failure("INVALID_RANGE", "code.read range must be an object", "input");
    }
    const startLine = value.startLine;
    const endLine = value.endLine;
    if ((startLine !== undefined && (!Number.isInteger(startLine) || Number(startLine) < 1)) ||
        (endLine !== undefined && (!Number.isInteger(endLine) || Number(endLine) < 1)) ||
        (startLine !== undefined && endLine !== undefined && Number(endLine) < Number(startLine))) {
        return failure("INVALID_RANGE", "code.read range must use positive line numbers with endLine >= startLine", "input");
    }
    return {
        startLine: typeof startLine === "number" ? startLine : undefined,
        endLine: typeof endLine === "number" ? endLine : undefined,
    };
}
function normalizePositiveInteger(value, fallback, errorCode) {
    if (value !== undefined && typeof value !== "number") {
        return failure(errorCode, "code.read byte limits must be positive integers", "resource");
    }
    const resolved = value ?? fallback;
    if (!Number.isInteger(resolved) || resolved < 1) {
        return failure(errorCode, "code.read byte limits must be positive integers", "resource");
    }
    return resolved;
}
function normalizeTargetPath(targetPath, workspaceRoot, allowedRoots = []) {
    if (targetPath.includes("\0")) {
        return failure("NUL_BYTE_IN_PATH", "code.read targetPath cannot contain NUL bytes", "input");
    }
    const trimmed = targetPath.trim();
    if (path.isAbsolute(trimmed)) {
        const allowed = [workspaceRoot, ...allowedRoots].filter((root) => typeof root === "string" && root.trim().length > 0);
        const normalizedAbsolute = path.resolve(trimmed);
        if (allowed.length === 0) {
            return failure("ABSOLUTE_TARGET_PATH", "code.read absolute targetPath requires workspaceRoot or allowedRoots", "scope");
        }
        const insideAllowed = allowed.some((root) => {
            const normalizedRoot = path.resolve(root);
            return normalizedAbsolute === normalizedRoot || normalizedAbsolute.startsWith(`${normalizedRoot}${path.sep}`);
        });
        if (!insideAllowed) {
            return failure("TARGET_PATH_OUTSIDE_SCOPE", "code.read targetPath must stay inside workspaceRoot or allowedRoots", "scope");
        }
        return normalizedAbsolute;
    }
    const normalized = path.posix.normalize(trimmed.replaceAll("\\", "/"));
    if (normalized === "." || normalized === ".." || normalized.startsWith("../")) {
        return failure("TARGET_PATH_OUTSIDE_SCOPE", "code.read targetPath must stay inside the workspace scope", "scope");
    }
    return normalized;
}
function normalizeTargets(request, context) {
    const requestTargets = [];
    const workspaceRoot = context.workspaceRoot ?? request.workspaceRoot;
    const allowedRoots = cleanList(context.allowedRoots);
    if (!isBlank(request.targetPath)) {
        const range = normalizeRange(request.range);
        if (range !== undefined && "ok" in range)
            return range;
        const targetPath = normalizeTargetPath(String(request.targetPath), workspaceRoot, allowedRoots);
        if (typeof targetPath !== "string")
            return targetPath;
        requestTargets.push({ targetPath, range });
    }
    if (Array.isArray(request.targetPaths)) {
        for (const rawTarget of request.targetPaths) {
            if (typeof rawTarget !== "string" || rawTarget.trim().length === 0) {
                return failure("INVALID_TARGETS", "code.read targetPaths must contain non-empty strings", "input");
            }
            const targetPath = normalizeTargetPath(rawTarget, workspaceRoot, allowedRoots);
            if (typeof targetPath !== "string")
                return targetPath;
            requestTargets.push({ targetPath });
        }
    }
    if (Array.isArray(request.targets)) {
        for (const rawTarget of request.targets) {
            if (!isRecord(rawTarget) || isBlank(rawTarget.targetPath)) {
                return failure("INVALID_TARGETS", "code.read targets must contain targetPath strings", "input");
            }
            const range = normalizeRange(rawTarget.range);
            if (range !== undefined && "ok" in range)
                return range;
            const targetPath = normalizeTargetPath(String(rawTarget.targetPath), workspaceRoot, allowedRoots);
            if (typeof targetPath !== "string")
                return targetPath;
            requestTargets.push({ targetPath, range });
        }
    }
    if (requestTargets.length === 0) {
        return failure("MISSING_TARGET_PATH", "code.read requires targetPath, targetPaths, or targets", "input");
    }
    const deduped = new Map();
    for (const target of requestTargets) {
        const key = `${target.targetPath}:${target.range?.startLine ?? ""}:${target.range?.endLine ?? ""}`;
        deduped.set(key, target);
    }
    return [...deduped.values()];
}
function resolveAcceptedScopes(requestedScopes, allowedScopes) {
    const requested = cleanList(requestedScopes);
    const allowed = cleanList(allowedScopes);
    if (requested.length === 0) {
        return [];
    }
    const denied = requested.filter((scope) => !allowed.includes(scope));
    if (denied.length > 0) {
        return failure("SCOPE_DENIED", `code.read scope ${denied[0]} is outside runtime governance`, "scope");
    }
    return requested;
}
function normalizeRequest(value) {
    if (!isRecord(value)) {
        return failure("INVALID_REQUEST", "code.read request must be a JSON object", "input");
    }
    const request = value;
    const context = isRecord(request.context) ? request.context : {};
    const governance = request.governance ?? context.guard;
    if (guardRejected(governance)) {
        return failure("GOVERNANCE_REJECTED", governance?.reason ?? "code.read was rejected by runtime governance", "governance");
    }
    const targets = normalizeTargets(request, context);
    if ("ok" in targets)
        return targets;
    const maxBytesPerFile = normalizePositiveInteger(request.maxBytesPerFile ?? request.maxBytes, codeReadDescriptor.defaultMaxBytesPerFile, "INVALID_MAX_BYTES");
    if (typeof maxBytesPerFile !== "number")
        return maxBytesPerFile;
    const maxTotalBytes = normalizePositiveInteger(request.maxTotalBytes, codeReadDescriptor.defaultMaxTotalBytes, "INVALID_MAX_BYTES");
    if (typeof maxTotalBytes !== "number")
        return maxTotalBytes;
    const encoding = request.encoding === undefined ? "utf8" : stringValue(request.encoding)?.trim();
    if (encoding === undefined || encoding.length === 0 || encoding.includes("\0")) {
        return failure("INVALID_ENCODING", "code.read encoding must be a safe string", "input");
    }
    const acceptedScopes = resolveAcceptedScopes(request.requestedScopes ?? context.requestedScopes, request.allowedScopes ?? context.allowedScopes);
    if ("ok" in acceptedScopes)
        return acceptedScopes;
    return {
        toolCallId: request.toolCallId?.trim() || context.invocationId?.trim() || "code.read:dry-run",
        context: {
            ...context,
            dryRun: request.dryRun ?? context.dryRun,
            workspaceRoot: context.workspaceRoot ?? request.workspaceRoot,
        },
        targetPath: targets[0]?.targetPath ?? "",
        targets,
        range: targets[0]?.range,
        maxBytesPerFile,
        maxTotalBytes,
        encoding,
        includeLineNumbers: request.includeLineNumbers ?? false,
        acceptedScopes,
        provider: request.reader ?? request.provider,
        metadata: request.metadata ?? {},
    };
}
function applyRange(content, range) {
    if (range === undefined || (range.startLine === undefined && range.endLine === undefined)) {
        return content;
    }
    const lines = content.split(/\r?\n/u);
    const start = Math.max((range.startLine ?? 1) - 1, 0);
    const end = range.endLine === undefined ? lines.length : range.endLine;
    return lines.slice(start, end).join("\n");
}
function withLineNumbers(content, startLine = 1) {
    return content
        .split(/\r?\n/u)
        .map((line, index) => `${startLine + index}: ${line}`)
        .join("\n");
}
function truncateByBytes(content, encoding, maxBytes) {
    const bytes = Buffer.byteLength(content, encoding);
    if (bytes <= maxBytes) {
        return { content, truncated: false, bytes };
    }
    let end = content.length;
    while (end > 0 && Buffer.byteLength(content.slice(0, end), encoding) > maxBytes) {
        end -= 1;
    }
    const truncatedContent = content.slice(0, end);
    return { content: truncatedContent, truncated: true, bytes: Buffer.byteLength(truncatedContent, encoding) };
}
function buildPlan(normalized) {
    return {
        kind: "agentCore.basicTool.code.read.plan",
        operation: "read-file-or-code-content",
        targetPath: normalized.targetPath,
        targetPaths: normalized.targets.map((target) => target.targetPath),
        range: normalized.range,
        maxBytes: normalized.maxBytesPerFile,
        maxBytesPerFile: normalized.maxBytesPerFile,
        maxTotalBytes: normalized.maxTotalBytes,
        encoding: normalized.encoding,
        includeLineNumbers: normalized.includeLineNumbers,
        dispatch: normalized.context.dryRun === false ? "injected-reader" : "dry-run",
        readsFileSystemDirectly: false,
    };
}
function buildAudit(normalized) {
    return {
        tool: "code.read",
        toolCallId: normalized.toolCallId,
        targetPath: normalized.targetPath,
        targetPaths: normalized.targets.map((target) => target.targetPath),
        workspaceRoot: normalized.context.workspaceRoot,
        requestedScopes: cleanList(normalized.context.requestedScopes),
        acceptedScopes: normalized.acceptedScopes,
        dryRun: normalized.context.dryRun !== false,
        unsafeSideEffects: false,
        metadata: {
            ...(normalized.context.auditMetadata ?? {}),
            ...normalized.metadata,
        },
    };
}
export async function planCodeRead(request = {}) {
    const normalized = normalizeRequest(request);
    if ("ok" in normalized) {
        return normalized;
    }
    const plan = buildPlan(normalized);
    const audit = buildAudit(normalized);
    if (plan.dispatch === "dry-run") {
        return { ok: true, plan, audit, events: ["code.read.planned"] };
    }
    if (normalized.provider === undefined) {
        return failure("READER_NOT_INJECTED", "code.read requires an injected reader when dryRun is false", "provider");
    }
    try {
        const files = [];
        let totalBytes = 0;
        let totalTruncated = false;
        for (const target of normalized.targets) {
            const payload = await normalized.provider({
                targetPath: target.targetPath,
                maxBytes: normalized.maxBytesPerFile,
                encoding: normalized.encoding,
                context: normalized.context,
            });
            const outputEncoding = (payload.encoding ?? normalized.encoding);
            const ranged = applyRange(payload.content, target.range);
            const lineNumbered = normalized.includeLineNumbers ? withLineNumbers(ranged, target.range?.startLine ?? 1) : ranged;
            const perFile = truncateByBytes(lineNumbered, outputEncoding, normalized.maxBytesPerFile);
            const remaining = Math.max(normalized.maxTotalBytes - totalBytes, 0);
            const totalLimited = truncateByBytes(perFile.content, outputEncoding, remaining);
            const bytes = totalLimited.bytes;
            totalBytes += bytes;
            totalTruncated ||= Boolean(payload.truncated) || perFile.truncated || totalLimited.truncated || totalBytes >= normalized.maxTotalBytes;
            files.push({
                targetPath: target.targetPath,
                content: totalLimited.content,
                encoding: outputEncoding,
                bytes,
                truncated: Boolean(payload.truncated) || perFile.truncated || totalLimited.truncated,
                range: target.range,
            });
            if (totalBytes >= normalized.maxTotalBytes) {
                break;
            }
        }
        const content = files.length === 1
            ? files[0]?.content ?? ""
            : files.map((file) => `===== ${file.targetPath} =====\n${file.content}`).join("\n");
        return {
            ok: true,
            plan,
            audit,
            output: {
                kind: "agentCore.basicTool.code.read.output",
                targetPath: normalized.targetPath,
                targetPaths: normalized.targets.map((target) => target.targetPath),
                content,
                files,
                encoding: normalized.encoding,
                bytes: totalBytes,
                truncated: totalTruncated,
                unsafeSideEffects: false,
            },
            events: ["code.read.injectedReaderCompleted"],
        };
    }
    catch (error) {
        void error;
        return failure("READER_REJECTED", "code.read provider rejected the request", "provider");
    }
}
export async function executeCodeRead(request = {}) {
    const result = await planCodeRead(request);
    if (!result.ok) {
        return toolFailure(result.error.code, result.error.message, result.error.boundary, request.context);
    }
    if (result.output === undefined) {
        return {
            ok: true,
            toolId: "code.read",
            output: {
                kind: "agentCore.basicTool.code.read.output",
                targetPath: result.plan.targetPath,
                targetPaths: result.plan.targetPaths,
                content: "",
                files: [],
                encoding: result.plan.encoding,
                bytes: 0,
                truncated: false,
                unsafeSideEffects: false,
            },
            audit: [auditEvent("agentCore.basicTool.code.read.dryRun", request.context, result.audit.metadata)],
            events: ["code.read.dryRun"],
        };
    }
    return {
        ok: true,
        toolId: "code.read",
        output: result.output,
        audit: [auditEvent("agentCore.basicTool.code.read.provider", request.context, result.audit.metadata)],
        events: result.events,
    };
}
