import { invoke } from '@tauri-apps/api/core';
import { listen, type UnlistenFn } from '@tauri-apps/api/event';
import { NewsItem } from '../types/financial';
import {
  AgentDeltaEvent,
  AgentErrorEvent,
  AgentResultEvent,
  ChatStreamStart,
  ExecuteTerminalCommandRequest,
  PanelPayload,
  ResolvedTerminalCommandResponse,
  StartChatStreamRequest,
  TerminalCommandResponse,
  TransportPanelPayload,
} from '../types/terminal';

interface StreamCallbacks {
  workspaceId: string;
  onDelta: (event: AgentDeltaEvent) => void;
  onResult: (event: AgentResultEvent) => void;
  onError: (event: AgentErrorEvent) => void;
}

const deserializePanelPayload = (payload: TransportPanelPayload): PanelPayload => {
  if (payload.type !== 'news') {
    return payload;
  }

  // News timestamps cross the Tauri boundary as strings and are rehydrated here for panel rendering.
  return {
    ...payload,
    data: payload.data.map(
      (item): NewsItem => ({
        ...item,
        timestamp: new Date(item.timestamp),
      }),
    ),
  };
};

class TerminalBridge {
  private listenersReady: Promise<void> | null = null;
  private unlistenFns: UnlistenFn[] = [];
  private streamCallbacks = new Map<string, StreamCallbacks>();

  private async ensureListeners() {
    if (this.listenersReady) {
      return this.listenersReady;
    }

    this.listenersReady = Promise.all([
      // Route incremental stream events back to the workspace that initiated the request.
      listen<AgentDeltaEvent>('agent_delta', (event) => {
        const callbacks = this.streamCallbacks.get(event.payload.requestId);
        if (!callbacks || callbacks.workspaceId !== event.payload.workspaceId) {
          return;
        }
        callbacks.onDelta(event.payload);
      }),
      listen<AgentResultEvent>('agent_result', (event) => {
        const callbacks = this.streamCallbacks.get(event.payload.requestId);
        if (!callbacks || callbacks.workspaceId !== event.payload.workspaceId) {
          return;
        }
        callbacks.onResult(event.payload);
        this.streamCallbacks.delete(event.payload.requestId);
      }),
      listen<AgentErrorEvent>('agent_error', (event) => {
        const callbacks = this.streamCallbacks.get(event.payload.requestId);
        if (!callbacks || callbacks.workspaceId !== event.payload.workspaceId) {
          return;
        }
        callbacks.onError(event.payload);
        this.streamCallbacks.delete(event.payload.requestId);
      }),
    ]).then((unlistenFns) => {
      this.unlistenFns = unlistenFns;
    });

    return this.listenersReady;
  }

  async executeTerminalCommand(
    request: ExecuteTerminalCommandRequest,
  ): Promise<ResolvedTerminalCommandResponse> {
    const response = await invoke<TerminalCommandResponse>('execute_terminal_command', {
      request,
    });

    if (response.kind === 'text') {
      return response;
    }

    return {
      kind: 'panel',
      panel: deserializePanelPayload(response.panel),
    };
  }

  async startChatStream(
    request: StartChatStreamRequest,
    callbacks: Omit<StreamCallbacks, 'workspaceId'>,
  ): Promise<ChatStreamStart> {
    await this.ensureListeners();

    const start = await invoke<ChatStreamStart>('start_chat_stream', {
      request,
    });

    // Register callbacks after the backend returns the request id used by subsequent stream events.
    this.streamCallbacks.set(start.requestId, {
      workspaceId: request.workspaceId,
      ...callbacks,
    });

    return start;
  }

  async dispose() {
    for (const unlisten of this.unlistenFns) {
      await unlisten();
    }
    this.unlistenFns = [];
    this.streamCallbacks.clear();
    this.listenersReady = null;
  }
}

export const terminalBridge = new TerminalBridge();
