import { registerServerToolHandler } from '../registry.js';
import { cloneJson } from '../server-side-tools.js';
import { extractCapturedChatSeed } from './followup-request-builder.js';
import { ensureRuntimeMetadata, readRuntimeMetadata } from '../../conversion/runtime-metadata.js';
import { cancelClockTask, clearClockTasks, listClockTasks, resolveClockConfig, parseDueAtMs, scheduleClockTasks, startClockDaemonIfNeeded, updateClockTask } from '../clock/task-store.js';
import { getClockTimeSnapshot } from '../clock/ntp.js';
import { nowMs } from '../clock/state.js';
import { logClock } from '../clock/log.js';
import { resolveClockSessionScope } from '../clock/session-scope.js';
const FLOW_ID = 'clock_flow';
let fallbackClockToolCallSeq = 0;
function ensureClockToolCall(toolCall, requestId) {
    if (!toolCall || toolCall.name !== 'clock') {
        return null;
    }
    const existingId = typeof toolCall.id === 'string' ? toolCall.id.trim() : '';
    if (existingId) {
        return toolCall;
    }
    fallbackClockToolCallSeq += 1;
    const reqToken = String(requestId || 'req').replace(/[^a-zA-Z0-9_-]+/g, '_') || 'req';
    return {
        ...toolCall,
        id: `call_clock_fallback_${reqToken}_${fallbackClockToolCallSeq}`
    };
}
function extractAssistantMessageFromChatLike(chatResponse) {
    if (!chatResponse || typeof chatResponse !== 'object') {
        return null;
    }
    const choices = Array.isArray(chatResponse.choices) ? chatResponse.choices : [];
    if (!choices.length) {
        return null;
    }
    const first = choices[0];
    if (!first || typeof first !== 'object' || Array.isArray(first)) {
        return null;
    }
    const message = first.message;
    if (!message || typeof message !== 'object' || Array.isArray(message)) {
        return null;
    }
    const role = typeof message.role === 'string' ? String(message.role).toLowerCase() : '';
    if (role && role !== 'assistant') {
        return null;
    }
    return cloneJson(message);
}
function buildToolMessagesFromToolOutputs(chatResponse) {
    const outputs = Array.isArray(chatResponse.tool_outputs) ? chatResponse.tool_outputs : [];
    const out = [];
    for (const entry of outputs) {
        if (!entry || typeof entry !== 'object' || Array.isArray(entry))
            continue;
        const toolCallId = typeof entry.tool_call_id === 'string' ? String(entry.tool_call_id) : '';
        if (!toolCallId)
            continue;
        const name = typeof entry.name === 'string' && String(entry.name).trim()
            ? String(entry.name).trim()
            : 'tool';
        const rawContent = entry.content;
        let contentText;
        if (typeof rawContent === 'string') {
            contentText = rawContent;
        }
        else {
            try {
                contentText = JSON.stringify(rawContent ?? {});
            }
            catch {
                contentText = String(rawContent ?? '');
            }
        }
        out.push({ role: 'tool', tool_call_id: toolCallId, name, content: contentText });
    }
    return out;
}
function parseToolArguments(toolCall) {
    if (!toolCall.arguments || typeof toolCall.arguments !== 'string') {
        return {};
    }
    try {
        const parsed = JSON.parse(toolCall.arguments);
        return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
    }
    catch {
        return {};
    }
}
function injectClockToolOutput(base, toolCall, content) {
    const cloned = cloneJson(base);
    const existingOutputs = Array.isArray(cloned.tool_outputs)
        ? cloned.tool_outputs
        : [];
    let payloadText;
    if (typeof content === 'string') {
        payloadText = content;
    }
    else {
        try {
            payloadText = JSON.stringify(content ?? {});
        }
        catch {
            payloadText = String(content ?? '');
        }
    }
    cloned.tool_outputs = [
        ...existingOutputs,
        {
            tool_call_id: toolCall.id,
            name: 'clock',
            content: payloadText
        }
    ];
    return cloned;
}
function asPlainObject(value) {
    if (value && typeof value === 'object' && !Array.isArray(value)) {
        return value;
    }
    return null;
}
function normalizeAction(value) {
    const raw = typeof value === 'string' ? value.trim().toLowerCase() : '';
    if (raw === 'get' || raw === 'list' || raw === 'cancel' || raw === 'clear' || raw === 'schedule' || raw === 'update') {
        return raw;
    }
    return 'schedule';
}
function toIso(ms) {
    try {
        return new Date(ms).toISOString();
    }
    catch {
        return new Date(0).toISOString();
    }
}
function parseRecurrenceKind(raw) {
    const value = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
    if (!value) {
        return undefined;
    }
    if (value === 'daily' || value === 'day') {
        return 'daily';
    }
    if (value === 'weekly' || value === 'week') {
        return 'weekly';
    }
    if (value === 'interval' || value === 'every_minutes' || value === 'every-minutes' || value === 'everyminutes') {
        return 'interval';
    }
    return undefined;
}
function parseRecurrenceFromRecord(record) {
    const recurrenceRaw = record.recurrence ?? record.repeat ?? record.cycle;
    const maxRunsRaw = Number(record.maxRuns ?? (recurrenceRaw && typeof recurrenceRaw === 'object' && !Array.isArray(recurrenceRaw)
        ? recurrenceRaw.maxRuns
        : undefined));
    if (recurrenceRaw === undefined || recurrenceRaw === null || recurrenceRaw === false) {
        return {};
    }
    let kind;
    let everyMinutesRaw;
    if (typeof recurrenceRaw === 'string') {
        kind = parseRecurrenceKind(recurrenceRaw);
        everyMinutesRaw = record.everyMinutes;
    }
    else if (recurrenceRaw && typeof recurrenceRaw === 'object' && !Array.isArray(recurrenceRaw)) {
        const recurrenceRecord = recurrenceRaw;
        kind = parseRecurrenceKind(recurrenceRecord.kind ?? recurrenceRecord.type ?? recurrenceRecord.mode ?? recurrenceRecord.every);
        everyMinutesRaw = recurrenceRecord.everyMinutes ?? recurrenceRecord.minutes ?? record.everyMinutes;
    }
    if (!kind) {
        return { message: 'clock.schedule recurrence kind must be daily|weekly|interval' };
    }
    const maxRuns = Number.isFinite(maxRunsRaw) ? Math.floor(maxRunsRaw) : NaN;
    if (!Number.isFinite(maxRuns) || maxRuns <= 0) {
        return { message: 'clock.schedule recurring task requires maxRuns >= 1' };
    }
    if (kind === 'interval') {
        const everyMinutesNum = Number(everyMinutesRaw);
        const everyMinutes = Number.isFinite(everyMinutesNum) ? Math.floor(everyMinutesNum) : NaN;
        if (!Number.isFinite(everyMinutes) || everyMinutes <= 0) {
            return { message: 'clock.schedule interval recurrence requires everyMinutes >= 1' };
        }
        return { recurrence: { kind: 'interval', maxRuns, everyMinutes } };
    }
    return { recurrence: { kind, maxRuns } };
}
function normalizeScheduleItems(parsed) {
    const itemsRaw = parsed.items;
    if (!Array.isArray(itemsRaw) || itemsRaw.length === 0) {
        return { ok: false, items: [], message: 'clock.schedule requires items: [{ dueAt, task, tool?, arguments? }]' };
    }
    const items = [];
    for (const entry of itemsRaw) {
        const rec = asPlainObject(entry);
        if (!rec) {
            return { ok: false, items: [], message: 'clock.schedule items must be objects' };
        }
        const dueAtMs = parseDueAtMs(rec.dueAt);
        if (!dueAtMs) {
            return { ok: false, items: [], message: 'clock.schedule dueAt must be an ISO8601 string' };
        }
        const task = typeof rec.task === 'string' ? rec.task.trim() : '';
        if (!task) {
            return { ok: false, items: [], message: 'clock.schedule task must be a non-empty string' };
        }
        const tool = typeof rec.tool === 'string' && rec.tool.trim().length ? rec.tool.trim() : undefined;
        const argsObj = (() => {
            const raw = rec.arguments;
            if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
                return cloneJson(raw);
            }
            if (typeof raw === 'string' && raw.trim().length) {
                try {
                    const parsed = JSON.parse(raw);
                    return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : undefined;
                }
                catch {
                    return undefined;
                }
            }
            return undefined;
        })();
        const recurrenceParsed = parseRecurrenceFromRecord(rec);
        if (recurrenceParsed.message) {
            return { ok: false, items: [], message: recurrenceParsed.message };
        }
        items.push({
            dueAtMs,
            task,
            setBy: 'agent',
            ...(tool ? { tool } : {}),
            ...(argsObj ? { arguments: argsObj } : {}),
            ...(recurrenceParsed.recurrence ? { recurrence: recurrenceParsed.recurrence } : {})
        });
    }
    return { ok: true, items };
}
function mapTaskForTool(t) {
    const recurrence = t.recurrence && typeof t.recurrence === 'object'
        ? {
            kind: t.recurrence.kind,
            maxRuns: t.recurrence.maxRuns,
            ...(t.recurrence.kind === 'interval' && typeof t.recurrence.everyMinutes === 'number'
                ? { everyMinutes: t.recurrence.everyMinutes }
                : {})
        }
        : undefined;
    const remainingRuns = recurrence
        ? Math.max(0, Math.floor(Number(recurrence.maxRuns) || 0) - Math.max(0, Math.floor(Number(t.deliveryCount) || 0)))
        : undefined;
    return {
        taskId: t.taskId,
        dueAt: toIso(t.dueAtMs),
        setBy: t.setBy === 'agent' ? 'agent' : 'user',
        setAt: toIso(t.createdAtMs),
        task: t.task,
        ...(t.tool ? { tool: t.tool } : {}),
        ...(t.arguments ? { arguments: t.arguments } : {}),
        ...(typeof t.deliveredAtMs === 'number' ? { deliveredAt: toIso(t.deliveredAtMs) } : {}),
        deliveryCount: t.deliveryCount,
        ...(recurrence ? { recurrence } : {}),
        ...(typeof remainingRuns === 'number' ? { remainingRuns } : {})
    };
}
const handler = async (ctx) => {
    const toolCall = ensureClockToolCall(ctx.toolCall, ctx.requestId);
    if (!toolCall) {
        return null;
    }
    const rt = readRuntimeMetadata(ctx.adapterContext);
    const sessionId = resolveClockSessionScope(ctx.adapterContext, rt);
    const rawConfig = rt?.clock ?? ctx.adapterContext.clock;
    // Default-enable clock when config is absent, but keep "explicitly disabled" honored.
    const clockConfig = resolveClockConfig(rawConfig);
    const parsedArgs = parseToolArguments(toolCall);
    const action = normalizeAction(parsedArgs.action);
    const respond = (payload) => {
        return {
            flowId: FLOW_ID,
            finalize: async () => {
                const patched = injectClockToolOutput(ctx.base, toolCall, payload);
                const seed = extractCapturedChatSeed(ctx.adapterContext?.capturedChatRequest);
                const canFollowup = Boolean(seed);
                const bootstrapActive = rt?.antigravityThoughtSignatureBootstrap === true;
                const forcedProviderKey = bootstrapActive &&
                    typeof ctx.adapterContext.providerKey === 'string' &&
                    String(ctx.adapterContext.providerKey).trim()
                    ? String(ctx.adapterContext.providerKey).trim()
                    : '';
                return {
                    chatResponse: patched,
                    execution: {
                        flowId: FLOW_ID,
                        ...(canFollowup
                            ? {
                                followup: {
                                    requestIdSuffix: ':clock_followup',
                                    entryEndpoint: ctx.entryEndpoint,
                                    ...(bootstrapActive && seed
                                        ? {
                                            payload: (() => {
                                                const messages = Array.isArray(seed.messages) ? cloneJson(seed.messages) : [];
                                                const assistant = extractAssistantMessageFromChatLike(ctx.base);
                                                if (assistant) {
                                                    messages.push(assistant);
                                                }
                                                messages.push(...buildToolMessagesFromToolOutputs(patched));
                                                const params = seed.parameters && typeof seed.parameters === 'object' && !Array.isArray(seed.parameters)
                                                    ? { ...seed.parameters }
                                                    : {};
                                                // Bootstrap-only: the first hop forces tool_config=clock; the second hop must clear it
                                                // so the model can either answer or call other tools normally.
                                                delete params.tool_config;
                                                return {
                                                    ...(seed.model ? { model: seed.model } : {}),
                                                    messages,
                                                    ...(Array.isArray(seed.tools) ? { tools: cloneJson(seed.tools) } : {}),
                                                    ...(Object.keys(params).length ? { parameters: params } : {})
                                                };
                                            })(),
                                            metadata: (() => {
                                                const meta = {};
                                                if (forcedProviderKey) {
                                                    meta.__shadowCompareForcedProviderKey = forcedProviderKey;
                                                }
                                                const runtime = ensureRuntimeMetadata(meta);
                                                runtime.antigravityThoughtSignatureBootstrapAttempted = true;
                                                return meta;
                                            })()
                                        }
                                        : {
                                            injection: {
                                                ops: [
                                                    { op: 'append_assistant_message', required: true },
                                                    { op: 'append_tool_messages_from_tool_outputs', required: true }
                                                ]
                                            }
                                        })
                                }
                            }
                            : {})
                    }
                };
            }
        };
    };
    if (!clockConfig) {
        logClock('disabled', { action, hasSessionId: true });
        return respond({
            ok: false,
            action,
            message: 'clock tool is not enabled (virtualrouter.clock.enabled=true required).'
        });
    }
    await startClockDaemonIfNeeded(clockConfig);
    if (action === 'get') {
        try {
            const snapshot = await getClockTimeSnapshot();
            logClock('get', { hasSessionId: Boolean(sessionId) });
            return respond({
                ok: true,
                action,
                active: true,
                nowMs: snapshot.nowMs,
                utc: snapshot.utc,
                local: snapshot.local,
                timezone: snapshot.timezone,
                ntp: snapshot.ntp
            });
        }
        catch (err) {
            logClock('get_error', { message: String(err?.message || err || '') });
            return respond({
                ok: false,
                action,
                message: `clock.get failed: ${String(err?.message || err || 'unknown')}`
            });
        }
    }
    if (!sessionId) {
        logClock('missing_session', { action });
        return respond({
            ok: false,
            action,
            message: 'clock requires tmux session scope (clientTmuxSessionId/tmuxSessionId).'
        });
    }
    if (action === 'list') {
        const items = await listClockTasks(sessionId, clockConfig);
        logClock('list', { sessionId, count: items.length });
        return respond({ ok: true, action, items: items.map(mapTaskForTool) });
    }
    if (action === 'clear') {
        const removedCount = await clearClockTasks(sessionId, clockConfig);
        logClock('clear', { sessionId, removedCount });
        return respond({ ok: true, action, removedCount });
    }
    if (action === 'cancel') {
        const taskId = typeof parsedArgs.taskId === 'string' ? parsedArgs.taskId.trim() : '';
        if (!taskId) {
            logClock('cancel_invalid', { sessionId, action });
            return respond({ ok: false, action, message: 'clock.cancel requires taskId' });
        }
        const removed = await cancelClockTask(sessionId, taskId, clockConfig);
        logClock('cancel', { sessionId, taskId, removed });
        return respond({ ok: true, action, removed: removed ? taskId : null });
    }
    if (action === 'update') {
        const taskId = typeof parsedArgs.taskId === 'string' ? parsedArgs.taskId.trim() : '';
        if (!taskId) {
            logClock('update_invalid', { sessionId, action, reason: 'missing_task_id' });
            return respond({ ok: false, action, message: 'clock.update requires taskId' });
        }
        const normalized = normalizeScheduleItems(parsedArgs);
        if (!normalized.ok || normalized.items.length < 1) {
            logClock('update_invalid', { sessionId, action, message: normalized.message ?? 'invalid update payload' });
            return respond({ ok: false, action, message: normalized.message ?? 'clock.update requires items[0] with dueAt/task' });
        }
        const item = normalized.items[0];
        const patch = {
            dueAtMs: item.dueAtMs,
            task: item.task,
            resetDelivery: true,
            ...(item.tool ? { tool: item.tool } : {}),
            ...(item.arguments ? { arguments: item.arguments } : {}),
            ...(item.recurrence ? { recurrence: item.recurrence } : {})
        };
        const updated = await updateClockTask(sessionId, taskId, patch, clockConfig);
        if (!updated) {
            logClock('update_miss', { sessionId, taskId });
            return respond({ ok: false, action, message: 'clock.update failed: task not found or patch invalid' });
        }
        logClock('update', { sessionId, taskId });
        return respond({ ok: true, action, updated: mapTaskForTool(updated) });
    }
    const normalized = normalizeScheduleItems(parsedArgs);
    if (!normalized.ok) {
        logClock('schedule_invalid', { sessionId, message: normalized.message ?? 'invalid schedule items' });
        return respond({ ok: false, action, message: normalized.message ?? 'invalid schedule items' });
    }
    const at = nowMs();
    const guardedItems = normalized.items.map((item) => {
        if (!item || typeof item !== 'object')
            return item;
        if (!Number.isFinite(item.dueAtMs))
            return item;
        // When dueAt is already within the trigger window, do NOT allow same-request injection.
        if (item.dueAtMs <= at + clockConfig.dueWindowMs) {
            return { ...item, notBeforeRequestId: ctx.requestId };
        }
        return item;
    });
    const scheduled = await scheduleClockTasks(sessionId, guardedItems, clockConfig);
    logClock('schedule', { sessionId, count: scheduled.length });
    return respond({
        ok: true,
        action,
        scheduled: scheduled.map(mapTaskForTool)
    });
};
registerServerToolHandler('clock', handler);
