import { createContext } from "react";
import { io, Socket } from "socket.io-client";

const SOCKET_URL = "";

export interface ServerToClientEvents {
  thinking: (content: string) => void;
  response: (content: string) => void;
  toolCall: (
    callId: string,
    name: string,
    params: string,
    response: string,
  ) => void;
  workOrderComplete: (
    turnId: string,
    success: boolean,
    message?: string,
  ) => void;
}

export interface ClientToServerEvents {
  submitPrompt: (content: string) => void;
  requestSessionLock: (
    registration: any,
    project: any,
    chatSession: any,
    cb: (success: boolean, chatSessionId: string) => void,
  ) => void;
  requestWorkspaceMode: (
    registration: any,
    project: any,
    chatSession: any,
    mode: string,
    cb: (success: boolean, mode: string) => void,
  ) => void;
  releaseSessionLock: (
    registration: any,
    project: any,
    chatSession: any,
    cb: (success: boolean) => void,
  ) => void;
  sessionHeartbeat: (cb: (ack: boolean) => void) => void;
}

export interface SocketEvents {
  thinking: (content: string) => void;
  response: (content: string) => void;
  toolCall: (
    callId: string,
    name: string,
    params: string,
    response: string,
  ) => void;
  workOrderComplete: (
    turnId: string,
    success: boolean,
    message?: string,
  ) => void;
  "agent:thinking": (data: { agentId: string; thinking: string }) => void;
  "agent:response": (data: { agentId: string; chunk: string }) => void;
  "agent:tool-call": (data: {
    agentId: string;
    tool: string;
    args: unknown;
  }) => void;
  "agent:tool-result": (data: {
    agentId: string;
    tool: string;
    result: unknown;
  }) => void;
  "agent:complete": (data: { agentId: string }) => void;
  "log:entry": (data: {
    level: string;
    message: string;
    timestamp: number;
  }) => void;
  "chat:message": (data: {
    agentId: string;
    message: string;
    role: "user" | "assistant" | "system";
  }) => void;
  workspaceModeChanged: (mode: string) => void;
  connect: () => void;
  disconnect: (reason: string) => void;
  error: (error: Error) => void;
}

class SocketClient {
  private _socket: Socket | null = null;
  private eventListeners: Map<string, Set<(...args: unknown[]) => void>> =
    new Map();
  private reconnectAttempts = 0;
  private maxReconnectAttempts = 5;
  private jwt: string | null = null;
  private heartbeatInterval: ReturnType<typeof setInterval> | null = null;

  get connected(): boolean {
    return this._socket?.connected ?? false;
  }

  get socket(): Socket | null {
    return this._socket;
  }

  connect(token: string): void {
    if (this._socket?.connected) {
      return;
    }

    this.jwt = token;

    this._socket = io(SOCKET_URL, {
      auth: {
        token: this.jwt,
      },
      transports: ["websocket", "polling"],
      reconnection: true,
      reconnectionAttempts: this.maxReconnectAttempts,
      reconnectionDelay: 1000,
      reconnectionDelayMax: 5000,
    });

    if (!this.socket) {
      return;
    }

    // Forward server events to our event listeners
    this.socket.on("thinking", (content: string) => {
      this.emit("thinking", content);
    });

    this.socket.on("response", (content: string) => {
      this.emit("response", content);
    });

    this.socket.on(
      "toolCall",
      (callId: string, name: string, params: string, response: string) => {
        this.emit("toolCall", callId, name, params, response);
      },
    );

    this.socket.on(
      "workOrderComplete",
      (turnId: string, success: boolean, message?: string) => {
        this.emit("workOrderComplete", turnId, success, message);
      },
    );

    this.socket.on("workspaceModeChanged", (mode: string) => {
      this.emit("workspaceModeChanged", mode);
    });

    this._socket.on("connect", () => {
      this.reconnectAttempts = 0;
      this.emit("connect");
    });

    this._socket.on("disconnect", (reason) => {
      this.emit("disconnect", reason);
    });

    this._socket.on("connect_error", (error) => {
      this.reconnectAttempts++;
      this.emit("error", error);
    });
  }

  disconnect(): void {
    this.stopSessionHeartbeat();
    if (this._socket) {
      this._socket.disconnect();
      this._socket = null;
      this.jwt = null;
    }
  }

  on<K extends keyof SocketEvents>(event: K, callback: SocketEvents[K]): void {
    if (!this.eventListeners.has(event)) {
      this.eventListeners.set(event, new Set());
    }
    this.eventListeners
      .get(event)!
      .add(callback as (...args: unknown[]) => void);
  }

  off<K extends keyof SocketEvents>(event: K, callback: SocketEvents[K]): void {
    const listeners = this.eventListeners.get(event);
    if (listeners) {
      listeners.delete(callback as (...args: unknown[]) => void);
    }
  }

  emit<K extends keyof SocketEvents>(
    event: K,
    ...args: Parameters<SocketEvents[K]>
  ): void {
    const listeners = this.eventListeners.get(event);
    if (listeners) {
      listeners.forEach((callback) => callback(...args));
    }
  }

  send<K extends keyof SocketEvents>(
    event: K,
    ...args: Parameters<SocketEvents[K]>
  ): void {
    if (this._socket?.connected) {
      this._socket.emit(event, ...args);
    }
  }

  emitServer<K extends keyof ClientToServerEvents>(
    event: K,
    ...args: Parameters<ClientToServerEvents[K]>
  ): void {
    if (this._socket?.connected) {
      this._socket.emit(event, ...args);
    }
  }

  submitPrompt(
    prompt: string,
    cb: (success: boolean, data: { turnId?: string; message?: string }) => void,
  ): void {
    if (this._socket?.connected) {
      this._socket.emit('submitPrompt', prompt, cb);
    } else {
      cb(false, { message: 'Socket not connected' });
    }
  }

  requestSessionLock(
    registration: any,
    project: any,
    chatSession: any,
  ): Promise<boolean> {
    return new Promise((resolve) => {
      if (this._socket?.connected) {
        this._socket.emit(
          "requestSessionLock",
          registration,
          project,
          chatSession,
          resolve,
        );
      } else {
        resolve(false);
      }
    });
  }

  requestWorkspaceMode(
    registration: any,
    project: any,
    chatSession: any,
    mode: string,
  ): Promise<{ success: boolean; mode: string; reason?: string }> {
    return new Promise((resolve) => {
      if (this._socket?.connected) {
        this._socket.emit(
          "requestWorkspaceMode",
          registration,
          project,
          chatSession,
          mode,
          (success: boolean, mode: string, reason?: string) =>
            resolve({ success, mode, reason }),
        );
      } else {
        resolve({ success: false, mode: "" });
      }
    });
  }

  releaseSessionLock(
    registration: any,
    project: any,
    chatSession: any,
  ): Promise<boolean> {
    return new Promise((resolve) => {
      if (this._socket?.connected) {
        this._socket.emit(
          "releaseSessionLock",
          registration,
          project,
          chatSession,
          resolve,
        );
      } else {
        resolve(false);
      }
    });
  }

  startSessionHeartbeat(): void {
    if (this.heartbeatInterval) return;
    this.heartbeatInterval = setInterval(() => {
      if (this._socket?.connected) {
        this._socket.emit("sessionHeartbeat", (ack: boolean) => {
          if (!ack) {
            console.warn("sessionHeartbeat: drone did not acknowledge");
          }
        });
      }
    }, 19000);
  }

  stopSessionHeartbeat(): void {
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
      this.heartbeatInterval = null;
    }
  }
}

export const socketClient = new SocketClient();

export const SocketContext = createContext<Socket | null>(null);
