import type { SessionEventRecord } from '@shared/domain/event';
import { upsertSessionRunRecord } from '@shared/domain/runTimeline';
import type { ChatMessageRecord, SessionRecord } from '@shared/domain/session';
import type { WorkspaceState } from '@shared/domain/workspace';
import { mergeStreamingText } from '@shared/utils/streamingText';

export function applySessionEventWorkspace(
  current: WorkspaceState | undefined,
  event: SessionEventRecord,
): WorkspaceState | undefined {
  if (!current) {
    return current;
  }

  const sessionIndex = current.sessions.findIndex((session) => session.id === event.sessionId);
  if (sessionIndex < 0) {
    return current;
  }

  const session = current.sessions[sessionIndex];
  const nextSession = applySessionEvent(session, event);
  if (nextSession === session) {
    return current;
  }

  const nextSessions = current.sessions.slice();
  nextSessions[sessionIndex] = nextSession;
  return {
    ...current,
    sessions: nextSessions,
  };
}

function applySessionEvent(session: SessionRecord, event: SessionEventRecord): SessionRecord {
  switch (event.kind) {
    case 'status':
      return applyStatusEvent(session, event);
    case 'error':
      return applyErrorEvent(session, event);
    case 'message-delta':
      return applyMessageDeltaEvent(session, event);
    case 'message-complete':
      return applyMessageCompleteEvent(session, event);
    case 'run-updated':
      return applyRunUpdatedEvent(session, event);
    default:
      return session;
  }
}

function applyStatusEvent(session: SessionRecord, event: SessionEventRecord): SessionRecord {
  if (!event.status) {
    return session;
  }

  if (session.status === event.status && (event.status === 'error' || !session.lastError)) {
    return session;
  }

  return {
    ...session,
    status: event.status,
    lastError: event.status === 'error' ? session.lastError : undefined,
    updatedAt: event.occurredAt,
  };
}

function applyErrorEvent(session: SessionRecord, event: SessionEventRecord): SessionRecord {
  const error = event.error?.trim();
  if (session.status === 'error' && session.lastError === error) {
    return session;
  }

  return {
    ...session,
    status: 'error',
    lastError: error,
    updatedAt: event.occurredAt,
  };
}

function applyMessageDeltaEvent(session: SessionRecord, event: SessionEventRecord): SessionRecord {
  if (!event.messageId || (event.content === undefined && event.contentDelta === undefined)) {
    return session;
  }

  const resolvedContent = event.content ?? event.contentDelta ?? '';
  const messageIndex = session.messages.findIndex((message) => message.id === event.messageId);
  if (messageIndex >= 0) {
    const existing = session.messages[messageIndex];
    const nextMessage: ChatMessageRecord = {
      ...existing,
      authorName: event.authorName ?? existing.authorName,
      content: event.content ?? mergeStreamingText(existing.content, resolvedContent),
      pending: true,
    };

    if (
      nextMessage.authorName === existing.authorName
      && nextMessage.content === existing.content
      && existing.pending
    ) {
      return session;
    }

    const nextMessages = session.messages.slice();
    nextMessages[messageIndex] = nextMessage;
    return {
      ...session,
      messages: nextMessages,
      updatedAt: event.occurredAt,
    };
  }

  return {
    ...session,
    messages: [
      ...session.messages,
        {
          id: event.messageId,
          role: 'assistant',
          authorName: event.authorName ?? 'assistant',
          content: resolvedContent,
          createdAt: event.occurredAt,
          pending: true,
        },
    ],
    updatedAt: event.occurredAt,
  };
}

function applyMessageCompleteEvent(session: SessionRecord, event: SessionEventRecord): SessionRecord {
  if (!event.messageId) {
    return session;
  }

  const messageIndex = session.messages.findIndex((message) => message.id === event.messageId);
  if (messageIndex < 0) {
    return session;
  }

  const existing = session.messages[messageIndex];
  const nextMessage: ChatMessageRecord = {
    ...existing,
    authorName: event.authorName ?? existing.authorName,
    content: event.content ?? existing.content,
    pending: false,
  };

  if (
    nextMessage.authorName === existing.authorName
    && nextMessage.content === existing.content
    && !existing.pending
  ) {
    return session;
  }

  const nextMessages = session.messages.slice();
  nextMessages[messageIndex] = nextMessage;
  return {
    ...session,
    messages: nextMessages,
    updatedAt: event.occurredAt,
  };
}

function applyRunUpdatedEvent(session: SessionRecord, event: SessionEventRecord): SessionRecord {
  if (!event.run) {
    return session;
  }

  const nextRuns = upsertSessionRunRecord(session.runs, event.run);
  if (
    nextRuns.length === session.runs.length
    && nextRuns.every((run, index) => run === session.runs[index])
  ) {
    return session;
  }

  return {
    ...session,
    runs: nextRuns,
    updatedAt: event.occurredAt,
  };
}
