import { PROTOCOL_VERSION } from '@platform-modules/chatgpt-orchestrator-protocol';
import type { BackendContinuityView, RecentContinuityEvent, UiRequest } from './ui-model.js';

export interface OperatorResultFrame {
  protocolVersion: number;
  type: 'operator.result';
  executorId: string;
  requestId: string;
  workerId: string;
  success: boolean;
  errorCode?: string;
  error?: string;
  view?: BackendContinuityView;
}

export interface WorkerContinuityFrame {
  protocolVersion: number;
  type: 'worker.continuity';
  executorId: string;
  view: BackendContinuityView;
}

export type ContinuityControlServerFrame =
  | { kind: 'operator-result'; frame: OperatorResultFrame }
  | { kind: 'worker-continuity'; frame: WorkerContinuityFrame };

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}

function optionalString(value: unknown): string | undefined {
  return typeof value === 'string' ? value : undefined;
}

function optionalNullableString(value: unknown): string | null | undefined {
  return value === null || typeof value === 'string' ? value : undefined;
}

function parseRecentEvents(value: unknown): RecentContinuityEvent[] | undefined {
  if (!Array.isArray(value)) return undefined;
  const events: RecentContinuityEvent[] = [];
  for (const candidate of value) {
    if (!isRecord(candidate) || typeof candidate.type !== 'string' || typeof candidate.timestamp !== 'string') return undefined;
    events.push({
      type: candidate.type,
      timestamp: candidate.timestamp,
      ...(typeof candidate.detail === 'string' ? { detail: candidate.detail } : {}),
    });
  }
  return events;
}

export function parseBackendContinuityView(value: unknown): BackendContinuityView | null {
  if (!isRecord(value) || typeof value.workerId !== 'string') return null;
  const dispositions = new Set(['running', 'awaiting_execution', 'awaiting_human', 'awaiting_dependency', 'paused', 'terminal']);
  if (typeof value.disposition !== 'string' || !dispositions.has(value.disposition)) return null;
  if (value.policyMode !== 'auto' && value.policyMode !== 'manual') return null;
  if (value.attemptNumber !== null && (!Number.isInteger(value.attemptNumber) || (value.attemptNumber as number) < 1)) return null;
  if (!Number.isInteger(value.autoResumeCount) || (value.autoResumeCount as number) < 0) return null;
  if (value.lastProgressAt !== null && typeof value.lastProgressAt !== 'string') return null;
  if (typeof value.needsUser !== 'boolean' || typeof value.isWorking !== 'boolean' || typeof value.isResuming !== 'boolean') return null;

  const policySources = new Set(['worker', 'run', 'default']);
  if (value.policySource !== undefined && (typeof value.policySource !== 'string' || !policySources.has(value.policySource))) return null;
  const pauseReasons = new Set(['user', 'stall', 'error']);
  if (value.pauseReason !== undefined && value.pauseReason !== null && (typeof value.pauseReason !== 'string' || !pauseReasons.has(value.pauseReason))) return null;
  if (value.noProgressAttempts !== undefined && value.noProgressAttempts !== null && (!Number.isInteger(value.noProgressAttempts) || (value.noProgressAttempts as number) < 0)) return null;
  for (const key of ['stallWarningAt', 'stallPausedAt', 'blockReason'] as const) {
    if (value[key] !== undefined && value[key] !== null && typeof value[key] !== 'string') return null;
  }
  const recentEvents = value.recentEvents === undefined ? undefined : parseRecentEvents(value.recentEvents);
  if (value.recentEvents !== undefined && recentEvents === undefined) return null;
  const stallMetadataAvailable = ['pauseReason', 'noProgressAttempts', 'stallWarningAt', 'stallPausedAt'].some((key) => Object.prototype.hasOwnProperty.call(value, key));

  return {
    workerId: value.workerId,
    ...(optionalString(value.displayName) ? { displayName: optionalString(value.displayName)! } : {}),
    ...(optionalString(value.assignmentTitle) ? { assignmentTitle: optionalString(value.assignmentTitle)! } : {}),
    ...(optionalString(value.runId) ? { runId: optionalString(value.runId)! } : {}),
    ...(optionalString(value.runTitle) ? { runTitle: optionalString(value.runTitle)! } : {}),
    ...(optionalString(value.workerName) ? { workerName: optionalString(value.workerName)! } : {}),
    disposition: value.disposition as BackendContinuityView['disposition'],
    policyMode: value.policyMode,
    ...(value.policySource ? { policySource: value.policySource as NonNullable<BackendContinuityView['policySource']> } : {}),
    attemptNumber: value.attemptNumber as number | null,
    autoResumeCount: value.autoResumeCount as number,
    lastProgressAt: value.lastProgressAt as string | null,
    ...(optionalNullableString(value.blockReason) !== undefined ? { blockReason: optionalNullableString(value.blockReason)! } : {}),
    ...(value.pauseReason !== undefined ? { pauseReason: value.pauseReason as NonNullable<BackendContinuityView['pauseReason']> | null } : {}),
    ...(value.noProgressAttempts !== undefined ? { noProgressAttempts: value.noProgressAttempts as number | null } : {}),
    ...(optionalNullableString(value.stallWarningAt) !== undefined ? { stallWarningAt: optionalNullableString(value.stallWarningAt)! } : {}),
    ...(optionalNullableString(value.stallPausedAt) !== undefined ? { stallPausedAt: optionalNullableString(value.stallPausedAt)! } : {}),
    ...(recentEvents ? { recentEvents } : {}),
    needsUser: value.needsUser,
    isWorking: value.isWorking,
    isResuming: value.isResuming,
    ...(typeof value.updatedAt === 'string' ? { updatedAt: value.updatedAt } : {}),
    stallMetadataAvailable,
  };
}

export function parseContinuityControlServerFrame(value: unknown): ContinuityControlServerFrame | null {
  if (!isRecord(value)) return null;
  if (value.type === 'operator.result') {
    if (value.protocolVersion !== PROTOCOL_VERSION || typeof value.executorId !== 'string' || typeof value.requestId !== 'string' || typeof value.workerId !== 'string' || typeof value.success !== 'boolean') {
      throw new Error('Invalid operator.result frame.');
    }
    const view = value.view === undefined ? undefined : parseBackendContinuityView(value.view);
    if (value.view !== undefined && !view) throw new Error('Invalid operator.result continuity view.');
    return { kind: 'operator-result', frame: {
      protocolVersion: PROTOCOL_VERSION,
      type: 'operator.result',
      executorId: value.executorId,
      requestId: value.requestId,
      workerId: value.workerId,
      success: value.success,
      ...(typeof value.errorCode === 'string' ? { errorCode: value.errorCode } : {}),
      ...(typeof value.error === 'string' ? { error: value.error } : {}),
      ...(view ? { view } : {}),
    } };
  }
  if (value.type === 'worker.continuity') {
    if (value.protocolVersion !== PROTOCOL_VERSION || typeof value.executorId !== 'string') throw new Error('Invalid worker.continuity frame envelope.');
    const view = parseBackendContinuityView(value.view);
    if (!view) throw new Error('Invalid worker.continuity frame.');
    return { kind: 'worker-continuity', frame: { protocolVersion: PROTOCOL_VERSION, type: 'worker.continuity', executorId: value.executorId, view } };
  }
  return null;
}

export function operatorActionFrame(
  executorId: string,
  requestId: string,
  request: Exclude<UiRequest, { action: 'snapshot' }>,
): Record<string, unknown> {
  const action = request.action === 'continue-now' || request.action === 'continue-anyway'
    ? 'continue_now'
    : request.action === 'stop'
      ? 'cancel'
      : request.action === 'set-auto-resume'
        ? 'set_auto_resume'
        : 'pause';
  return {
    protocolVersion: PROTOCOL_VERSION,
    type: 'operator.action',
    executorId,
    requestId,
    workerId: request.workerId,
    action,
    ...(request.action === 'set-auto-resume' ? { enabled: request.enabled } : {}),
    ...(request.action === 'continue-anyway' ? { overrideBlock: true } : {}),
    idempotencyKey: requestId,
  };
}
