import { isLeadMember } from '@shared/utils/leadDetection';
import { migrateProviderBackendId } from '@shared/utils/providerBackend';
import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider';

import type {
  MemberLaunchState,
  MemberSpawnLivenessSource,
  MemberSpawnStatusEntry,
  OpenCodeAppManagedBootstrapCandidate,
  PersistedTeamLaunchMemberSources,
  PersistedTeamLaunchMemberState,
  PersistedTeamLaunchPhase,
  PersistedTeamLaunchSnapshot,
  PersistedTeamLaunchSummary,
  ProviderModelLaunchIdentity,
  TeamAgentRuntimeDiagnosticSeverity,
  TeamAgentRuntimeLivenessKind,
  TeamAgentRuntimePidSource,
  TeamLaunchAggregateState,
} from '@shared/types';

interface LegacyPartialLaunchStateFile {
  version?: unknown;
  state?: unknown;
  updatedAt?: unknown;
  leadSessionId?: unknown;
  expectedMembers?: unknown;
  confirmedMembers?: unknown;
  missingMembers?: unknown;
}

type RuntimeMemberSpawnState = Pick<
  MemberSpawnStatusEntry,
  | 'launchState'
  | 'status'
  | 'error'
  | 'hardFailureReason'
  | 'livenessSource'
  | 'agentToolAccepted'
  | 'runtimeAlive'
  | 'bootstrapConfirmed'
  | 'hardFailure'
  | 'skippedForLaunch'
  | 'skipReason'
  | 'skippedAt'
  | 'pendingPermissionRequestIds'
  | 'livenessKind'
  | 'runtimeDiagnostic'
  | 'runtimeDiagnosticSeverity'
  | 'bootstrapStalled'
  | 'livenessLastCheckedAt'
  | 'firstSpawnAcceptedAt'
  | 'lastHeartbeatAt'
  | 'runtimeModel'
  | 'updatedAt'
>;

function normalizePendingPermissionRequestIds(value: unknown): string[] | undefined {
  if (!Array.isArray(value)) {
    return undefined;
  }
  const normalized = value
    .filter((item): item is string => typeof item === 'string')
    .map((item) => item.trim())
    .filter((item) => item.length > 0);
  return normalized.length > 0 ? Array.from(new Set(normalized)) : undefined;
}

function normalizeRuntimePid(value: unknown): number | undefined {
  return typeof value === 'number' && Number.isFinite(value) && value > 0
    ? Math.trunc(value)
    : undefined;
}

function normalizeLivenessKind(value: unknown): TeamAgentRuntimeLivenessKind | undefined {
  return value === 'confirmed_bootstrap' ||
    value === 'runtime_process' ||
    value === 'runtime_process_candidate' ||
    value === 'permission_blocked' ||
    value === 'shell_only' ||
    value === 'registered_only' ||
    value === 'stale_metadata' ||
    value === 'not_found'
    ? value
    : undefined;
}

function preservesStrongRuntimeAlive(
  value:
    | {
        runtimeAlive?: boolean;
        bootstrapConfirmed?: boolean;
        livenessKind?: TeamAgentRuntimeLivenessKind;
      }
    | undefined
): boolean {
  return (
    value?.runtimeAlive === true &&
    (value.bootstrapConfirmed === true ||
      value.livenessKind === 'confirmed_bootstrap' ||
      value.livenessKind === 'runtime_process')
  );
}

function isOpenCodeSecondaryBootstrapPending(
  member: Pick<
    PersistedTeamLaunchMemberState,
    | 'providerId'
    | 'laneKind'
    | 'laneOwnerProviderId'
    | 'launchState'
    | 'bootstrapConfirmed'
    | 'hardFailure'
  >
): boolean {
  return (
    member.providerId === 'opencode' &&
    member.laneKind === 'secondary' &&
    member.laneOwnerProviderId === 'opencode' &&
    member.launchState === 'runtime_pending_bootstrap' &&
    member.bootstrapConfirmed !== true &&
    member.hardFailure !== true
  );
}

function isPersistedBootstrapStalled(
  member: Pick<
    PersistedTeamLaunchMemberState,
    | 'providerId'
    | 'laneKind'
    | 'laneOwnerProviderId'
    | 'launchState'
    | 'runtimeAlive'
    | 'bootstrapConfirmed'
    | 'hardFailure'
    | 'bootstrapStalled'
    | 'runtimePid'
    | 'runtimeSessionId'
    | 'livenessKind'
  >
): boolean {
  const hasMaterializedOpenCodeRuntimeMarker =
    member.runtimeAlive === true ||
    (typeof member.runtimePid === 'number' &&
      Number.isFinite(member.runtimePid) &&
      member.runtimePid > 0) ||
    (typeof member.runtimeSessionId === 'string' && member.runtimeSessionId.trim().length > 0) ||
    member.livenessKind === 'runtime_process' ||
    member.livenessKind === 'runtime_process_candidate' ||
    member.livenessKind === 'registered_only';
  return (
    member.bootstrapStalled === true &&
    isOpenCodeSecondaryBootstrapPending(member) &&
    hasMaterializedOpenCodeRuntimeMarker
  );
}

function normalizePidSource(value: unknown): TeamAgentRuntimePidSource | undefined {
  return value === 'lead_process' ||
    value === 'tmux_pane' ||
    value === 'tmux_child' ||
    value === 'agent_process_table' ||
    value === 'opencode_bridge' ||
    value === 'runtime_bootstrap' ||
    value === 'persisted_metadata'
    ? value
    : undefined;
}

function normalizeDiagnosticSeverity(
  value: unknown
): TeamAgentRuntimeDiagnosticSeverity | undefined {
  return value === 'info' || value === 'warning' || value === 'error' ? value : undefined;
}

function normalizeOptionalString(value: unknown): string | undefined {
  return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
}

function normalizeOpenCodeAppManagedBootstrapCandidate(
  value: unknown
): OpenCodeAppManagedBootstrapCandidate | undefined {
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    return undefined;
  }
  const record = value as Record<string, unknown>;
  if (record.schemaVersion !== 1 || record.source !== 'app_managed_bootstrap') {
    return undefined;
  }
  const teamName = normalizeOptionalString(record.teamName);
  const memberName = normalizeOptionalString(record.memberName);
  const runId = normalizeOptionalString(record.runId);
  const laneId = normalizeOptionalString(record.laneId);
  const runtimeSessionId = normalizeOptionalString(record.runtimeSessionId);
  const messageID = normalizeOptionalString(record.messageID);
  const contextHash = normalizeOptionalString(record.contextHash);
  const briefingHash = normalizeOptionalString(record.briefingHash);
  const injectionVerifiedAt = normalizeOptionalString(record.injectionVerifiedAt);
  const candidateAt = normalizeOptionalString(record.candidateAt);
  if (
    !teamName ||
    !memberName ||
    !runId ||
    !laneId ||
    !runtimeSessionId ||
    !messageID ||
    !contextHash ||
    !briefingHash ||
    !injectionVerifiedAt ||
    !candidateAt
  ) {
    return undefined;
  }
  const model = normalizeOptionalString(record.model);
  const agent = normalizeOptionalString(record.agent);
  return {
    schemaVersion: 1,
    source: 'app_managed_bootstrap',
    teamName,
    memberName,
    runId,
    laneId,
    runtimeSessionId,
    messageID,
    contextHash,
    briefingHash,
    injectionVerifiedAt,
    candidateAt,
    ...(model ? { model } : {}),
    ...(agent ? { agent } : {}),
  };
}

function decodeJsonStringLiteral(value: string): string {
  try {
    return JSON.parse(`"${value}"`) as string;
  } catch {
    return value.replace(/\\"/g, '"').replace(/\\n/g, '\n').replace(/\\\\/g, '\\');
  }
}

function extractLooseJsonStringField(text: string, fieldName: string): string | undefined {
  const strictMatch = new RegExp(`"${fieldName}"\\s*:\\s*"((?:\\\\.|[^"\\\\])*)"`).exec(text);
  if (strictMatch?.[1]) {
    return decodeJsonStringLiteral(strictMatch[1]).trim() || undefined;
  }

  const looseMatch = new RegExp(`"${fieldName}"\\s*:\\s*"((?:\\\\.|[^"\\\\])*)$`).exec(text);
  return looseMatch?.[1] ? decodeJsonStringLiteral(looseMatch[1]).trim() || undefined : undefined;
}

function joinUniqueReasonParts(parts: readonly (string | undefined)[]): string | undefined {
  const uniqueParts = Array.from(
    new Set(parts.map((part) => part?.trim()).filter((part): part is string => !!part))
  );
  return uniqueParts.length > 0 ? uniqueParts.join(': ') : undefined;
}

function extractMessageSendRoutingReason(text: string): string | undefined {
  const trimmed = text.trim();
  if (!trimmed.includes('Message sent to') && !trimmed.includes('"routing"')) {
    return undefined;
  }

  try {
    const parsed = JSON.parse(trimmed) as {
      success?: unknown;
      message?: unknown;
      routing?: { summary?: unknown; content?: unknown };
    };
    if (parsed.success === true && parsed.routing && typeof parsed.routing === 'object') {
      return joinUniqueReasonParts([
        typeof parsed.routing.summary === 'string' ? parsed.routing.summary : undefined,
        typeof parsed.routing.content === 'string' ? parsed.routing.content : undefined,
      ]);
    }
  } catch {
    // Fall through to loose extraction for persisted reasons truncated by older builds.
  }

  return joinUniqueReasonParts([
    extractLooseJsonStringField(trimmed, 'summary'),
    extractLooseJsonStringField(trimmed, 'content'),
  ]);
}

export function normalizeLaunchFailureReasonText(value: unknown): string | undefined {
  const text = normalizeOptionalString(value);
  if (!text) {
    return undefined;
  }
  return extractMessageSendRoutingReason(text) ?? text;
}

function normalizeMemberName(name: string): string {
  return name.trim();
}

function buildDiagnostics(
  member: Pick<
    PersistedTeamLaunchMemberState,
    | 'agentToolAccepted'
    | 'runtimeAlive'
    | 'bootstrapConfirmed'
    | 'hardFailure'
    | 'hardFailureReason'
    | 'skippedForLaunch'
    | 'skipReason'
    | 'sources'
    | 'pendingPermissionRequestIds'
    | 'bootstrapStalled'
  >
): string[] {
  const diagnostics: string[] = [];
  if (member.agentToolAccepted) diagnostics.push('spawn accepted');
  if (member.runtimeAlive) diagnostics.push('runtime alive');
  if (member.bootstrapConfirmed) diagnostics.push('late heartbeat received');
  if ((member.pendingPermissionRequestIds?.length ?? 0) > 0) {
    diagnostics.push('waiting for permission approval');
  } else if (member.bootstrapStalled) {
    diagnostics.push('opencode_bootstrap_stalled');
  } else if (member.hardFailure && member.runtimeAlive && !member.bootstrapConfirmed) {
    diagnostics.push('bootstrap failed while runtime process was still alive');
  } else if (member.runtimeAlive && !member.bootstrapConfirmed) {
    diagnostics.push('waiting for teammate check-in');
  }
  const hardFailureReason = normalizeLaunchFailureReasonText(member.hardFailureReason);
  if (hardFailureReason) diagnostics.push(`hard failure reason: ${hardFailureReason}`);
  if (member.skippedForLaunch) {
    diagnostics.push(
      member.skipReason
        ? `skipped for this launch: ${member.skipReason}`
        : 'skipped for this launch'
    );
  }
  if (member.sources?.duplicateRespawnBlocked) diagnostics.push('respawn blocked as duplicate');
  if (member.sources?.configDrift) diagnostics.push('config drift detected');
  return diagnostics;
}

export function deriveTeamLaunchAggregateState(
  summary: PersistedTeamLaunchSummary
): TeamLaunchAggregateState {
  if (summary.failedCount > 0) {
    return 'partial_failure';
  }
  if (summary.pendingCount > 0) {
    return 'partial_pending';
  }
  if ((summary.skippedCount ?? 0) > 0) {
    return 'partial_skipped';
  }
  return 'clean_success';
}

export function summarizePersistedLaunchMembers(
  expectedMembers: readonly string[],
  members: Record<string, PersistedTeamLaunchMemberState>
): PersistedTeamLaunchSummary {
  let confirmedCount = 0;
  let pendingCount = 0;
  let failedCount = 0;
  let skippedCount = 0;
  let runtimeAlivePendingCount = 0;
  let shellOnlyPendingCount = 0;
  let runtimeProcessPendingCount = 0;
  let runtimeCandidatePendingCount = 0;
  let noRuntimePendingCount = 0;
  let permissionPendingCount = 0;
  const normalizedExpected = expectedMembers.map(normalizeMemberName).filter(Boolean);
  const memberNames = Array.from(
    new Set([
      ...normalizedExpected,
      ...Object.keys(members).map(normalizeMemberName).filter(Boolean),
    ])
  );

  for (const memberName of memberNames) {
    const entry = members[memberName];
    if (!entry) {
      pendingCount += 1;
      continue;
    }
    if (entry.launchState === 'confirmed_alive') {
      confirmedCount += 1;
      continue;
    }
    if (entry.launchState === 'skipped_for_launch' || entry.skippedForLaunch === true) {
      skippedCount += 1;
      continue;
    }
    if (entry.launchState === 'failed_to_start') {
      failedCount += 1;
      continue;
    }
    pendingCount += 1;
    if (preservesStrongRuntimeAlive(entry)) {
      runtimeAlivePendingCount += 1;
    }
    if (entry.launchState === 'runtime_pending_permission') {
      permissionPendingCount += 1;
    }
    if (entry.livenessKind === 'shell_only') {
      shellOnlyPendingCount += 1;
    } else if (entry.livenessKind === 'runtime_process') {
      runtimeProcessPendingCount += 1;
    } else if (entry.livenessKind === 'runtime_process_candidate') {
      runtimeCandidatePendingCount += 1;
    } else if (
      entry.livenessKind === 'not_found' ||
      entry.livenessKind === 'stale_metadata' ||
      entry.livenessKind === 'registered_only'
    ) {
      noRuntimePendingCount += 1;
    }
  }

  return {
    confirmedCount,
    pendingCount,
    failedCount,
    skippedCount,
    runtimeAlivePendingCount,
    shellOnlyPendingCount,
    runtimeProcessPendingCount,
    runtimeCandidatePendingCount,
    noRuntimePendingCount,
    permissionPendingCount,
  };
}

export function hasMixedPersistedLaunchMetadata(
  snapshot: PersistedTeamLaunchSnapshot | null | undefined
): boolean {
  if (!snapshot) {
    return false;
  }
  if (
    Array.isArray(snapshot.bootstrapExpectedMembers) &&
    snapshot.bootstrapExpectedMembers.join('\u0000') !== snapshot.expectedMembers.join('\u0000')
  ) {
    return true;
  }
  return Object.values(snapshot.members).some(
    (member) =>
      Boolean(member?.laneId) ||
      Boolean(member?.laneKind) ||
      Boolean(member?.laneOwnerProviderId) ||
      Boolean(member?.launchIdentity)
  );
}

function deriveMemberLaunchState(
  member: Pick<
    PersistedTeamLaunchMemberState,
    | 'hardFailure'
    | 'bootstrapConfirmed'
    | 'runtimeAlive'
    | 'agentToolAccepted'
    | 'skippedForLaunch'
    | 'pendingPermissionRequestIds'
  >
): MemberLaunchState {
  if (member.skippedForLaunch) {
    return 'skipped_for_launch';
  }
  if (member.hardFailure) {
    return 'failed_to_start';
  }
  if (member.bootstrapConfirmed) {
    return 'confirmed_alive';
  }
  if ((member.pendingPermissionRequestIds?.length ?? 0) > 0) {
    return 'runtime_pending_permission';
  }
  if (member.runtimeAlive || member.agentToolAccepted) {
    return 'runtime_pending_bootstrap';
  }
  return 'starting';
}

function toBoolean(value: unknown): boolean {
  return value === true;
}

function normalizeFastMode(value: unknown): PersistedTeamLaunchMemberState['selectedFastMode'] {
  return value === 'inherit' || value === 'on' || value === 'off' ? value : undefined;
}

function normalizeLaunchIdentity(
  value: unknown,
  fallbackProviderId?: PersistedTeamLaunchMemberState['providerId']
): ProviderModelLaunchIdentity | undefined {
  if (!value || typeof value !== 'object') {
    return undefined;
  }
  const raw = value as Record<string, unknown>;
  const providerId =
    normalizeOptionalTeamProviderId(raw.providerId) ??
    normalizeOptionalTeamProviderId(fallbackProviderId);
  if (!providerId) {
    return undefined;
  }
  const selectedModelKind =
    raw.selectedModelKind === 'explicit' || raw.selectedModelKind === 'default'
      ? raw.selectedModelKind
      : 'default';
  const catalogSource =
    raw.catalogSource === 'anthropic-models-api' ||
    raw.catalogSource === 'anthropic-compatible-api' ||
    raw.catalogSource === 'app-server' ||
    raw.catalogSource === 'static-fallback' ||
    raw.catalogSource === 'runtime' ||
    raw.catalogSource === 'unavailable'
      ? raw.catalogSource
      : 'unavailable';
  return {
    providerId,
    providerBackendId:
      migrateProviderBackendId(
        providerId,
        typeof raw.providerBackendId === 'string' ? raw.providerBackendId : undefined
      ) ?? null,
    selectedModel: typeof raw.selectedModel === 'string' ? raw.selectedModel.trim() || null : null,
    selectedModelKind,
    resolvedLaunchModel:
      typeof raw.resolvedLaunchModel === 'string' ? raw.resolvedLaunchModel.trim() || null : null,
    catalogId: typeof raw.catalogId === 'string' ? raw.catalogId.trim() || null : null,
    catalogSource,
    catalogFetchedAt:
      typeof raw.catalogFetchedAt === 'string' ? raw.catalogFetchedAt.trim() || null : null,
    selectedEffort:
      raw.selectedEffort === 'none' ||
      raw.selectedEffort === 'minimal' ||
      raw.selectedEffort === 'low' ||
      raw.selectedEffort === 'medium' ||
      raw.selectedEffort === 'high' ||
      raw.selectedEffort === 'xhigh' ||
      raw.selectedEffort === 'max'
        ? raw.selectedEffort
        : null,
    resolvedEffort:
      raw.resolvedEffort === 'none' ||
      raw.resolvedEffort === 'minimal' ||
      raw.resolvedEffort === 'low' ||
      raw.resolvedEffort === 'medium' ||
      raw.resolvedEffort === 'high' ||
      raw.resolvedEffort === 'xhigh' ||
      raw.resolvedEffort === 'max'
        ? raw.resolvedEffort
        : null,
    selectedFastMode:
      raw.selectedFastMode === 'inherit' ||
      raw.selectedFastMode === 'on' ||
      raw.selectedFastMode === 'off'
        ? raw.selectedFastMode
        : null,
    resolvedFastMode: typeof raw.resolvedFastMode === 'boolean' ? raw.resolvedFastMode : null,
    fastResolutionReason:
      typeof raw.fastResolutionReason === 'string' ? raw.fastResolutionReason.trim() || null : null,
  };
}

function normalizeSources(value: unknown): PersistedTeamLaunchMemberSources | undefined {
  if (!value || typeof value !== 'object') {
    return undefined;
  }
  const source = value as Record<string, unknown>;
  const normalized: PersistedTeamLaunchMemberSources = {
    inboxHeartbeat: toBoolean(source.inboxHeartbeat),
    nativeHeartbeat: toBoolean(source.nativeHeartbeat),
    processAlive: toBoolean(source.processAlive),
    configRegistered: toBoolean(source.configRegistered),
    configDrift: toBoolean(source.configDrift),
    hardFailureSignal: toBoolean(source.hardFailureSignal),
    duplicateRespawnBlocked: toBoolean(source.duplicateRespawnBlocked),
  };
  return Object.values(normalized).some(Boolean) ? normalized : undefined;
}

function normalizePersistedMemberState(
  memberName: string,
  value: unknown,
  updatedAtFallback: string
): PersistedTeamLaunchMemberState | null {
  if (!value || typeof value !== 'object') {
    return null;
  }
  const parsed = value as Record<string, unknown>;
  const normalizedName = normalizeMemberName(memberName);
  if (!normalizedName || normalizedName === 'user' || isLeadMember({ name: normalizedName })) {
    return null;
  }
  const providerId = normalizeOptionalTeamProviderId(parsed.providerId);
  const skippedForLaunch =
    toBoolean(parsed.skippedForLaunch) || parsed.launchState === 'skipped_for_launch';
  const bootstrapConfirmed =
    !skippedForLaunch &&
    (toBoolean(parsed.bootstrapConfirmed) || parsed.launchState === 'confirmed_alive');
  const livenessKind = normalizeLivenessKind(parsed.livenessKind);
  const runtimeAlive = skippedForLaunch
    ? false
    : preservesStrongRuntimeAlive({
        runtimeAlive: toBoolean(parsed.runtimeAlive),
        bootstrapConfirmed,
        livenessKind,
      });
  const hardFailure = skippedForLaunch
    ? false
    : toBoolean(parsed.hardFailure) || parsed.launchState === 'failed_to_start';
  const sources = normalizeSources(parsed.sources) ?? {};
  if (!runtimeAlive) {
    sources.processAlive = undefined;
  }
  const next: PersistedTeamLaunchMemberState = {
    name: normalizedName,
    providerId,
    providerBackendId: migrateProviderBackendId(
      providerId,
      typeof parsed.providerBackendId === 'string' ? parsed.providerBackendId : undefined
    ),
    model: typeof parsed.model === 'string' ? parsed.model.trim() || undefined : undefined,
    effort:
      parsed.effort === 'none' ||
      parsed.effort === 'minimal' ||
      parsed.effort === 'low' ||
      parsed.effort === 'medium' ||
      parsed.effort === 'high' ||
      parsed.effort === 'xhigh' ||
      parsed.effort === 'max'
        ? parsed.effort
        : undefined,
    selectedFastMode: normalizeFastMode(parsed.selectedFastMode),
    resolvedFastMode:
      typeof parsed.resolvedFastMode === 'boolean' ? parsed.resolvedFastMode : undefined,
    laneId: typeof parsed.laneId === 'string' ? parsed.laneId.trim() || undefined : undefined,
    laneKind:
      parsed.laneKind === 'primary' || parsed.laneKind === 'secondary'
        ? parsed.laneKind
        : undefined,
    laneOwnerProviderId: normalizeOptionalTeamProviderId(parsed.laneOwnerProviderId),
    launchIdentity: normalizeLaunchIdentity(parsed.launchIdentity, providerId),
    launchState: 'starting',
    skippedForLaunch,
    skipReason: normalizeOptionalString(parsed.skipReason),
    skippedAt: normalizeOptionalString(parsed.skippedAt),
    agentToolAccepted: skippedForLaunch ? false : toBoolean(parsed.agentToolAccepted),
    runtimeAlive,
    bootstrapConfirmed,
    hardFailure,
    hardFailureReason: !hardFailure
      ? undefined
      : normalizeLaunchFailureReasonText(parsed.hardFailureReason),
    pendingPermissionRequestIds: normalizePendingPermissionRequestIds(
      parsed.pendingPermissionRequestIds
    ),
    runtimePid: normalizeRuntimePid(parsed.runtimePid),
    runtimeRunId: normalizeOptionalString(parsed.runtimeRunId),
    runtimeSessionId: normalizeOptionalString(parsed.runtimeSessionId),
    bootstrapEvidenceSource:
      parsed.bootstrapEvidenceSource === 'runtime_bootstrap_checkin' ||
      parsed.bootstrapEvidenceSource === 'app_managed_bootstrap'
        ? parsed.bootstrapEvidenceSource
        : undefined,
    bootstrapMode:
      parsed.bootstrapMode === 'model_tool_checkin' ||
      parsed.bootstrapMode === 'app_managed_context'
        ? parsed.bootstrapMode
        : undefined,
    appManagedBootstrapCandidate: normalizeOpenCodeAppManagedBootstrapCandidate(
      parsed.appManagedBootstrapCandidate
    ),
    livenessKind,
    pidSource: normalizePidSource(parsed.pidSource),
    runtimeDiagnostic: normalizeOptionalString(parsed.runtimeDiagnostic),
    runtimeDiagnosticSeverity: normalizeDiagnosticSeverity(parsed.runtimeDiagnosticSeverity),
    bootstrapStalled: toBoolean(parsed.bootstrapStalled),
    runtimeLastSeenAt: normalizeOptionalString(parsed.runtimeLastSeenAt),
    firstSpawnAcceptedAt:
      typeof parsed.firstSpawnAcceptedAt === 'string' ? parsed.firstSpawnAcceptedAt : undefined,
    lastHeartbeatAt:
      typeof parsed.lastHeartbeatAt === 'string' ? parsed.lastHeartbeatAt : undefined,
    lastRuntimeAliveAt:
      typeof parsed.lastRuntimeAliveAt === 'string' ? parsed.lastRuntimeAliveAt : undefined,
    lastEvaluatedAt:
      typeof parsed.lastEvaluatedAt === 'string' ? parsed.lastEvaluatedAt : updatedAtFallback,
    sources: Object.values(sources).some(Boolean) ? sources : undefined,
    diagnostics: Array.isArray(parsed.diagnostics)
      ? parsed.diagnostics.filter(
          (item): item is string => typeof item === 'string' && item.trim().length > 0
        )
      : undefined,
  };
  const launchState =
    parsed.launchState === 'starting' ||
    parsed.launchState === 'runtime_pending_bootstrap' ||
    parsed.launchState === 'runtime_pending_permission' ||
    parsed.launchState === 'confirmed_alive' ||
    parsed.launchState === 'failed_to_start' ||
    parsed.launchState === 'skipped_for_launch'
      ? parsed.launchState
      : deriveMemberLaunchState(next);
  next.launchState = launchState;
  if (!isPersistedBootstrapStalled(next)) {
    next.bootstrapStalled = undefined;
  }
  next.diagnostics = next.diagnostics?.length ? next.diagnostics : buildDiagnostics(next);
  return next;
}

export function createPersistedLaunchSnapshot(params: {
  teamName: string;
  expectedMembers: readonly string[];
  bootstrapExpectedMembers?: readonly string[];
  includeLeadMembers?: boolean;
  leadSessionId?: string;
  launchPhase?: PersistedTeamLaunchPhase;
  members?: Record<string, PersistedTeamLaunchMemberState>;
  updatedAt?: string;
}): PersistedTeamLaunchSnapshot {
  const updatedAt = params.updatedAt ?? new Date().toISOString();
  const shouldKeepExpectedMemberName = (name: string): boolean =>
    name.length > 0 && name !== 'user' && (params.includeLeadMembers || !isLeadMember({ name }));
  const expectedMembers = Array.from(
    new Set(params.expectedMembers.map(normalizeMemberName).filter(shouldKeepExpectedMemberName))
  );
  const bootstrapExpectedMembers = Array.from(
    new Set(
      (params.bootstrapExpectedMembers ?? expectedMembers)
        .map(normalizeMemberName)
        .filter(shouldKeepExpectedMemberName)
    )
  );
  const members = params.members ?? {};
  const launchPhase = params.launchPhase ?? 'active';

  for (const name of expectedMembers) {
    if (members[name]) {
      continue;
    }
    members[name] = {
      name,
      launchState: 'starting',
      skippedForLaunch: false,
      agentToolAccepted: false,
      runtimeAlive: false,
      bootstrapConfirmed: false,
      hardFailure: false,
      lastEvaluatedAt: updatedAt,
      diagnostics: [],
    };
  }

  // When the launch is over (finished/reconciled), members still in 'starting' state
  // (never spawned — agentToolAccepted is false) are unreachable and should be marked
  // as failed. Without this, they stay as 'pending' forever, causing the UI to show
  // "Last launch is still reconciling" indefinitely after a crash or incomplete launch.
  if (launchPhase !== 'active') {
    for (const name of expectedMembers) {
      const member = members[name];
      const isRecoverableOpenCodeSecondaryLane =
        member?.laneKind === 'secondary' &&
        member.laneOwnerProviderId === 'opencode' &&
        typeof member.laneId === 'string' &&
        member.laneId.trim().length > 0;
      if (
        member?.launchState === 'starting' &&
        !member.agentToolAccepted &&
        !member.runtimeAlive &&
        !member.bootstrapConfirmed &&
        !member.hardFailure &&
        !isRecoverableOpenCodeSecondaryLane
      ) {
        member.hardFailure = true;
        member.hardFailureReason =
          member.hardFailureReason ?? 'Teammate was never spawned during launch.';
        member.launchState = deriveMemberLaunchState(member);
        member.diagnostics = buildDiagnostics(member);
      }
    }
  }

  const summary = summarizePersistedLaunchMembers(expectedMembers, members);
  return {
    version: 2,
    teamName: params.teamName,
    updatedAt,
    ...(params.leadSessionId ? { leadSessionId: params.leadSessionId } : {}),
    launchPhase,
    expectedMembers,
    ...(bootstrapExpectedMembers.length > 0 &&
    bootstrapExpectedMembers.join('\u0000') !== expectedMembers.join('\u0000')
      ? { bootstrapExpectedMembers }
      : {}),
    members,
    summary,
    teamLaunchState: deriveTeamLaunchAggregateState(summary),
  };
}

export function snapshotFromRuntimeMemberStatuses(params: {
  teamName: string;
  expectedMembers: readonly string[];
  leadSessionId?: string;
  launchPhase?: PersistedTeamLaunchPhase;
  statuses: Record<string, RuntimeMemberSpawnState>;
  updatedAt?: string;
}): PersistedTeamLaunchSnapshot {
  const updatedAt = params.updatedAt ?? new Date().toISOString();
  const members: Record<string, PersistedTeamLaunchMemberState> = {};

  for (const expected of params.expectedMembers) {
    const name = normalizeMemberName(expected);
    if (!name || name === 'user' || isLeadMember({ name })) continue;
    const runtime = params.statuses[name];
    const sources: PersistedTeamLaunchMemberSources = {};
    if (runtime?.livenessSource === 'heartbeat') {
      sources.nativeHeartbeat = true;
      sources.inboxHeartbeat = true;
    }
    const skippedForLaunch =
      runtime?.skippedForLaunch === true || runtime?.launchState === 'skipped_for_launch';
    const runtimeAlive = skippedForLaunch ? false : preservesStrongRuntimeAlive(runtime);
    if (runtime?.livenessSource === 'process' && runtimeAlive) {
      sources.processAlive = true;
    }
    const launchState = runtime?.launchState ?? 'starting';
    const hardFailure =
      runtime?.launchState === 'skipped_for_launch'
        ? false
        : runtime?.hardFailure === true || launchState === 'failed_to_start';
    const entry: PersistedTeamLaunchMemberState = {
      name,
      launchState,
      skippedForLaunch,
      skipReason: runtime?.skipReason,
      skippedAt: runtime?.skippedAt,
      agentToolAccepted: skippedForLaunch ? false : runtime?.agentToolAccepted === true,
      runtimeAlive,
      bootstrapConfirmed: skippedForLaunch ? false : runtime?.bootstrapConfirmed === true,
      hardFailure,
      hardFailureReason: hardFailure ? (runtime?.hardFailureReason ?? runtime?.error) : undefined,
      pendingPermissionRequestIds: runtime?.pendingPermissionRequestIds?.length
        ? [...new Set(runtime.pendingPermissionRequestIds)]
        : undefined,
      livenessKind: runtime?.livenessKind,
      runtimeDiagnostic: runtime?.runtimeDiagnostic,
      runtimeDiagnosticSeverity: runtime?.runtimeDiagnosticSeverity,
      bootstrapStalled: runtime?.bootstrapStalled === true,
      runtimeLastSeenAt: runtime?.livenessLastCheckedAt,
      firstSpawnAcceptedAt: runtime?.firstSpawnAcceptedAt,
      lastHeartbeatAt: runtime?.lastHeartbeatAt,
      lastRuntimeAliveAt: runtimeAlive ? updatedAt : undefined,
      lastEvaluatedAt: runtime?.updatedAt ?? updatedAt,
      sources: Object.values(sources).some(Boolean) ? sources : undefined,
      diagnostics: undefined,
    };
    entry.launchState = deriveMemberLaunchState(entry);
    if (!isPersistedBootstrapStalled(entry)) {
      entry.bootstrapStalled = undefined;
    }
    entry.diagnostics = buildDiagnostics(entry);
    members[name] = entry;
  }

  return createPersistedLaunchSnapshot({
    teamName: params.teamName,
    expectedMembers: params.expectedMembers,
    leadSessionId: params.leadSessionId,
    launchPhase: params.launchPhase,
    members,
    updatedAt,
  });
}

export function snapshotToMemberSpawnStatuses(
  snapshot: PersistedTeamLaunchSnapshot | null
): Record<string, MemberSpawnStatusEntry> {
  if (!snapshot) return {};
  const statuses: Record<string, MemberSpawnStatusEntry> = {};
  const memberNames = Array.from(
    new Set([
      ...snapshot.expectedMembers.map(normalizeMemberName).filter(Boolean),
      ...Object.keys(snapshot.members).map(normalizeMemberName).filter(Boolean),
    ])
  );
  for (const memberName of memberNames) {
    const entry = snapshot.members[memberName];
    if (!entry) continue;
    let status: MemberSpawnStatusEntry['status'] = 'offline';
    let livenessSource: MemberSpawnLivenessSource | undefined;
    const skippedForLaunch =
      entry.launchState === 'skipped_for_launch' || entry.skippedForLaunch === true;
    const runtimeAlive = skippedForLaunch ? false : preservesStrongRuntimeAlive(entry);
    const openCodeBootstrapPending = isOpenCodeSecondaryBootstrapPending(entry);
    const bootstrapStalled = isPersistedBootstrapStalled(entry);
    if (entry.launchState === 'failed_to_start') {
      status = 'error';
    } else if (entry.launchState === 'skipped_for_launch') {
      status = 'skipped';
    } else if (entry.launchState === 'confirmed_alive') {
      status = 'online';
      livenessSource = 'heartbeat';
    } else if (
      entry.launchState === 'runtime_pending_permission' ||
      entry.launchState === 'runtime_pending_bootstrap'
    ) {
      status =
        runtimeAlive && !openCodeBootstrapPending && !bootstrapStalled ? 'online' : 'waiting';
      livenessSource =
        runtimeAlive && !openCodeBootstrapPending && !bootstrapStalled ? 'process' : undefined;
    } else {
      status = entry.agentToolAccepted ? 'waiting' : 'spawning';
    }
    statuses[memberName] = {
      status,
      launchState: entry.launchState,
      error: entry.hardFailure ? entry.hardFailureReason : undefined,
      hardFailureReason: entry.hardFailureReason,
      skippedForLaunch: entry.skippedForLaunch,
      skipReason: entry.skipReason,
      skippedAt: entry.skippedAt,
      livenessSource,
      agentToolAccepted: skippedForLaunch ? false : entry.agentToolAccepted,
      runtimeAlive,
      bootstrapConfirmed: skippedForLaunch ? false : entry.bootstrapConfirmed,
      hardFailure: skippedForLaunch ? false : entry.hardFailure,
      pendingPermissionRequestIds: entry.pendingPermissionRequestIds,
      bootstrapEvidenceSource: entry.bootstrapEvidenceSource,
      bootstrapMode: entry.bootstrapMode,
      appManagedBootstrapCandidate: entry.appManagedBootstrapCandidate,
      livenessKind: entry.livenessKind,
      runtimeDiagnostic: entry.runtimeDiagnostic,
      runtimeDiagnosticSeverity: entry.runtimeDiagnosticSeverity,
      bootstrapStalled,
      livenessLastCheckedAt: entry.runtimeLastSeenAt ?? entry.lastEvaluatedAt,
      firstSpawnAcceptedAt: entry.firstSpawnAcceptedAt,
      lastHeartbeatAt: entry.lastHeartbeatAt,
      updatedAt: entry.lastEvaluatedAt,
    };
  }
  return statuses;
}

export function normalizePersistedLaunchSnapshot(
  teamName: string,
  parsed: unknown
): PersistedTeamLaunchSnapshot | null {
  if (!parsed || typeof parsed !== 'object') {
    return null;
  }

  const maybeLegacy = parsed as LegacyPartialLaunchStateFile;
  if (maybeLegacy.state === 'partial_launch_failure') {
    const expectedMembers = Array.isArray(maybeLegacy.expectedMembers)
      ? maybeLegacy.expectedMembers.filter(
          (name): name is string => typeof name === 'string' && normalizeMemberName(name).length > 0
        )
      : [];
    const confirmedMembers = Array.isArray(maybeLegacy.confirmedMembers)
      ? maybeLegacy.confirmedMembers.filter(
          (name): name is string => typeof name === 'string' && normalizeMemberName(name).length > 0
        )
      : [];
    const missingMembers = Array.isArray(maybeLegacy.missingMembers)
      ? maybeLegacy.missingMembers.filter(
          (name): name is string => typeof name === 'string' && normalizeMemberName(name).length > 0
        )
      : [];
    if (expectedMembers.length === 0 || missingMembers.length === 0) {
      return null;
    }
    const updatedAt =
      typeof maybeLegacy.updatedAt === 'string' ? maybeLegacy.updatedAt : new Date().toISOString();
    const members: Record<string, PersistedTeamLaunchMemberState> = {};
    for (const name of expectedMembers) {
      const failed = missingMembers.includes(name);
      const confirmed = confirmedMembers.includes(name);
      const entry: PersistedTeamLaunchMemberState = {
        name,
        launchState: failed ? 'failed_to_start' : confirmed ? 'confirmed_alive' : 'starting',
        agentToolAccepted: true,
        runtimeAlive: false,
        bootstrapConfirmed: confirmed,
        hardFailure: failed,
        hardFailureReason: failed
          ? 'Legacy partial launch marker reported teammate missing.'
          : undefined,
        lastEvaluatedAt: updatedAt,
        diagnostics: undefined,
      };
      entry.diagnostics = buildDiagnostics(entry);
      members[name] = entry;
    }
    return createPersistedLaunchSnapshot({
      teamName,
      expectedMembers,
      leadSessionId:
        typeof maybeLegacy.leadSessionId === 'string' && maybeLegacy.leadSessionId.trim().length > 0
          ? maybeLegacy.leadSessionId.trim()
          : undefined,
      launchPhase: 'reconciled',
      members,
      updatedAt,
    });
  }

  const record = parsed as Record<string, unknown>;
  if (record.version !== 2) {
    return null;
  }
  const expectedMembers = Array.isArray(record.expectedMembers)
    ? record.expectedMembers.filter(
        (name): name is string => typeof name === 'string' && normalizeMemberName(name).length > 0
      )
    : [];
  const bootstrapExpectedMembers = Array.isArray(record.bootstrapExpectedMembers)
    ? record.bootstrapExpectedMembers.filter(
        (name): name is string => typeof name === 'string' && normalizeMemberName(name).length > 0
      )
    : undefined;
  const updatedAt =
    typeof record.updatedAt === 'string' && record.updatedAt.trim().length > 0
      ? record.updatedAt
      : new Date().toISOString();
  const normalizedMembers: Record<string, PersistedTeamLaunchMemberState> = {};
  const rawMembers =
    record.members && typeof record.members === 'object'
      ? (record.members as Record<string, unknown>)
      : {};
  for (const [memberName, value] of Object.entries(rawMembers)) {
    const normalized = normalizePersistedMemberState(memberName, value, updatedAt);
    if (!normalized) continue;
    normalizedMembers[normalized.name] = normalized;
  }
  return createPersistedLaunchSnapshot({
    teamName:
      typeof record.teamName === 'string' && record.teamName.trim().length > 0
        ? record.teamName.trim()
        : teamName,
    expectedMembers,
    leadSessionId:
      typeof record.leadSessionId === 'string' && record.leadSessionId.trim().length > 0
        ? record.leadSessionId.trim()
        : undefined,
    launchPhase:
      record.launchPhase === 'active' ||
      record.launchPhase === 'finished' ||
      record.launchPhase === 'reconciled'
        ? record.launchPhase
        : 'finished',
    bootstrapExpectedMembers,
    members: normalizedMembers,
    updatedAt,
  });
}
