/* WebRTC signaling helpers extracted from games.ts
 * Exports:
 *  - audio: map of gameId -> peers
 *  - join(peers, session, config)
 *  - part(peers, session)
 *  - handleRelayICECandidate(gameId, cfg, session, debug)
 *  - handleRelaySessionDescription(gameId, cfg, session, debug)
 *  - broadcastPeerStateUpdate(gameId, cfg, session)
 */

import { Session } from "./games/types";

interface Peer {
  ws: any;
  name: string;
}

/* Map of session => peer_id => peer */
export const audio: Record<string, Record<string, Peer>> = {};

// Default send helper used when caller doesn't provide a safeSend implementation.
const defaultSend = (targetOrSession: any, message: any): boolean => {
  try {
    const target =
      targetOrSession && typeof targetOrSession.send === "function"
        ? targetOrSession
        : targetOrSession && targetOrSession.ws
        ? targetOrSession.ws
        : null;
    if (!target) return false;
    target.send(typeof message === "string" ? message : JSON.stringify(message));
    return true;
  } catch (e) {
    return false;
  }
};

export const join = (peers: Record<string, Peer>, session: Session): void => {
  const send = defaultSend;
  const ws = session.ws;

  if (!session.name) {
    console.error(`${session.short}: <- join - No name set yet. Audio not available.`);
    send(ws, {
      type: "join_status",
      status: "Error",
      message: "No name set yet. Audio not available.",
    });
    return;
  }

  console.log(`${session.short}: <- join - ${session.name}`);

  const peer = peers[session.id];
  // Use session.id as the canonical peer key
  if (peer) {
    console.log(`${session.short}:${session.id} - Already joined to Audio, updating WebSocket reference.`);
    try {
      const prev = peer.ws;
      if (prev && prev._pingInterval) {
        clearInterval(prev._pingInterval);
      }
    } catch (e) {
      /* ignore */
    }
    peer.ws = ws;

    send(ws, {
      type: "join_status",
      status: "Joined",
      message: "Reconnected",
    });

    // Tell the reconnecting client about existing peers
    for (const peerId in peers) {
      if (peerId === session.id) continue;

      send(ws, {
        type: "addPeer",
        data: {
          peer_id: peerId,
          peer_name: peers[peerId]!.name,
          should_create_offer: true,
        },
      });
    }

    // Tell existing peers about the reconnecting client
    for (const peerId in peers) {
      if (peerId === session.id) continue;

      send(peers[peerId]!.ws, {
        type: "addPeer",
        data: {
          peer_id: session.id,
          peer_name: session.name,
          should_create_offer: false,
        },
      });
    }

    return;
  }

  for (let peerId in peers) {
    // notify existing peers about the new client
    send(peers[peerId]!.ws, {
      type: "addPeer",
      data: {
        peer_id: session.id,
        peer_name: session.name,
        should_create_offer: false,
      },
    });

    // tell the new client about existing peers
    send(ws, {
      type: "addPeer",
      data: {
        peer_id: peerId,
        peer_name: peers[peerId]!.name || peerId,
        should_create_offer: true,
      },
    });
  }

  // Store peer keyed by session.id and keep the display name
  peers[session.id] = {
    ws,
    name: session.name,
  };

  send(ws, {
    type: "join_status",
    status: "Joined",
    message: "Successfully joined",
  });
};

export const part = (peers: Record<string, Peer>, session: Session): void => {
  const ws = session.ws;
  const send = defaultSend;

  if (!session.name) {
    console.error(`${session.id}: <- part - No name set yet. Audio not available.`);
    return;
  }

  if (!(session.id in peers)) {
    console.log(`${session.short}: <- ${session.name} - Does not exist in game audio.`);
    return;
  }

  console.log(`${session.short}: <- ${session.name} - Audio part.`);
  console.log(`${session.short}: -> removePeer - ${session.name}`);

  // Remove this peer
  delete peers[session.id];

  for (let peerId in peers) {
    send(peers[peerId]!.ws, {
      type: "removePeer",
      data: {
        peer_id: session.id,
        peer_name: session.name,
      },
    });
    send(ws, {
      type: "removePeer",
      data: {
        peer_id: peerId,
        peer_name: peers[peerId]!.name || peerId,
      },
    });
  }
};

export const handleRelayICECandidate = (gameId: string, cfg: any, session: Session, debug?: any) => {
  const send = defaultSend;

  const ws = session && session.ws;
  if (!cfg) {
    // Reply with an error to the sender to aid debugging (mirror Python behaviour)
    send(ws, { type: "error", data: { error: "relayICECandidate missing data" } });
    return;
  }

  if (!(gameId in audio)) {
    console.error(`${session.id}:${gameId} <- relayICECandidate - Does not have Audio`);
    return;
  }
  const { peer_id, candidate } = cfg;
  if (debug && debug.audio)
    console.log(`${session.id}:${gameId} <- relayICECandidate ${session.name} to ${peer_id}`, candidate);

  const message = JSON.stringify({
    type: "iceCandidate",
    data: {
      peer_id: session.id,
      peer_name: session.name,
      candidate,
    },
  });

  if (peer_id in audio[gameId]!) {
    const target = audio[gameId]![peer_id] as any;
    if (!target || !target.ws) {
      console.warn(`${session.id}:${gameId} relayICECandidate - target ${peer_id} has no ws`);
    } else if (!send(target.ws, message)) {
      console.warn(`${session.id}:${gameId} relayICECandidate - send failed to ${peer_id}`);
    }
  }
};

export const handleRelaySessionDescription = (gameId: string, cfg: any, session: any, debug?: any) => {
  const send = defaultSend;

  const ws = session && session.ws;
  if (!cfg) {
    send(ws, { type: "error", data: { error: "relaySessionDescription missing data" } });
    return;
  }

  if (!(gameId in audio)) {
    console.error(`${gameId} - relaySessionDescription - Does not have Audio`);
    return;
  }
  const { peer_id, session_description } = cfg;
  if (!peer_id) {
    send(ws, { type: "error", data: { error: "relaySessionDescription missing peer_id" } });
    return;
  }
  if (debug && debug.audio)
    console.log(
      `${session.short}:${gameId} - relaySessionDescription ${session.name} to ${peer_id}`,
      session_description
    );
  const message = JSON.stringify({
    type: "sessionDescription",
    data: {
      peer_id: session.id,
      peer_name: session.name,
      session_description,
    },
  });
  if (peer_id in audio[gameId]!) {
    const target = audio[gameId]![peer_id] as any;
    if (!target || !target.ws) {
      console.warn(`${session.id}:${gameId} relaySessionDescription - target ${peer_id} has no ws`);
    } else if (!send(target.ws, message)) {
      console.warn(`${session.id}:${gameId} relaySessionDescription - send failed to ${peer_id}`);
    }
  }
};

export const broadcastPeerStateUpdate = (gameId: string, cfg: any, session: any) => {
  const send = (targetOrSession: any, message: any) => {
    try {
      const target =
        targetOrSession && typeof targetOrSession.send === "function"
          ? targetOrSession
          : targetOrSession && targetOrSession.ws
          ? targetOrSession.ws
          : null;
      if (!target) return false;
      target.send(typeof message === "string" ? message : JSON.stringify(message));
      return true;
    } catch (e) {
      return false;
    }
  };

  if (!(gameId in audio)) {
    console.error(`${session.id}:${gameId} <- peer_state_update - Does not have Audio`);
    return;
  }

  const { muted, video_on } = cfg;
  if (!session.name) {
    console.error(`${session.id}: peer_state_update - unnamed session`);
    return;
  }

  const messagePayload = JSON.stringify({
    type: "peer_state_update",
    data: {
      peer_id: session.id,
      peer_name: session.name,
      muted,
      video_on,
    },
  });

  for (const otherId in audio[gameId]) {
    if (otherId === session.id) continue;
    try {
      const tgt = audio[gameId][otherId] as any;
      if (!tgt || !tgt.ws) {
        console.warn(`${session.id}:${gameId} peer_state_update - target ${otherId} has no ws`);
      } else if (!send(tgt.ws, messagePayload)) {
        console.warn(`${session.id}:${gameId} peer_state_update - send failed to ${otherId}`);
      }
    } catch (e) {
      console.warn(`Failed sending peer_state_update to ${otherId}:`, e);
    }
  }
};
