/**
 * Per-tool contract for `switch_mode` — host-mediated user-gesture tool.
 *
 * The contract owns transition validation (against `getCurrentMode()`) and
 * the host-tool dispatch. Side effects on resolution (mode swap, next-turn
 * kick-off, stream resume on rejection) and tool-list gating (omitting
 * `switch_mode` when `autoPlan === false`) are the composition root's job.
 */
import { Type } from '@sinclair/typebox';
import { HostToolResultStatus, buildRendererArtifact } from '../../../contracts/host-tool.js';
import { withAbort } from '../../../utils/abort.js';
import { SWITCH_MODE } from '../tool-names.js';
export const SWITCH_MODE_KIND = 'switchMode';
export const SWITCH_MODE_TARGET_MODE_DESCRIPTION = "The mode to switch to: 'plan' or 'action'";
export const SWITCH_MODE_REASON_DESCRIPTION = `
The content to pass to the new mode. This becomes the user's message to start the new session.

CRITICAL: Write this in FIRST PERSON from the user's perspective, as if the user is saying it directly.
- WRONG: "The user wants to fix the formula errors in Sheet1"
- RIGHT: "Fix the formula errors in Sheet1"

Keep it actionable and direct. For plan→action transitions, include the implementation plan.
`;
const switchModeLlmSchema = Type.Object({
    target_mode: Type.Unsafe({
        type: 'string',
        enum: ['action', 'plan'],
        description: SWITCH_MODE_TARGET_MODE_DESCRIPTION
    }),
    reason: Type.String({
        description: SWITCH_MODE_REASON_DESCRIPTION
    })
});
const AGENT_SWITCHABLE_TARGETS = {
    action: ['plan'],
    plan: ['action'],
    // ask, manage, shorty, and review are all sinks — the user must change modes
    // manually in the UI; the agent cannot transition out of them.
    ask: [],
    manage: [],
    shorty: [],
    review: []
};
/**
 * Per-target description blurbs. Indexed by target mode so the dynamic
 * description below can list only the modes reachable from the current mode.
 */
const MODE_DESCRIPTIONS = {
    plan: `**plan**: **Switch to plan mode for these 4 big categories: user frustrations, ambiguous asks, subjective asks, and complex workbooks.
- Multi-sheet financial models, DCF valuations, comprehensive dashboards, tasks explicitly listing 4+ distinct deliverables
- Major structural reorganizations affecting multiple sheets
- Truly ambiguous asks with no prior context: "build a DCF", "create executive KPI dashboard"
- Vague requests or subjective asks with no prior context: "this is broken", "help", "I'm frustrated with this model", "make this look better", "improve this model"

**DON'T switch to plan mode when:**
- Recent messages already established what needs to be done (e.g., user discussed issues then said "fix it")
- The user just confirmed or approved a course of action
- You have clear, actionable context from the conversation`,
    action: `**action**: For executing changes to the spreadsheet.`
};
/** Compose the description per the agent's *current* mode. */
export function buildSwitchModeDescription(currentMode) {
    const allowedTargets = AGENT_SWITCHABLE_TARGETS[currentMode] ?? [];
    if (allowedTargets.length === 0) {
        return 'Mode switching is not available in this mode.';
    }
    const modeSections = allowedTargets.map((target) => MODE_DESCRIPTIONS[target]).join('\n\n');
    return `Switch to a different agent mode. You are currently in **${currentMode}** mode.

Available modes to switch to:

${modeSections}

IMPORTANT: Do NOT switch to "${currentMode}" - you are already in that mode.

The user will see your reason and can choose to accept or reject the mode switch.`;
}
/**
 * Static fallback used by tool catalogs that snapshot tools at boot. Real
 * agent flows read the dynamic description through createLlmTool, which
 * recomputes it per build using getCurrentMode().
 */
export const SWITCH_MODE_TOOL_DESCRIPTION = buildSwitchModeDescription('action');
function canSwitch(currentMode, targetMode) {
    return AGENT_SWITCHABLE_TARGETS[currentMode].includes(targetMode);
}
function formatInvalidTransitionMessage(currentMode, targetMode) {
    const allowed = AGENT_SWITCHABLE_TARGETS[currentMode];
    if (allowed.length === 0) {
        return `Cannot switch modes from ${currentMode} mode. Continuing in current mode.`;
    }
    if (currentMode === targetMode) {
        return `Cannot switch to ${targetMode} from ${currentMode}. Allowed: ${allowed.join(', ')}. Continuing in current mode.`;
    }
    return `Cannot switch to ${targetMode} from ${currentMode}. Allowed: ${allowed.join(', ')}. Continuing in current mode.`;
}
// Validators below are structural only; transition validity is checked at execute time.
function isSwitchableMode(value) {
    return value === 'plan' || value === 'action';
}
function isSwitchModeStatus(value) {
    return value === 'SUCCESS' || value === 'REJECTED';
}
function validateParams(value) {
    if (!value || typeof value !== 'object')
        return false;
    const candidate = value;
    return isSwitchableMode(candidate.target_mode) && typeof candidate.reason === 'string';
}
function validateResultDetails(value) {
    if (value === undefined)
        return true;
    if (!value || typeof value !== 'object')
        return false;
    const candidate = value;
    return (typeof candidate.accepted === 'boolean' &&
        isSwitchModeStatus(candidate.switch_status) &&
        isSwitchableMode(candidate.target_mode) &&
        typeof candidate.reason === 'string' &&
        (candidate.detail === undefined || typeof candidate.detail === 'string'));
}
function normalizeResultDetails(value) {
    return value === undefined ? undefined : { ...value };
}
function getResultObservabilityFields(details) {
    if (!validateResultDetails(details) || details === undefined)
        return undefined;
    return {
        accepted: details.accepted,
        switch_status: details.switch_status,
        target_mode: details.target_mode
    };
}
function evaluateApproval() {
    // The user gesture lives in the adapter (the approval panel), not the
    // host-tool approval gate — `true` here would cause double-prompting.
    return { required: false };
}
function buildSuccessDetails(params, resolution) {
    return {
        accepted: true,
        switch_status: 'SUCCESS',
        target_mode: params.target_mode,
        reason: params.reason,
        ...(resolution.detail !== undefined ? { detail: resolution.detail } : {})
    };
}
function buildRejectedDetails(params, resolution) {
    return {
        accepted: false,
        switch_status: 'REJECTED',
        target_mode: params.target_mode,
        reason: params.reason,
        ...(resolution.detail !== undefined ? { detail: resolution.detail } : {})
    };
}
const SUCCESS_CONTENT = (mode, reason) => {
    const trimmedReason = reason.trim();
    return trimmedReason.length > 0
        ? `Switched to ${mode} mode.\n\nHandoff message:\n${trimmedReason}`
        : `Switched to ${mode} mode.`;
};
const REJECTED_CONTENT = 'User chose to stay in current mode. Proceed with current approach.';
function createLlmTool(options) {
    // sessionPolicy unused — gesture seam is the adapter, not the host-tool approval gate.
    void options.sessionPolicy;
    // Description is rebuilt at tool-build time using whatever mode the host
    // says we're in right now. Mid-session UI mode changes will reach the next
    // call by virtue of the host re-Configuring the runtime; within a single
    // build the description is stable.
    //
    // Tool-catalog discovery paths (e.g. cross-tool invariant tests) may
    // construct the tool without a getCurrentMode callback. Default to action —
    // the resulting description is the static one any other consumer expects.
    const currentMode = typeof options.getCurrentMode === 'function' ? options.getCurrentMode() : 'action';
    const description = buildSwitchModeDescription(currentMode);
    return {
        name: SWITCH_MODE,
        label: 'Switch mode',
        isReadOnly: false,
        description,
        parameters: switchModeLlmSchema,
        async execute(toolCallId, llmArgs, signal) {
            const args = llmArgs;
            const params = {
                target_mode: args.target_mode,
                reason: args.reason
            };
            // Read fresh — UI mode may have changed between turns.
            const currentMode = options.getCurrentMode();
            // Invalid transitions (including target = current) resolve as a
            // non-error ToolMessage; no host request is made.
            if (!canSwitch(currentMode, params.target_mode)) {
                return {
                    content: [
                        {
                            type: 'text',
                            text: formatInvalidTransitionMessage(currentMode, params.target_mode)
                        }
                    ],
                    details: {
                        accepted: false,
                        switch_status: 'REJECTED',
                        target_mode: params.target_mode,
                        reason: 'invalid_transition'
                    },
                    isError: false
                };
            }
            const pendingHostToolRequests = options.pendingHostToolRequests;
            const request = {
                approval: evaluateApproval(),
                heartbeat: {
                    label: 'Waiting for mode switch approval...',
                    toolName: SWITCH_MODE
                },
                kind: SWITCH_MODE_KIND,
                params,
                requestId: toolCallId
            };
            const result = await withAbort(signal, async () => pendingHostToolRequests.request(request));
            const details = result.details;
            if (result.status === HostToolResultStatus.Cancelled ||
                result.status === HostToolResultStatus.Failed) {
                const failed = result.status === HostToolResultStatus.Failed;
                const fallback = failed ? 'Mode switch failed.' : 'Mode switch was cancelled.';
                const text = result.userMessage || details?.detail || fallback;
                return {
                    content: [{ type: 'text', text }],
                    details: buildRendererArtifact({
                        accepted: false,
                        switch_status: 'REJECTED',
                        target_mode: params.target_mode,
                        reason: params.reason
                    }, result.userMessage),
                    isError: true
                };
            }
            // Succeeded path: adapter has already built the artifact; we just
            // normalize it onto the ToolMessage and produce the content string.
            if (details === undefined) {
                throw new Error('switch_mode Succeeded result is missing details.');
            }
            const status = details.switch_status === 'SUCCESS' || details.switch_status === 'REJECTED'
                ? details.switch_status
                : 'REJECTED';
            const detail = typeof details.detail === 'string' ? details.detail : undefined;
            const approvedReason = status === 'SUCCESS' && typeof details.reason === 'string' ? details.reason : params.reason;
            const resolvedParams = { ...params, reason: approvedReason };
            const finalDetails = status === 'SUCCESS'
                ? buildSuccessDetails(resolvedParams, { status, detail })
                : buildRejectedDetails(resolvedParams, { status, detail });
            if (status === 'SUCCESS' &&
                result.runtimeHandoff?.kind === 'switchMode' &&
                result.runtimeHandoff.configuration &&
                options.applyRuntimeHandoff) {
                await options.applyRuntimeHandoff(result.runtimeHandoff);
            }
            return {
                content: [
                    {
                        type: 'text',
                        text: status === 'SUCCESS'
                            ? SUCCESS_CONTENT(resolvedParams.target_mode, resolvedParams.reason)
                            : REJECTED_CONTENT
                    }
                ],
                details: finalDetails,
                isError: false
            };
        }
    };
}
export const switchModeContract = {
    kind: SWITCH_MODE_KIND,
    createLlmTool,
    evaluateApproval,
    getResultObservabilityFields,
    validateParams,
    validateResultDetails,
    normalizeResultDetails
};
//# sourceMappingURL=contract.js.map