/*
 * 文件定位：ToolabilityPool / TAP 高级工具系统 / Office 工具集合 / 演示文稿工具。
 * 核心目的：提供 办公文档基础工具 / 演示文稿工具 中的“解码演示文稿”基础能力原语。
 * 能力要求1：需要定义该能力的输入、输出、错误、权限需求和可观测事件。
 * 能力要求2：这些 Office 能力由 TAP 高级工具系统承接，不再作为 baseTool 完成目标。
 * 能力要求3：TAP 负责 Office 能力的治理、审批、组合和专业能力库承接。
 * 边界：承接 Office 高级工具能力，不回写到 agentCore baseTools。
 * 对接：通过 TAP 复用转交与 runtime 官方模块桥接接通，并保留治理、审批和审计边界。
 * 实现提示：先补稳定类型契约、最小可测行为和清晰错误边界，再接入真实执行逻辑。
 */
import path from "node:path";
export const officeSlidesDecodeDescriptor = {
    tool: "office.slidesDecode",
    route: "toolabilityPool.officeBase.presentations",
    purpose: "plan a governed presentation decode through an injected decoder envelope",
    permissionsRequired: ["filesystem:read", "office:read"],
    defaultDispatch: "dry-run",
    unsafeSideEffects: false,
    tapOwnsApproval: true,
};
function cleanList(values) {
    return [...new Set((values ?? []).map((value) => value.trim()).filter(Boolean))];
}
function dryRunEnabled(context) {
    return context?.dryRun !== false;
}
function auditFor(context, presentationPath, acceptedScopes = []) {
    return {
        tool: "office.slidesDecode",
        toolCallId: context?.toolCallId?.trim() || "office.slidesDecode:dry-run",
        presentationPath,
        workspaceRoot: context?.workspaceRoot?.trim() || undefined,
        dryRun: dryRunEnabled(context),
        requestedScopes: cleanList(context?.requestedScopes),
        acceptedScopes,
        permissionsRequired: officeSlidesDecodeDescriptor.permissionsRequired,
        unsafeSideEffects: false,
        metadata: context?.auditMetadata ?? {},
    };
}
function failure(code, message, boundary, context, presentationPath) {
    return {
        ok: false,
        error: { code, message, boundary, publicSafe: true },
        audit: auditFor(context, presentationPath),
        events: ["office.slidesDecode.rejected"],
    };
}
function normalizePresentationPath(presentationPath, context) {
    const rawPath = presentationPath?.trim() ?? "";
    if (rawPath.length === 0) {
        return failure("MISSING_PRESENTATION_PATH", "office.slidesDecode requires presentationPath", "input", context, presentationPath);
    }
    if (rawPath.includes("\0")) {
        return failure("NUL_BYTE_IN_PATH", "office.slidesDecode presentationPath cannot contain NUL bytes", "input", context);
    }
    const normalized = path.posix.normalize(rawPath.replaceAll("\\", "/"));
    if (path.posix.isAbsolute(normalized) || /^[A-Za-z]:\//.test(normalized)) {
        return failure("ABSOLUTE_PRESENTATION_PATH", "office.slidesDecode only accepts workspace-relative presentationPath", "scope", context, normalized);
    }
    if (normalized === ".." || normalized.startsWith("../")) {
        return failure("PRESENTATION_PATH_OUTSIDE_SCOPE", "office.slidesDecode presentationPath must stay inside workspace scope", "scope", context, normalized);
    }
    const allowedRoots = cleanList(context?.allowedPresentationRoots).map((root) => path.posix.normalize(root.replaceAll("\\", "/")));
    if (allowedRoots.length > 0) {
        const inScope = allowedRoots.some((root) => root === "." || normalized === root || normalized.startsWith(`${root}/`));
        if (!inScope) {
            return failure("PRESENTATION_PATH_OUTSIDE_SCOPE", "office.slidesDecode presentationPath is outside allowed presentation roots", "scope", context, normalized);
        }
    }
    return normalized;
}
function normalizeMaxSlides(maxSlides, context) {
    const resolved = maxSlides ?? 30;
    if (!Number.isInteger(resolved) || resolved < 1) {
        return failure("INVALID_MAX_SLIDES", "office.slidesDecode maxSlides must be a positive integer", "input", context);
    }
    return resolved;
}
function resolveScopes(context) {
    const requested = cleanList(context?.requestedScopes);
    const allowed = cleanList(context?.allowedScopes);
    if (requested.length === 0) {
        return [];
    }
    const denied = requested.filter((scope) => !allowed.includes(scope));
    if (denied.length > 0) {
        return failure("SCOPE_DENIED", `office.slidesDecode scope ${denied[0]} is outside runtime governance`, "scope", context);
    }
    return requested;
}
function ensurePermissions(context, presentationPath) {
    const granted = cleanList(context?.grantedPermissions);
    if (granted.length === 0) {
        return undefined;
    }
    const missing = officeSlidesDecodeDescriptor.permissionsRequired.filter((permission) => !granted.includes(permission));
    if (missing.length > 0) {
        return failure("PERMISSION_DENIED", `office.slidesDecode is missing permissions: ${missing.join(", ")}`, "permission", context, presentationPath);
    }
    return undefined;
}
function buildCommandPreview(request) {
    return [
        "office-slides-decode",
        "--max-slides",
        String(request.maxSlides),
        ...(request.includeSpeakerNotes ? ["--speaker-notes"] : []),
        ...(request.includeImages ? ["--images"] : []),
        "--",
        request.presentationPath,
    ];
}
export async function planOfficeSlidesDecode(request = {}) {
    if (request.context?.governance?.accepted === false) {
        return failure("GOVERNANCE_REJECTED", request.context.governance.reason ?? "office.slidesDecode was rejected by runtime governance", "governance", request.context);
    }
    const presentationPath = normalizePresentationPath(request.presentationPath, request.context);
    if (typeof presentationPath !== "string") {
        return presentationPath;
    }
    const maxSlides = normalizeMaxSlides(request.maxSlides, request.context);
    if (typeof maxSlides !== "number") {
        return maxSlides;
    }
    const acceptedScopes = resolveScopes(request.context);
    if ("ok" in acceptedScopes) {
        return acceptedScopes;
    }
    const permissionFailure = ensurePermissions(request.context, presentationPath);
    if (permissionFailure !== undefined) {
        return permissionFailure;
    }
    const dispatch = dryRunEnabled(request.context) ? "dry-run" : "injected-decoder";
    if (dispatch === "injected-decoder" && request.decoder === undefined) {
        return failure("DECODER_NOT_INJECTED", "office.slidesDecode requires an injected decoder when dryRun is false", "execution", request.context, presentationPath);
    }
    const includeSpeakerNotes = request.includeSpeakerNotes === true;
    const includeImages = request.includeImages === true;
    const commandPreview = buildCommandPreview({ presentationPath, maxSlides, includeSpeakerNotes, includeImages });
    const plan = {
        kind: "agentCore.basicTool.office.slidesDecode.plan",
        operation: "presentation-decode",
        presentationPath,
        maxSlides,
        includeSpeakerNotes,
        includeImages,
        commandPreview,
        dispatch,
        readsFileDirectly: false,
    };
    const audit = auditFor(request.context, presentationPath, acceptedScopes);
    if (dispatch === "dry-run") {
        return { ok: true, plan, audit, events: ["office.slidesDecode.planned"] };
    }
    try {
        const execution = await request.decoder?.({ presentationPath, maxSlides, includeSpeakerNotes, includeImages, commandPreview });
        if (execution === undefined) {
            return failure("DECODER_REJECTED", "office.slidesDecode decoder returned no envelope", "execution", request.context, presentationPath);
        }
        return {
            ok: true,
            plan,
            audit,
            output: {
                kind: "agentCore.basicTool.office.slidesDecode.output",
                slides: execution.slides.slice(0, maxSlides),
                warnings: execution.warnings ?? [],
                unsafeSideEffects: false,
            },
            events: ["office.slidesDecode.injectedDecoderCompleted"],
        };
    }
    catch (error) {
        return failure("DECODER_REJECTED", error instanceof Error ? error.message : "office.slidesDecode decoder rejected the request", "execution", request.context, presentationPath);
    }
}
