import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useEffect, useMemo, useRef, useState } from "react";
import { Activity, Loader2, ShieldAlert } from "lucide-react";
import { getAgentRunMessageAgentId, getAgentRunMessageSeq, isHeartbeatRunEventMessage, } from "./agentMessageHelpers";
import { subscribeAgentRecoveryEvents, } from "./agentRecoveryEventBus";
import { getAgentWatchdogStatus, subscribeAgentWatchdogStatus, } from "./agentWatchdogStatusRegistry";
const MAX_ENTRIES = 150;
const TIMER_INTERVAL_MS = 500;
function extractHeartbeatPayload(message) {
    const data = message?.payload?.data && typeof message.payload.data === "object"
        ? message.payload.data
        : null;
    if (!data) {
        return {
            message: null,
            waitedMs: null,
            providerRequestStartedAt: null,
            providerRequestInFlight: null,
        };
    }
    const pickString = (...keys) => {
        for (const key of keys) {
            const value = data[key];
            if (typeof value === "string" && value.trim().length > 0)
                return value;
        }
        return null;
    };
    const pickNumber = (...keys) => {
        for (const key of keys) {
            const value = data[key];
            if (typeof value === "number" && Number.isFinite(value))
                return value;
        }
        return null;
    };
    const pickBoolean = (...keys) => {
        for (const key of keys) {
            const value = data[key];
            if (typeof value === "boolean")
                return value;
        }
        return null;
    };
    return {
        message: pickString("message", "Message"),
        waitedMs: pickNumber("waitedMs", "WaitedMs"),
        providerRequestStartedAt: pickString("providerRequestStartedAt", "ProviderRequestStartedAt"),
        providerRequestInFlight: pickBoolean("providerRequestInFlight", "ProviderRequestInFlight"),
    };
}
function formatDuration(ms) {
    if (!Number.isFinite(ms) || ms < 0)
        return "—";
    if (ms < 1000)
        return `${ms}ms`;
    const totalSeconds = Math.floor(ms / 1000);
    if (totalSeconds < 60)
        return `${totalSeconds}s`;
    const minutes = Math.floor(totalSeconds / 60);
    const seconds = totalSeconds % 60;
    if (minutes < 60)
        return `${minutes}m ${seconds}s`;
    const hours = Math.floor(minutes / 60);
    return `${hours}h ${minutes % 60}m`;
}
function formatClockTime(date) {
    const hh = String(date.getHours()).padStart(2, "0");
    const mm = String(date.getMinutes()).padStart(2, "0");
    const ss = String(date.getSeconds()).padStart(2, "0");
    const ms = String(date.getMilliseconds()).padStart(3, "0");
    return `${hh}:${mm}:${ss}.${ms}`;
}
function Stat({ label, value, emphasis, }) {
    const valueClass = emphasis === "alert"
        ? "text-feedback-red"
        : emphasis === "warn"
            ? "text-feedback-orange"
            : emphasis === "ok"
                ? "text-feedback-green"
                : "text-neutral-grey-100";
    return (_jsxs("div", { className: "border-border-default rounded border bg-white px-2 py-1.5", children: [_jsx("div", { className: "text-neutral-grey-50 text-[10px] tracking-wider", children: label }), _jsx("div", { className: `mt-0.5 truncate text-[12px] font-medium ${valueClass}`, children: value })] }));
}
function recoveryEntryFromBusEvent(event, seq) {
    let detail = null;
    try {
        detail = JSON.stringify(event.payload);
        if (detail === "{}")
            detail = null;
    }
    catch {
        detail = null;
    }
    return {
        kind: "recovery",
        id: `bus-${event.timestamp}-${event.kind}-${seq}`,
        receivedAt: new Date(event.timestamp),
        type: `recovery:${event.kind}`,
        recoveryKind: event.kind,
        detail,
    };
}
function recoveryEntryFromSeed(event) {
    if (!event?.type?.startsWith("recovery:"))
        return null;
    const recoveryKind = event.type.slice("recovery:".length);
    const receivedAt = new Date(event.timestamp);
    if (Number.isNaN(receivedAt.getTime()))
        return null;
    return {
        kind: "recovery",
        id: `seed-${event.timestamp}-${recoveryKind}-${event.seq ?? "x"}`,
        receivedAt,
        type: event.type,
        recoveryKind,
        detail: event.detail ?? null,
    };
}
export function HeartbeatDiagnosticsPanel({ agentId, subscribeSocketMessages, seedRecoveryEvents, }) {
    const [entries, setEntries] = useState([]);
    const [now, setNow] = useState(() => Date.now());
    const [watchdogStatus, setWatchdogStatus] = useState(() => getAgentWatchdogStatus(agentId));
    const counterRef = useRef(0);
    const seenIdsRef = useRef(new Set());
    const mountedAtRef = useRef(Date.now());
    useEffect(() => {
        setWatchdogStatus(getAgentWatchdogStatus(agentId));
        const unsubscribe = subscribeAgentWatchdogStatus((id, status) => {
            if (id !== agentId)
                return;
            setWatchdogStatus(status);
        });
        return unsubscribe;
    }, [agentId]);
    useEffect(() => {
        mountedAtRef.current = Date.now();
        counterRef.current = 0;
        seenIdsRef.current = new Set();
        const seeded = [];
        if (seedRecoveryEvents) {
            for (const event of seedRecoveryEvents) {
                const entry = recoveryEntryFromSeed(event);
                if (entry && !seenIdsRef.current.has(entry.id)) {
                    seenIdsRef.current.add(entry.id);
                    seeded.push(entry);
                }
            }
        }
        seeded.sort((a, b) => b.receivedAt.getTime() - a.receivedAt.getTime());
        setEntries(seeded.slice(0, MAX_ENTRIES));
    }, [agentId, seedRecoveryEvents]);
    useEffect(() => {
        if (!subscribeSocketMessages)
            return;
        const handler = (message) => {
            if (!message)
                return;
            if (!isHeartbeatRunEventMessage(message))
                return;
            const messageAgentId = getAgentRunMessageAgentId(message.payload);
            if (messageAgentId && messageAgentId !== agentId)
                return;
            const seq = getAgentRunMessageSeq(message.payload);
            const detail = extractHeartbeatPayload(message);
            counterRef.current += 1;
            const entry = {
                kind: "heartbeat",
                id: `hb-${counterRef.current}`,
                receivedAt: new Date(),
                type: message.type,
                seq,
                ...detail,
            };
            seenIdsRef.current.add(entry.id);
            setEntries((prev) => {
                const next = [entry, ...prev];
                return next.length > MAX_ENTRIES ? next.slice(0, MAX_ENTRIES) : next;
            });
        };
        const unsubscribe = subscribeSocketMessages(handler);
        return () => unsubscribe();
    }, [agentId, subscribeSocketMessages]);
    useEffect(() => {
        const unsubscribe = subscribeAgentRecoveryEvents((event) => {
            if (event.agentId !== agentId)
                return;
            counterRef.current += 1;
            const entry = recoveryEntryFromBusEvent(event, counterRef.current);
            if (seenIdsRef.current.has(entry.id))
                return;
            seenIdsRef.current.add(entry.id);
            setEntries((prev) => {
                const next = [entry, ...prev];
                next.sort((a, b) => b.receivedAt.getTime() - a.receivedAt.getTime());
                return next.length > MAX_ENTRIES ? next.slice(0, MAX_ENTRIES) : next;
            });
        });
        return unsubscribe;
    }, [agentId]);
    useEffect(() => {
        const id = window.setInterval(() => setNow(Date.now()), TIMER_INTERVAL_MS);
        return () => window.clearInterval(id);
    }, []);
    const heartbeatEntries = useMemo(() => entries.filter((e) => e.kind === "heartbeat"), [entries]);
    const recoveryEntries = useMemo(() => entries.filter((e) => e.kind === "recovery"), [entries]);
    const lastHeartbeat = heartbeatEntries[0];
    const sinceLastMs = lastHeartbeat
        ? now - lastHeartbeat.receivedAt.getTime()
        : null;
    const watchingMs = now - mountedAtRef.current;
    const interArrivals = useMemo(() => {
        const intervals = [];
        for (let i = 0; i < heartbeatEntries.length - 1; i++) {
            const a = heartbeatEntries[i].receivedAt.getTime();
            const b = heartbeatEntries[i + 1].receivedAt.getTime();
            intervals.push(a - b);
        }
        return intervals;
    }, [heartbeatEntries]);
    const avgIntervalMs = interArrivals.length > 0
        ? Math.round(interArrivals.reduce((sum, value) => sum + value, 0) /
            interArrivals.length)
        : null;
    const lastIntervalEmphasis = (() => {
        if (sinceLastMs == null)
            return undefined;
        if (sinceLastMs > 30_000)
            return "alert";
        if (sinceLastMs > 10_000)
            return "warn";
        return "ok";
    })();
    return (_jsxs("div", { className: "flex h-full min-h-0 flex-col gap-2", children: [_jsxs("div", { className: "border-feedback-blue bg-feedback-blue-light/70 text-feedback-blue rounded border px-3 py-2 text-[11px]", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin", strokeWidth: 1.5 }), _jsx("span", { className: "font-medium", children: "Live heartbeat & watchdog capture" })] }), _jsxs("div", { className: "text-feedback-blue mt-1 text-[10px]", children: ["Watching incoming heartbeats and stall-watchdog activity for this agent (", formatDuration(watchingMs), " so far). Watchdog history before the panel opened is seeded from the existing run snapshot. Nothing is recorded once the panel closes."] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-2 text-[11px] sm:grid-cols-4", children: [_jsx(Stat, { label: "Heartbeats received", value: String(heartbeatEntries.length) }), _jsx(Stat, { label: "Since last heartbeat", value: sinceLastMs != null
                            ? `${formatDuration(sinceLastMs)} ago`
                            : "—", emphasis: lastIntervalEmphasis }), _jsx(Stat, { label: "Average interval", value: avgIntervalMs != null ? formatDuration(avgIntervalMs) : "—" }), _jsx(Stat, { label: "Watchdog events", value: String(recoveryEntries.length), emphasis: recoveryEntries.length > 0 ? "warn" : undefined })] }), _jsx(WatchdogStatusCard, { status: watchdogStatus, now: now }), _jsx("div", { className: "border-border-default min-h-0 flex-1 overflow-auto rounded border bg-white", children: entries.length === 0 ? (_jsx("div", { className: "text-neutral-grey-50 flex h-full items-center justify-center px-4 py-6 text-center text-[11px]", children: "Waiting for heartbeats. If the agent is running and none arrive within ~30 seconds, the provider request or stream may be stuck \u2014 the watchdog will fire and show up here." })) : (_jsxs("table", { className: "text-neutral-grey-100 w-full border-collapse text-[11px]", children: [_jsx("thead", { className: "bg-neutral-grey-5 sticky top-0 z-10", children: _jsxs("tr", { className: "border-border-default text-neutral-grey-50 border-b text-left text-[10px] font-semibold tracking-wider", children: [_jsx("th", { className: "px-2 py-1.5", children: "Received" }), _jsx("th", { className: "px-2 py-1.5", children: "Source" }), _jsx("th", { className: "px-2 py-1.5", children: "Event" }), _jsx("th", { className: "px-2 py-1.5", children: "Seq" }), _jsx("th", { className: "px-2 py-1.5", children: "Detail" })] }) }), _jsx("tbody", { children: entries.map((entry, index) => {
                                const nextEntry = entries[index + 1];
                                const intervalMs = nextEntry
                                    ? entry.receivedAt.getTime() - nextEntry.receivedAt.getTime()
                                    : null;
                                const isRecovery = entry.kind === "recovery";
                                const sourceClass = isRecovery
                                    ? "text-feedback-orange"
                                    : "text-feedback-blue";
                                const rowClass = isRecovery
                                    ? "bg-feedback-orange-light/40"
                                    : "";
                                const heartbeatDetail = entry.kind === "heartbeat"
                                    ? [
                                        entry.message ? entry.message : null,
                                        entry.waitedMs != null
                                            ? `waited ${formatDuration(entry.waitedMs)}`
                                            : null,
                                        entry.providerRequestInFlight == null
                                            ? null
                                            : `in-flight: ${entry.providerRequestInFlight ? "yes" : "no"}`,
                                    ]
                                        .filter(Boolean)
                                        .join(" · ") || "—"
                                    : null;
                                return (_jsxs("tr", { className: `border-border-default border-b align-top last:border-b-0 ${rowClass}`, children: [_jsxs("td", { className: "badge-pad text-neutral-grey-50 whitespace-nowrap tabular-nums", children: [_jsx("div", { children: formatClockTime(entry.receivedAt) }), intervalMs != null && (_jsxs("div", { className: "text-neutral-grey-50 text-[10px]", children: ["+", formatDuration(intervalMs)] }))] }), _jsx("td", { className: `badge-pad font-medium whitespace-nowrap ${sourceClass}`, children: _jsxs("span", { className: "inline-flex items-center gap-1", children: [isRecovery ? (_jsx(ShieldAlert, { className: "h-3 w-3", strokeWidth: 1.5 })) : (_jsx(Activity, { className: "h-3 w-3", strokeWidth: 1.5 })), isRecovery ? "Watchdog" : "Heartbeat"] }) }), _jsx("td", { className: "badge-pad font-medium whitespace-nowrap", children: entry.kind === "recovery"
                                                ? entry.recoveryKind
                                                : entry.type }), _jsx("td", { className: "badge-pad whitespace-nowrap tabular-nums", children: entry.kind === "heartbeat"
                                                ? (entry.seq ?? "—")
                                                : "—" }), _jsx("td", { className: "badge-pad text-neutral-grey-50 break-all", children: entry.kind === "heartbeat"
                                                ? heartbeatDetail
                                                : (entry.detail ?? "—") })] }, entry.id));
                            }) })] })) })] }));
}
function WatchdogStatusCard({ status, now, }) {
    if (!status) {
        return (_jsxs("div", { className: "border-border-default bg-neutral-grey-5 text-neutral-grey-50 rounded border px-3 py-2 text-[11px]", children: [_jsx("div", { className: "text-neutral-grey-100 font-medium", children: "Watchdog: unknown" }), _jsx("div", { className: "mt-0.5 text-[10px]", children: "The watchdog has not reported any state for this agent yet. It runs inside the agent terminal \u2014 open the terminal to see status here." })] }));
    }
    const reasonsNotRunning = [];
    if (status.isWatchdogActive === false)
        reasonsNotRunning.push("watchdog gate inactive");
    if (status.isVisible === false)
        reasonsNotRunning.push("terminal not visible");
    if (status.isStopping === true)
        reasonsNotRunning.push("stop pending");
    if (status.isWaitingForCapacity === true)
        reasonsNotRunning.push("waiting for capacity");
    const lastTickAgo = status.lastTickAt != null ? now - status.lastTickAt : null;
    const silentMs = status.lastNonHeartbeatUpdateAt != null &&
        status.lastNonHeartbeatUpdateAt > 0
        ? now - status.lastNonHeartbeatUpdateAt
        : null;
    const threshold = status.stalledThresholdMs ?? null;
    const isStalled = status.running &&
        silentMs != null &&
        threshold != null &&
        silentMs > threshold;
    const headerClass = status.running
        ? isStalled
            ? "text-feedback-red"
            : "text-feedback-green"
        : "text-feedback-orange";
    const containerClass = status.running
        ? isStalled
            ? "border-feedback-red bg-feedback-red-light/60"
            : "border-feedback-green bg-feedback-green-light/60"
        : "border-feedback-orange bg-feedback-orange-light/60";
    return (_jsxs("div", { className: `text-neutral-grey-100 rounded border px-3 py-2 text-[11px] ${containerClass}`, children: [_jsx("div", { className: `font-medium ${headerClass}`, children: status.running
                    ? isStalled
                        ? "Watchdog: running — stream STALLED"
                        : "Watchdog: running"
                    : `Watchdog: not running${reasonsNotRunning.length > 0
                        ? ` (${reasonsNotRunning.join(", ")})`
                        : ""}` }), _jsxs("div", { className: "text-neutral-grey-50 mt-1 grid grid-cols-2 gap-x-4 gap-y-0.5 text-[10px] sm:grid-cols-4", children: [_jsxs("div", { children: ["watchdogActive:", " ", _jsx("span", { className: "font-mono", children: status.isWatchdogActive == null
                                    ? "—"
                                    : status.isWatchdogActive
                                        ? "yes"
                                        : "no" })] }), _jsxs("div", { children: ["local isExecuting:", " ", _jsx("span", { className: "font-mono", children: status.isExecuting == null
                                    ? "—"
                                    : status.isExecuting
                                        ? "yes"
                                        : "no" })] }), _jsxs("div", { children: ["isVisible:", " ", _jsx("span", { className: "font-mono", children: status.isVisible == null
                                    ? "—"
                                    : status.isVisible
                                        ? "yes"
                                        : "no" })] }), _jsxs("div", { children: ["isStopping:", " ", _jsx("span", { className: "font-mono", children: status.isStopping == null
                                    ? "—"
                                    : status.isStopping
                                        ? "yes"
                                        : "no" })] }), _jsxs("div", { children: ["waitingForCapacity:", " ", _jsx("span", { className: "font-mono", children: status.isWaitingForCapacity == null
                                    ? "—"
                                    : status.isWaitingForCapacity
                                        ? "yes"
                                        : "no" })] }), _jsxs("div", { children: ["last tick:", " ", _jsx("span", { className: "font-mono", children: lastTickAgo != null
                                    ? `${formatDuration(lastTickAgo)} ago`
                                    : "—" })] }), _jsxs("div", { children: ["silent (non-heartbeat):", " ", _jsx("span", { className: "font-mono", children: silentMs != null ? formatDuration(silentMs) : "—" })] }), _jsxs("div", { children: ["stall threshold:", " ", _jsx("span", { className: "font-mono", children: threshold != null ? formatDuration(threshold) : "—" })] }), _jsxs("div", { children: ["started:", " ", _jsx("span", { className: "font-mono", children: status.startedAt != null
                                    ? `${formatDuration(now - status.startedAt)} ago`
                                    : "—" })] })] })] }));
}
//# sourceMappingURL=HeartbeatDiagnosticsPanel.js.map