const peerConnections = {};

async function joinChannelCall() {
    LogMessage("Current username: " + currentUsername);
    LogMessage("Current channel: " + currentChannelId);

    if (!currentUsername || !currentChannelId) {
        LogMessage("Cannot join RTC: missing username or channel.");
        return;
    }

    await RelaySocket.joinRtcChannel();
    await Media.ensureLocalMedia();

    const participants = await RelaySocket.getRtcParticipants();

    LogMessage("Participants: " + JSON.stringify(participants));

    const existingUsers = participants.filter(x => x !== currentUsername);

    if (existingUsers.length === 0) {
        LogMessage("Joined call as first participant. Waiting for others...");
        return;
    }

    for (const username of existingUsers) {
        await sendOffer(username); //Creates an offer to each person in call for MESH RTC
    }
}

async function sendOffer(username) {
    const pc = await ensurePeerConnectionForUser(username);

    await Media.applyLocalStreamToPeerConnection(pc, username);

    const offer = await pc.createOffer();
    // LogMessage(`Offer created: ${JSON.stringify(offer)}`);
    await pc.setLocalDescription(offer);

    await RelaySocket.sendRtcSignal({
        type: "rtc_offer",
        channelId: currentChannelId,
        from: currentUsername,
        to: username,
        sdp: offer.sdp
    });

    LogMessage(`Sent offer to ${username}`);
}

async function handleRtcSignal(rawJson) {
    try {
        const msg = typeof rawJson === "string" ? JSON.parse(rawJson) : rawJson;

        if (!msg || !msg.type) return;
        if (msg.from === currentUsername) return;

        if (msg.to && msg.to !== currentUsername) {
            LogMessage(`Ignoring RTC signal meant for ${msg.to}`);
            return;
        }

        LogMessage(`Received signal: ${msg.type} from ${msg.from}`);

        if (msg.type === "rtc_offer") {
            await handleOffer(msg);
            return;
        }

        if (msg.type === "rtc_answer") {
            await handleAnswer(msg);
            return;
        }

        if (msg.type === "rtc_ice") {
            await handleIce(msg);
            return;
        }

        if (msg.type === "rtc_leave") {
            closePeerConnection(msg.from);
            return;
        }

        LogMessage("Unhandled RTC signal type: " + msg.type);
    } catch (err) {
        LogMessage("handleRtcSignal failed: " + err);
    }
}

async function handleOffer(msg) {
    LogMessage(`Offer handler: ${msg}`);
    const pc = await ensurePeerConnectionForUser(msg.from);

    await Media.ensureLocalMedia();
    await Media.applyLocalStreamToPeerConnection(pc, msg.from);
    // const offer = JSON.parse(msg.offer);
    await pc.setRemoteDescription({
        type: "offer",
        sdp: msg.sdp
    });

    const answer = await pc.createAnswer();
    await pc.setLocalDescription(answer);

    await RelaySocket.sendRtcSignal({
        type: "rtc_answer",
        channelId: currentChannelId,
        from: currentUsername,
        to: msg.from,
        sdp: answer.sdp
    });

    LogMessage(`Sent answer to ${msg.from}`);
}

async function handleAnswer(msg) {
    const pc = peerConnections[msg.from];

    if (!pc) {
        LogMessage(`No peer connection found for answer from ${msg.from}`);
        return;
    }

    await pc.setRemoteDescription({
        type: "answer",
        sdp: msg.sdp
    });

    LogMessage(`Applied answer from ${msg.from}`);
}

async function handleIce(msg) {
    const pc = peerConnections[msg.from];

    if (!pc) {
        LogMessage(`No peer connection found for ICE from ${msg.from}`);
        return;
    }

    if (!msg.candidate) return;
    
    const candidateInit = {
        candidate: msg.candidate,
        sdpMid: msg.sdpMid,
        sdpMLineIndex: msg.sdpMLineIndex
    };

    await pc.addIceCandidate(candidateInit);

    LogMessage(`Applied ICE from ${msg.from}`);
}

async function ensurePeerConnectionForUser(username) {
    if (peerConnections[username]) {
        return peerConnections[username];
    }

    const pc = new RTCPeerConnection(configuration);
    peerConnections[username] = pc;

    pc.onicecandidate = async event => {
        if (!event.candidate) return;

        await RelaySocket.sendRtcSignal({
            type: "rtc_ice",
            channelId: currentChannelId,
            from: currentUsername,
            to: username,
            candidate: event.candidate.candidate,
            sdpMid: event.candidate.sdpMid,
            sdpMLineIndex: event.candidate.sdpMLineIndex
        });
    };

    pc.ontrack = event => {
        LogMessage(`Remote track received from ${username}`);

        const stream = event.streams[0];
        if (!stream) return;

        Media.attachRemoteStream(username, stream);
    };

    pc.onconnectionstatechange = () => {
        LogMessage(`Connection ${username}: ${pc.connectionState}`);

        if (
            pc.connectionState === "failed" ||
            pc.connectionState === "closed" ||
            pc.connectionState === "disconnected"
        ) {
            closePeerConnection(username);
        }
    };

    return pc;
}

async function leaveChannelCall() {
    await RelaySocket.sendRtcSignal({
        type: "rtc_leave",
        channelId: currentChannelId,
        from: currentUsername
    });

    for (const username of Object.keys(peerConnections)) {
        closePeerConnection(username);
    }

    await RelaySocket.leaveRtcChannel();

    LogMessage("Left RTC channel");
}

function closePeerConnection(username) {
    const pc = peerConnections[username];
    if (!pc) return;

    pc.close();
    delete peerConnections[username];

    Media.removeRemoteStream(username);

    LogMessage(`Closed RTC connection with ${username}`);
}

window.RelayRtc = {
    joinChannelCall,
    leaveChannelCall,
    handleRtcSignal,
    peerConnections
};

window.handleRtcSignal = handleRtcSignal;