import { invoke } from '@tauri-apps/api/core';
import { listen, type UnlistenFn } from '@tauri-apps/api/event';
import {
  AgentStreamItemEvent,
  ChatStreamStart,
  LookupCompanyRequest,
  ExecuteTerminalCommandRequest,
  ResolveAgentToolApprovalRequest,
  StartChatStreamRequest,
  TerminalCommandResponse,
} from '../types/terminal';
import { Company } from '../types/financial';

interface StreamCallbacks {
  workspaceId: string;
  onStreamItem: (event: Omit<AgentStreamItemEvent, 'response'> & {
    response?: TerminalCommandResponse;
  }) => void;
}

const createRequestId = (): string => {
  if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
    return crypto.randomUUID();
  }

  return `request-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
};

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([
      listen<AgentStreamItemEvent>('agent_stream_item', (event) => {
        const callbacks = this.streamCallbacks.get(event.payload.requestId);
        if (!callbacks || callbacks.workspaceId !== event.payload.workspaceId) {
          return;
        }
        callbacks.onStreamItem({
          ...event.payload,
          response: event.payload.response,
        });
        if (
          event.payload.kind === 'stream_complete' ||
          event.payload.kind === 'error'
        ) {
          this.streamCallbacks.delete(event.payload.requestId);
        }
      }),
    ]).then((unlistenFns) => {
      this.unlistenFns = unlistenFns;
    });

    return this.listenersReady;
  }

  async executeTerminalCommand(
    request: ExecuteTerminalCommandRequest,
  ): Promise<TerminalCommandResponse> {
    return invoke<TerminalCommandResponse>('execute_terminal_command', {
      request,
    });
  }

  async lookupCompany(request: LookupCompanyRequest): Promise<Company> {
    return invoke<Company>('lookup_company', {
      request,
    });
  }

  // CRITICAL ISSUE CONFIRMED: Chat streaming relies on timing workaround instead of explicit protocol
  //
  // PROBLEM: The frontend registers callbacks AFTER start_chat_stream returns, creating a race condition
  // where early stream events can be lost. The backend compensates with a 30ms sleep (see terminal.rs:70),
  // but this is brittle and can still fail under load or slower UI conditions.
  //
  // IMPACT:
  // - Data loss: Early stream events may be dropped
  // - Non-deterministic: Race window varies with system conditions
  // - Not testable: Race conditions are hard to reproduce in tests
  //
  // RECOMMENDED FIX: Implement explicit handshake:
  // 1. Frontend registers callbacks before invoking start_chat_stream
  // 2. Backend waits for "ready" signal before streaming
  // 3. Or pass callbacks in request payload
  async startChatStream(
    request: Omit<StartChatStreamRequest, 'requestId'>,
    callbacks: Omit<StreamCallbacks, 'workspaceId'>,
  ): Promise<ChatStreamStart> {
    await this.ensureListeners();

    const requestId = createRequestId();
    this.streamCallbacks.set(requestId, {
      workspaceId: request.workspaceId,
      ...callbacks,
    });

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

      if (start.requestId !== requestId) {
        this.streamCallbacks.delete(requestId);
        throw new Error('Stream request ID mismatch.');
      }

      return start;
    } catch (error) {
      this.streamCallbacks.delete(requestId);
      throw error;
    }
  }

  async resolveAgentToolApproval(
    request: ResolveAgentToolApprovalRequest,
  ): Promise<void> {
    await invoke('resolve_agent_tool_approval', {
      request,
    });
  }

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

export const terminalBridge = new TerminalBridge();
