import { codingTools, createEditTool, createReadTool, createWriteTool, readTool, } from "@mariozechner/pi-coding-agent";
import { logWarn } from "../logger.js";
import { getPluginToolMeta } from "../plugins/tools.js";
import { isSubagentSessionKey } from "../routing/session-key.js";
import { resolveGatewayMessageChannel } from "../utils/message-channel.js";
import { createApplyPatchTool } from "./apply-patch.js";
import { createExecTool, createProcessTool, } from "./bash-tools.js";
import { listChannelAgentTools } from "./channel-tools.js";
import { createOpenClawTools } from "./openclaw-tools.js";
import { wrapToolWithAbortSignal } from "./pi-tools.abort.js";
import { wrapToolWithBeforeToolCallHook } from "./pi-tools.before-tool-call.js";
import { filterToolsByPolicy, isToolAllowedByPolicies, resolveEffectiveToolPolicy, resolveGroupToolPolicy, resolveSubagentToolPolicy, } from "./pi-tools.policy.js";
import { assertRequiredParams, CLAUDE_PARAM_GROUPS, createClawdbotReadTool, createSandboxedEditTool, createSandboxedReadTool, createSandboxedWriteTool, normalizeToolParams, patchToolSchemaForClaudeCompatibility, wrapToolParamNormalization, } from "./pi-tools.read.js";
import { cleanToolSchemaForGemini, normalizeToolParameters } from "./pi-tools.schema.js";
import { buildPluginToolGroups, collectExplicitAllowlist, expandPolicyWithPluginGroups, normalizeToolName, resolveToolProfilePolicy, stripPluginOnlyAllowlist, } from "./tool-policy.js";
function isOpenAIProvider(provider) {
    const normalized = provider?.trim().toLowerCase();
    return normalized === "openai" || normalized === "openai-codex";
}
function isApplyPatchAllowedForModel(params) {
    const allowModels = Array.isArray(params.allowModels) ? params.allowModels : [];
    if (allowModels.length === 0) {
        return true;
    }
    const modelId = params.modelId?.trim();
    if (!modelId) {
        return false;
    }
    const normalizedModelId = modelId.toLowerCase();
    const provider = params.modelProvider?.trim().toLowerCase();
    const normalizedFull = provider && !normalizedModelId.includes("/")
        ? `${provider}/${normalizedModelId}`
        : normalizedModelId;
    return allowModels.some((entry) => {
        const normalized = entry.trim().toLowerCase();
        if (!normalized) {
            return false;
        }
        return normalized === normalizedModelId || normalized === normalizedFull;
    });
}
function resolveExecConfig(cfg) {
    const globalExec = cfg?.tools?.exec;
    return {
        host: globalExec?.host,
        security: globalExec?.security,
        ask: globalExec?.ask,
        node: globalExec?.node,
        pathPrepend: globalExec?.pathPrepend,
        safeBins: globalExec?.safeBins,
        backgroundMs: globalExec?.backgroundMs,
        timeoutSec: globalExec?.timeoutSec,
        approvalRunningNoticeMs: globalExec?.approvalRunningNoticeMs,
        cleanupMs: globalExec?.cleanupMs,
        notifyOnExit: globalExec?.notifyOnExit,
        applyPatch: globalExec?.applyPatch,
    };
}
export const __testing = {
    cleanToolSchemaForGemini,
    normalizeToolParams,
    patchToolSchemaForClaudeCompatibility,
    wrapToolParamNormalization,
    assertRequiredParams,
};
export function createOpenClawCodingTools(options) {
    const execToolName = "exec";
    const sandbox = options?.sandbox?.enabled ? options.sandbox : undefined;
    const { agentId, globalPolicy, globalProviderPolicy, agentPolicy, agentProviderPolicy, profile, providerProfile, 
    // @ts-ignore -- cherry-pick upstream type mismatch
    // @ts-ignore -- cherry-pick upstream type mismatch
    profileAlsoAllow, 
    // @ts-ignore -- cherry-pick upstream type mismatch
    providerProfileAlsoAllow, } = resolveEffectiveToolPolicy({
        config: options?.config,
        sessionKey: options?.sessionKey,
        modelProvider: options?.modelProvider,
        modelId: options?.modelId,
    });
    const groupPolicy = resolveGroupToolPolicy({
        config: options?.config,
        sessionKey: options?.sessionKey,
        spawnedBy: options?.spawnedBy,
        messageProvider: options?.messageProvider,
        groupId: options?.groupId,
        groupChannel: options?.groupChannel,
        // @ts-ignore -- cherry-pick upstream type mismatch
        groupSpace: options?.groupSpace,
        accountId: options?.agentAccountId,
        // @ts-ignore -- cherry-pick upstream type mismatch
        senderId: options?.senderId,
        senderName: options?.senderName,
        senderUsername: options?.senderUsername,
        senderE164: options?.senderE164,
    });
    const profilePolicy = resolveToolProfilePolicy(profile);
    const providerProfilePolicy = resolveToolProfilePolicy(providerProfile);
    const mergeAlsoAllow = (policy, alsoAllow) => {
        if (!policy?.allow || !Array.isArray(alsoAllow) || alsoAllow.length === 0) {
            return policy;
        }
        return { ...policy, allow: Array.from(new Set([...policy.allow, ...alsoAllow])) };
    };
    const profilePolicyWithAlsoAllow = mergeAlsoAllow(profilePolicy, profileAlsoAllow);
    const providerProfilePolicyWithAlsoAllow = mergeAlsoAllow(providerProfilePolicy, providerProfileAlsoAllow);
    const scopeKey = options?.exec?.scopeKey ?? (agentId ? `agent:${agentId}` : undefined);
    const subagentPolicy = isSubagentSessionKey(options?.sessionKey) && options?.sessionKey
        ? resolveSubagentToolPolicy(options.config)
        : undefined;
    const allowBackground = isToolAllowedByPolicies("process", [
        profilePolicyWithAlsoAllow,
        providerProfilePolicyWithAlsoAllow,
        globalPolicy,
        globalProviderPolicy,
        agentPolicy,
        agentProviderPolicy,
        groupPolicy,
        sandbox?.tools,
        subagentPolicy,
    ]);
    const execConfig = resolveExecConfig(options?.config);
    const sandboxRoot = sandbox?.workspaceDir;
    const allowWorkspaceWrites = sandbox?.workspaceAccess !== "ro";
    const workspaceRoot = options?.workspaceDir ?? process.cwd();
    const applyPatchConfig = options?.config?.tools?.exec?.applyPatch;
    const applyPatchEnabled = !!applyPatchConfig?.enabled &&
        isOpenAIProvider(options?.modelProvider) &&
        isApplyPatchAllowedForModel({
            modelProvider: options?.modelProvider,
            modelId: options?.modelId,
            allowModels: applyPatchConfig?.allowModels,
        });
    const base = codingTools.flatMap((tool) => {
        if (tool.name === readTool.name) {
            if (sandboxRoot) {
                return [createSandboxedReadTool(sandboxRoot)];
            }
            const freshReadTool = createReadTool(workspaceRoot);
            return [createClawdbotReadTool(freshReadTool)];
        }
        if (tool.name === "bash" || tool.name === execToolName) {
            return [];
        }
        if (tool.name === "write") {
            if (sandboxRoot) {
                return [];
            }
            // Wrap with param normalization for Claude Code compatibility
            return [
                wrapToolParamNormalization(createWriteTool(workspaceRoot), CLAUDE_PARAM_GROUPS.write),
            ];
        }
        if (tool.name === "edit") {
            if (sandboxRoot) {
                return [];
            }
            // Wrap with param normalization for Claude Code compatibility
            return [wrapToolParamNormalization(createEditTool(workspaceRoot), CLAUDE_PARAM_GROUPS.edit)];
        }
        return [tool];
    });
    const { cleanupMs: cleanupMsOverride, ...execDefaults } = options?.exec ?? {};
    const execTool = createExecTool({
        ...execDefaults,
        host: options?.exec?.host ?? execConfig.host,
        security: options?.exec?.security ?? execConfig.security,
        ask: options?.exec?.ask ?? execConfig.ask,
        node: options?.exec?.node ?? execConfig.node,
        pathPrepend: options?.exec?.pathPrepend ?? execConfig.pathPrepend,
        safeBins: options?.exec?.safeBins ?? execConfig.safeBins,
        agentId,
        cwd: options?.workspaceDir,
        allowBackground,
        scopeKey,
        sessionKey: options?.sessionKey,
        messageProvider: options?.messageProvider,
        backgroundMs: options?.exec?.backgroundMs ?? execConfig.backgroundMs,
        timeoutSec: options?.exec?.timeoutSec ?? execConfig.timeoutSec,
        approvalRunningNoticeMs: options?.exec?.approvalRunningNoticeMs ?? execConfig.approvalRunningNoticeMs,
        notifyOnExit: options?.exec?.notifyOnExit ?? execConfig.notifyOnExit,
        sandbox: sandbox
            ? {
                containerName: sandbox.containerName,
                workspaceDir: sandbox.workspaceDir,
                containerWorkdir: sandbox.containerWorkdir,
                env: sandbox.docker.env,
            }
            : undefined,
    });
    const processTool = createProcessTool({
        cleanupMs: cleanupMsOverride ?? execConfig.cleanupMs,
        scopeKey,
    });
    const applyPatchTool = !applyPatchEnabled || (sandboxRoot && !allowWorkspaceWrites)
        ? // @ts-ignore -- cherry-pick upstream type mismatch
            null
        : createApplyPatchTool({
            cwd: sandboxRoot ?? workspaceRoot,
            // @ts-ignore -- cherry-pick upstream type mismatch
            sandboxRoot: sandboxRoot && allowWorkspaceWrites ? sandboxRoot : undefined,
        });
    const tools = [
        ...base,
        ...(sandboxRoot
            ? allowWorkspaceWrites
                ? [createSandboxedEditTool(sandboxRoot), createSandboxedWriteTool(sandboxRoot)]
                : []
            : []),
        ...(applyPatchTool ? [applyPatchTool] : []),
        execTool,
        // @ts-ignore -- cherry-pick upstream type mismatch
        processTool,
        // Channel docking: include channel-defined agent tools (login, etc.).
        ...listChannelAgentTools({ cfg: options?.config }),
        ...createOpenClawTools({
            // @ts-ignore -- cherry-pick upstream type mismatch
            sandboxBrowserBridgeUrl: sandbox?.browser?.bridgeUrl,
            allowHostBrowserControl: sandbox ? sandbox.browserAllowHostControl : true,
            agentSessionKey: options?.sessionKey,
            agentChannel: resolveGatewayMessageChannel(options?.messageProvider),
            agentAccountId: options?.agentAccountId,
            agentTo: options?.messageTo,
            agentThreadId: options?.messageThreadId,
            agentGroupId: options?.groupId ?? null,
            agentGroupChannel: options?.groupChannel ?? null,
            agentGroupSpace: options?.groupSpace ?? null,
            agentDir: options?.agentDir,
            sandboxRoot,
            workspaceDir: options?.workspaceDir,
            sandboxed: !!sandbox,
            config: options?.config,
            pluginToolAllowlist: collectExplicitAllowlist([
                profilePolicy,
                providerProfilePolicy,
                globalPolicy,
                globalProviderPolicy,
                agentPolicy,
                agentProviderPolicy,
                groupPolicy,
                sandbox?.tools,
                subagentPolicy,
            ]),
            currentChannelId: options?.currentChannelId,
            currentThreadTs: options?.currentThreadTs,
            replyToMode: options?.replyToMode,
            hasRepliedRef: options?.hasRepliedRef,
            modelHasVision: options?.modelHasVision,
            requireExplicitMessageTarget: options?.requireExplicitMessageTarget,
            disableMessageTool: options?.disableMessageTool,
            requesterAgentIdOverride: agentId,
        }),
    ];
    const coreToolNames = new Set(tools
        .filter((tool) => !getPluginToolMeta(tool))
        .map((tool) => normalizeToolName(tool.name))
        .filter(Boolean));
    const pluginGroups = buildPluginToolGroups({
        tools,
        toolMeta: (tool) => getPluginToolMeta(tool),
    });
    const resolvePolicy = (policy, label) => {
        const resolved = stripPluginOnlyAllowlist(policy, pluginGroups, coreToolNames);
        if (resolved.unknownAllowlist.length > 0) {
            const entries = resolved.unknownAllowlist.join(", ");
            const suffix = resolved.strippedAllowlist
                ? "Ignoring allowlist so core tools remain available. Use tools.alsoAllow for additive plugin tool enablement."
                : "These entries won't match any tool unless the plugin is enabled.";
            logWarn(`tools: ${label} allowlist contains unknown entries (${entries}). ${suffix}`);
        }
        return expandPolicyWithPluginGroups(resolved.policy, pluginGroups);
    };
    const profilePolicyExpanded = resolvePolicy(profilePolicyWithAlsoAllow, profile ? `tools.profile (${profile})` : "tools.profile");
    const providerProfileExpanded = resolvePolicy(providerProfilePolicyWithAlsoAllow, providerProfile ? `tools.byProvider.profile (${providerProfile})` : "tools.byProvider.profile");
    const globalPolicyExpanded = resolvePolicy(globalPolicy, "tools.allow");
    const globalProviderExpanded = resolvePolicy(globalProviderPolicy, "tools.byProvider.allow");
    const agentPolicyExpanded = resolvePolicy(agentPolicy, agentId ? `agents.${agentId}.tools.allow` : "agent tools.allow");
    const agentProviderExpanded = resolvePolicy(agentProviderPolicy, agentId ? `agents.${agentId}.tools.byProvider.allow` : "agent tools.byProvider.allow");
    const groupPolicyExpanded = resolvePolicy(groupPolicy, "group tools.allow");
    const sandboxPolicyExpanded = expandPolicyWithPluginGroups(sandbox?.tools, pluginGroups);
    const subagentPolicyExpanded = expandPolicyWithPluginGroups(subagentPolicy, pluginGroups);
    const toolsFiltered = profilePolicyExpanded
        ? filterToolsByPolicy(tools, profilePolicyExpanded)
        : tools;
    const providerProfileFiltered = providerProfileExpanded
        ? filterToolsByPolicy(toolsFiltered, providerProfileExpanded)
        : toolsFiltered;
    const globalFiltered = globalPolicyExpanded
        ? filterToolsByPolicy(providerProfileFiltered, globalPolicyExpanded)
        : providerProfileFiltered;
    const globalProviderFiltered = globalProviderExpanded
        ? filterToolsByPolicy(globalFiltered, globalProviderExpanded)
        : globalFiltered;
    const agentFiltered = agentPolicyExpanded
        ? filterToolsByPolicy(globalProviderFiltered, agentPolicyExpanded)
        : globalProviderFiltered;
    const agentProviderFiltered = agentProviderExpanded
        ? filterToolsByPolicy(agentFiltered, agentProviderExpanded)
        : agentFiltered;
    const groupFiltered = groupPolicyExpanded
        ? filterToolsByPolicy(agentProviderFiltered, groupPolicyExpanded)
        : agentProviderFiltered;
    const sandboxed = sandboxPolicyExpanded
        ? filterToolsByPolicy(groupFiltered, sandboxPolicyExpanded)
        : groupFiltered;
    const subagentFiltered = subagentPolicyExpanded
        ? filterToolsByPolicy(sandboxed, subagentPolicyExpanded)
        : sandboxed;
    // Always normalize tool JSON Schemas before handing them to pi-agent/pi-ai.
    // Without this, some providers (notably OpenAI) will reject root-level union schemas.
    const normalized = subagentFiltered.map(normalizeToolParameters);
    const withHooks = normalized.map((tool) => wrapToolWithBeforeToolCallHook(tool, {
        agentId,
        sessionKey: options?.sessionKey,
    }));
    const withAbort = options?.abortSignal
        ? withHooks.map((tool) => wrapToolWithAbortSignal(tool, options.abortSignal))
        : withHooks;
    // NOTE: Keep canonical (lowercase) tool names here.
    // pi-ai's Anthropic OAuth transport remaps tool names to Claude Code-style names
    // on the wire and maps them back for tool dispatch.
    return withAbort;
}
