// SPEC: PJ2026-0106050514 Workbench实时运行面 draft-2026-06-30-p0-1297-spec-first; PJ2026-010401080313 Workbench实时权威 draft-2026-07-08-p0-workbench-realtime-authority-v2.
// Responsibility: OpenCode-style realtime apply/recovery planning; the store executes plan steps without owning branching policy.
// Mechanical source: /root/opencode/packages/app/src/context/global-sync/event-reducer.ts:93-270 session/message reducer action boundary.
// Mechanical source: /root/opencode/packages/opencode/src/cli/cmd/run/session-data.ts:1-17 reducer side-effect boundary and replay notes.

import type { WorkbenchRealtimeEvent } from "../api/workbench-events";
import { firstNonEmptyString, normalizeWorkbenchSessionId } from "../utils";
import type { WorkbenchStreamTransportRecovery } from "../utils/workbench-realtime-runtime";
import type { WorkbenchRealtimeAction } from "./workbench-event-reducer";

export type WorkbenchRealtimeApplyStep =
  | { type: "apply-trace-event"; traceId: string | null; event: WorkbenchRealtimeEvent["event"]; snapshot: WorkbenchRealtimeEvent["snapshot"]; realtimeEvent: WorkbenchRealtimeEvent }
  | { type: "apply-projection-error"; realtimeEvent: WorkbenchRealtimeEvent }
  | { type: "clear-active-trace"; traceId: string; reason: string };

export interface WorkbenchRealtimeApplyPlan {
  sourceActionType: WorkbenchRealtimeAction["type"];
  steps: WorkbenchRealtimeApplyStep[];
  diagnostic: {
    module: "workbench-realtime-plan";
    sourceActionType: WorkbenchRealtimeAction["type"];
    stepTypes: string[];
    valuesRedacted: true;
  };
}

export interface WorkbenchRealtimeRecoveryContext {
  selectedSessionId?: string | null;
  activeSessionId?: string | null;
  fallbackTraceId?: string | null;
  activeTraceAuthorized?: boolean;
  terminalTraceSealed?: boolean;
}

export type WorkbenchRealtimeRecoveryStep =
  | { type: "events-reconnect"; sessionId: string | null; traceId: string | null; afterOutboxSeq: number | null; reason: string };

export interface WorkbenchRealtimeRecoveryPlan {
  sessionId: string | null;
  traceId: string | null;
  steps: WorkbenchRealtimeRecoveryStep[];
  diagnostic: {
    module: "workbench-realtime-plan";
    reason: string;
    actionTypes: string[];
    stepTypes: string[];
    valuesRedacted: true;
  };
}

export interface WorkbenchRealtimeConnectedAssessment {
  connectedSessionId: string | null;
  ready: boolean;
  warning: {
    code: "workbench_realtime_metadata_drift_warning";
    fields: string[];
    severity: "warning";
    valuesRedacted: true;
  } | null;
  blocker: {
    code: "workbench_realtime_session_scope_mismatch";
    expectedSessionId: string | null;
    connectedSessionId: string | null;
    valuesRedacted: true;
  } | null;
}

export function planWorkbenchRealtimeApply(action: WorkbenchRealtimeAction): WorkbenchRealtimeApplyPlan {
  const steps = applySteps(action);
  return {
    sourceActionType: action.type,
    steps,
    diagnostic: {
      module: "workbench-realtime-plan",
      sourceActionType: action.type,
      stepTypes: steps.map((step) => step.type),
      valuesRedacted: true
    }
  };
}

export function assessWorkbenchRealtimeConnected(
  event: WorkbenchRealtimeEvent,
  expectedSessionId: string | null | undefined,
  kafkaRefreshReplay: boolean
): WorkbenchRealtimeConnectedAssessment {
  const filters = event.filters && typeof event.filters === "object" ? event.filters as Record<string, unknown> : null;
  const connectedSessionId = normalizeWorkbenchSessionId(filters?.sessionId);
  const normalizedExpectedSessionId = normalizeWorkbenchSessionId(expectedSessionId);
  const scopeMatches = Boolean(connectedSessionId && normalizedExpectedSessionId && connectedSessionId === normalizedExpectedSessionId);
  const fields: string[] = [];
  const expectedDelivery = kafkaRefreshReplay ? "kafka-retention-then-live" : "live-only";
  if (event.deliverySemantics !== expectedDelivery) fields.push("deliverySemantics");
  if (event.liveOnly !== !kafkaRefreshReplay) fields.push("liveOnly");
  if (event.replay !== kafkaRefreshReplay) fields.push("replay");
  if (kafkaRefreshReplay && event.lossPossible !== false) fields.push("lossPossible");
  if (event.capabilities?.liveKafkaSse !== true) fields.push("capabilities.liveKafkaSse");
  if (event.capabilities?.kafkaRefreshReplay !== kafkaRefreshReplay) fields.push("capabilities.kafkaRefreshReplay");
  return {
    connectedSessionId,
    ready: scopeMatches,
    warning: fields.length > 0
      ? { code: "workbench_realtime_metadata_drift_warning", fields, severity: "warning", valuesRedacted: true }
      : null,
    blocker: scopeMatches
      ? null
      : {
          code: "workbench_realtime_session_scope_mismatch",
          expectedSessionId: normalizedExpectedSessionId,
          connectedSessionId,
          valuesRedacted: true
        }
  };
}

export function planWorkbenchRealtimeRecovery(recovery: WorkbenchStreamTransportRecovery, context: WorkbenchRealtimeRecoveryContext): WorkbenchRealtimeRecoveryPlan {
  const sessionId = normalizeWorkbenchSessionId(recovery.sessionId ?? context.selectedSessionId);
  const traceId = firstNonEmptyString(recovery.traceId, context.fallbackTraceId);
  const steps: WorkbenchRealtimeRecoveryStep[] = [];
  if (recovery.actions.includes("events-reconnect") && (sessionId || traceId)) steps.push({ type: "events-reconnect", sessionId: sessionId ?? null, traceId: traceId ?? null, afterOutboxSeq: finiteNumber(recovery.outboxSeq), reason: "realtime-error:events-reconnect" });
  return {
    sessionId: sessionId ?? null,
    traceId: traceId ?? null,
    steps,
    diagnostic: {
      module: "workbench-realtime-plan",
      reason: recovery.reason,
      actionTypes: recovery.actions,
      stepTypes: steps.map((step) => step.type),
      valuesRedacted: true
    }
  };
}

function finiteNumber(value: unknown): number | null {
  const number = Number(value);
  return Number.isFinite(number) && number >= 0 ? Math.trunc(number) : null;
}

function applySteps(action: WorkbenchRealtimeAction): WorkbenchRealtimeApplyStep[] {
  switch (action.type) {
    case "trace.event":
      return [{ type: "apply-trace-event", traceId: action.traceId, event: action.event, snapshot: action.snapshot, realtimeEvent: action.realtimeEvent }];
    case "projection.error":
      return [{ type: "apply-projection-error", realtimeEvent: action.realtimeEvent }];
    case "trace.unavailable":
      return action.traceId ? [{ type: "clear-active-trace", traceId: action.traceId, reason: action.reason }] : [];
    case "ignore":
      return [];
  }
}
