// SPEC: PJ2026-010403 API契约 draft-2026-06-18-r1; PJ2026-010401 Web工作台 draft-2026-06-18-r1; PJ2026-0104010803 唯一投影 draft-2026-06-18-p0-unique-projection.
// Responsibility: Normalized Workbench server-state reducer for session, turn, and trace caches.

import type { ChatMessage, ProjectionDiagnostic, WorkbenchSessionRecord } from "@/types";
import type { SessionStatusAuthority, TurnStatusAuthority } from "./workbench-session";

export interface WorkbenchServerState {
  sessionOrder: string[];
  sessionsById: Record<string, WorkbenchSessionRecord>;
  messagesBySessionId: Record<string, ChatMessage[]>;
  sessionStatusById: Record<string, SessionStatusAuthority>;
  turnStatusByTraceId: Record<string, TurnStatusAuthority>;
  traceById: Record<string, NonNullable<ChatMessage["runnerTrace"]>>;
}

export type WorkbenchServerAction =
  | { type: "session.list"; sessions: WorkbenchSessionRecord[] }
  | { type: "session.detail"; session: WorkbenchSessionRecord | null | undefined }
  | { type: "session.messages"; sessionId: string | null | undefined; messages: ChatMessage[] }
  | { type: "session.status"; session: SessionStatusAuthority }
  | { type: "session.forget"; sessionId: string }
  | { type: "turn.status"; turn: TurnStatusAuthority }
  | { type: "turn.forget"; traceId: string }
  | { type: "trace.snapshot"; traceId: string; trace: NonNullable<ChatMessage["runnerTrace"]> };

export function createWorkbenchServerState(): WorkbenchServerState {
  return {
    sessionOrder: [],
    sessionsById: {},
    messagesBySessionId: {},
    sessionStatusById: {},
    turnStatusByTraceId: {},
    traceById: {}
  };
}

export function reduceWorkbenchServerState(state: WorkbenchServerState, action: WorkbenchServerAction): WorkbenchServerState {
  switch (action.type) {
    case "session.list":
      return reduceSessionList(state, action.sessions);
    case "session.detail":
      return reduceSessionDetail(state, action.session);
    case "session.messages":
      return reduceSessionMessages(state, action.sessionId, action.messages);
    case "session.status":
      return { ...state, sessionStatusById: { ...state.sessionStatusById, [action.session.sessionId]: action.session } };
    case "session.forget":
      return {
        ...state,
        sessionOrder: state.sessionOrder.filter((id) => id !== action.sessionId),
        sessionsById: withoutKey(state.sessionsById, action.sessionId),
        messagesBySessionId: withoutKey(state.messagesBySessionId, action.sessionId),
        sessionStatusById: withoutKey(state.sessionStatusById, action.sessionId)
      };
    case "turn.status":
      return { ...state, turnStatusByTraceId: { ...state.turnStatusByTraceId, [action.turn.traceId]: action.turn } };
    case "turn.forget":
      return { ...state, turnStatusByTraceId: withoutKey(state.turnStatusByTraceId, action.traceId) };
    case "trace.snapshot":
      return { ...state, traceById: { ...state.traceById, [action.traceId]: action.trace } };
  }
}

export function selectSessionStatusAuthority(state: WorkbenchServerState): Readonly<Record<string, SessionStatusAuthority>> {
  return state.sessionStatusById;
}

export function selectTurnStatusAuthority(state: WorkbenchServerState): Readonly<Record<string, TurnStatusAuthority>> {
  return state.turnStatusByTraceId;
}

export function selectTraceAuthorityById(state: WorkbenchServerState): Readonly<Record<string, NonNullable<ChatMessage["runnerTrace"]>>> {
  return state.traceById;
}

export function selectSessionList(state: WorkbenchServerState): WorkbenchSessionRecord[] {
  return state.sessionOrder.map((sessionId) => state.sessionsById[sessionId]).filter((session): session is WorkbenchSessionRecord => Boolean(session));
}

export function selectActiveSession(state: WorkbenchServerState, sessionId: string | null): WorkbenchSessionRecord | null {
  return sessionId ? state.sessionsById[sessionId] ?? null : null;
}

export function selectActiveMessages(state: WorkbenchServerState, sessionId: string | null): ChatMessage[] {
  return sessionId ? state.messagesBySessionId[sessionId] ?? [] : [];
}

function reduceSessionDetail(state: WorkbenchServerState, session: WorkbenchSessionRecord | null | undefined): WorkbenchServerState {
  const sessionId = session?.sessionId;
  if (!sessionId || !session) return state;
  const existing = state.sessionsById[sessionId];
  const messages = Array.isArray(session.messages) ? session.messages : state.messagesBySessionId[sessionId] ?? existing?.messages ?? [];
  const merged = mergeSessionRecord(existing, { ...session, messages });
  const sessionStatus = sessionStatusAuthorityFromDetail(session);
  return {
    ...state,
    sessionOrder: state.sessionOrder.includes(sessionId) ? state.sessionOrder : [sessionId, ...state.sessionOrder],
    sessionsById: { ...state.sessionsById, [sessionId]: merged },
    sessionStatusById: sessionStatus
      ? { ...state.sessionStatusById, [sessionId]: sessionStatus }
      : state.sessionStatusById,
    messagesBySessionId: {
      ...state.messagesBySessionId,
      [sessionId]: messages
    }
  };
}

function reduceSessionList(state: WorkbenchServerState, sessions: WorkbenchSessionRecord[]): WorkbenchServerState {
  const sessionIds = sessions.map((session) => session.sessionId).filter((sessionId): sessionId is string => Boolean(sessionId));
  const next = sessions.reduce((current, session) => reduceSessionListItem(current, session), state);
  return { ...next, sessionOrder: unique(sessionIds) };
}

function reduceSessionListItem(state: WorkbenchServerState, session: WorkbenchSessionRecord): WorkbenchServerState {
  const sessionId = session.sessionId;
  if (!sessionId) return state;
  const existing = state.sessionsById[sessionId];
  const existingMessages = state.messagesBySessionId[sessionId] ?? existing?.messages;
  const messages = Array.isArray(existingMessages) ? existingMessages : Array.isArray(session.messages) ? session.messages : [];
  const merged = mergeSessionRecord(existing, { ...session, messages });
  const sessionStatus = sessionStatusAuthorityFromDetail(session);
  return {
    ...state,
    sessionsById: { ...state.sessionsById, [sessionId]: merged },
    sessionStatusById: sessionStatus
      ? { ...state.sessionStatusById, [sessionId]: sessionStatus }
      : state.sessionStatusById
  };
}

function reduceSessionMessages(state: WorkbenchServerState, sessionId: string | null | undefined, messages: ChatMessage[]): WorkbenchServerState {
  if (!sessionId) return state;
  const existing = state.sessionsById[sessionId];
  const session = existing ? { ...existing, messages, messageCount: messages.length } : null;
  return {
    ...state,
    sessionsById: session ? { ...state.sessionsById, [sessionId]: session } : state.sessionsById,
    messagesBySessionId: { ...state.messagesBySessionId, [sessionId]: messages }
  };
}

function mergeSessionRecord(existing: WorkbenchSessionRecord | undefined, incoming: WorkbenchSessionRecord): WorkbenchSessionRecord {
  if (!existing) return incoming;
  const incomingMessages = Array.isArray(incoming.messages) ? incoming.messages : undefined;
  const existingMessages = Array.isArray(existing.messages) ? existing.messages : [];
  return {
    ...existing,
    ...incoming,
    messages: incomingMessages ?? existing.messages,
    messageCount: incoming.messageCount ?? existing.messageCount ?? incomingMessages?.length ?? existingMessages.length
  };
}

function sessionStatusAuthorityFromDetail(session: WorkbenchSessionRecord): SessionStatusAuthority | null {
  const sessionId = session.sessionId;
  if (!sessionId || typeof session.status !== "string") return null;
  return {
    sessionId,
    status: session.status,
    updatedAt: typeof session.updatedAt === "string" ? session.updatedAt : null,
    lastTraceId: typeof session.lastTraceId === "string" ? session.lastTraceId : null,
    projection: projectionFromSessionRecord(session),
    loadedAt: new Date().toISOString()
  };
}

function projectionFromSessionRecord(session: WorkbenchSessionRecord): ProjectionDiagnostic | null {
  const turnSummary = session.turnSummary && typeof session.turnSummary === "object" ? session.turnSummary as Record<string, unknown> : null;
  const direct = session.projection && typeof session.projection === "object" ? session.projection : null;
  const turnProjection = turnSummary?.projection && typeof turnSummary.projection === "object" ? turnSummary.projection as ProjectionDiagnostic : null;
  if (direct) return direct;
  if (turnProjection) return turnProjection;
  const blocker = session.blocker && typeof session.blocker === "object" ? session.blocker : turnSummary?.blocker && typeof turnSummary.blocker === "object" ? turnSummary.blocker : null;
  if (!session.projectionHealth && !session.projectionStatus && !blocker) return null;
  return {
    projectionStatus: typeof session.projectionStatus === "string" ? session.projectionStatus : typeof turnSummary?.projectionStatus === "string" ? turnSummary.projectionStatus : null,
    projectionHealth: typeof session.projectionHealth === "string" ? session.projectionHealth : typeof turnSummary?.projectionHealth === "string" ? turnSummary.projectionHealth : null,
    blocker: blocker as ProjectionDiagnostic["blocker"] ?? null
  };
}

function withoutKey<T>(source: Record<string, T>, key: string): Record<string, T> {
  if (!(key in source)) return source;
  const next = { ...source };
  delete next[key];
  return next;
}

function unique(values: string[]): string[] {
  return [...new Set(values)];
}
