import * as IO from "socket.io-client";

import type {
    Socket as SocketType,
    SocketOptions,
    ManagerOptions,
} from "socket.io-client";

interface SocketData {
    readonly socket: SocketType;
    readonly routes?: {
        [eventName: string]: (...any) => any | Promise<any>;
    };
}

interface SocketDataOwner extends SocketData {
    children_db?: { [socketId: string]: RTCPeerConnection };
}

interface SocketConfig extends Partial<IO.ManagerOptions & IO.SocketOptions> {
    cid: string;
}

class PeerConnectionVisitor implements SocketData {
    private peer: RTCPeerConnection;

    private answer: RTCSessionDescriptionInit;

    readonly routes: SocketData["routes"] = {
        "peerconnection/visitor/offer": async (
            socketId: SocketType["id"],
            offer: RTCSessionDescriptionInit,
        ) => {
            console.log("GET OFFER", {
                socketId,
                kek: this.socket.id,
                bool: socketId == this.socket.id
            })
            if (socketId == this.socket.id) {
                await this.initOffer(offer);
                await this.initAnswer();
            }
        },
    };
    readonly socket: SocketType;

    protected async initOffer(offer: RTCSessionDescriptionInit) {
        await this.peer.setRemoteDescription(offer);
        this.peer.restartIce();
        console.log("[PeerConnection] Status:", {
            signal: this.peer.signalingState,
            connection: this.peer.connectionState
        })
    }

    protected async initAnswer() {
        this.answer = await this.peer.createAnswer();
        await this.peer.setLocalDescription(this.answer);
        this.socket.emit("peerconnection/visitor/answer", {
            answer: this.answer,
        });
        this.peer.restartIce();
        console.log("[PeerConnection] Status:", {
            signal: this.peer.signalingState,
            connection: this.peer.connectionState
        })
    }

    onTrack(callback: any) {
        this.peer.ontrack = ev => {
            callback(ev.streams[0])
        }
    }

    constructor(
        rtcConfig: RTCConfiguration,
        socketConfig: SocketConfig | SocketType,
    ) {
        this.peer = new RTCPeerConnection(rtcConfig);

        this.socket = !!(socketConfig as SocketType)?.io
            ? (socketConfig as SocketType)
            : IO.connect(
                socketConfig as Partial<
                    IO.ManagerOptions & IO.SocketOptions
                    >,
            );

        Object.keys(this.routes).forEach((eventKey) =>
            this.socket.on(eventKey, this.routes[eventKey]),
        );

        this.peer.onicecandidate = (ev) =>
            ev?.candidate &&
            this.socket.emit("peerconnection/visitor/candidate", {
                candidate: ev?.candidate,
            });

        // this.peer.addEventListener("")

        this.peer.ondatachannel = (ev) =>
            console.log("[PeerConnection] Channel open.");

        this.peer.onicecandidateerror = ev => {
            console.log("[PeerConnection] Error from Ice Candidate.", ev);
        }

    }
}
class PeerConnectionOwner implements SocketDataOwner {
    private peer: RTCPeerConnection;

    private readonly RTCConfiguration: RTCConfiguration;
    private readonly Stream: MediaStream;

    readonly socket: SocketType;
    readonly routes: SocketDataOwner["routes"] = {
        "peerconnection/owner/join": (socketId: SocketType["id"]) => {
            this.join(socketId);
        },
        "peerconnection/owner/answer": (
            socketId: SocketType["id"],
            answer: RTCSessionDescriptionInit,
        ) => {
            this.setAnswer(socketId, answer);
        },
        "peerconnection/owner/candidate": (
            socketId: SocketType["id"],
            candidate: RTCIceCandidate,
        ) => {
            this.setCandidate(socketId, candidate);
        },
    };
    private children: SocketDataOwner["children_db"] = {};

    private async join(socketId: SocketType["id"]) {
        const peer = (this.children[socketId] = new RTCPeerConnection(
            this.RTCConfiguration,
        ));
        peer.createDataChannel("broadcast");
        this.Stream.getTracks().forEach((track) =>
            peer.addTrack(track, this.Stream),
        );
        const offer = await peer.createOffer({iceRestart: true});
        await peer.setLocalDescription(offer);
        this.socket.emit("peerconnection/owner/offer", socketId, offer);
        peer.restartIce();
        console.log("[PeerConnection] Status:", {
            signal: peer.signalingState,
            connection: peer.connectionState
        })
    }

    private setAnswer(
        socketId: SocketType["id"],
        answer: RTCSessionDescriptionInit,
    ) {
        this.children[socketId]
            .setRemoteDescription(answer)
            .then(candidate => console.log("[PeerConnection] Add answer"))
            .catch(candidate => console.log("[PeerConnection] Error answer", candidate));
    }

    private setCandidate(
        socketId: SocketType["id"],
        candidate: RTCIceCandidate,
    ) {
        this.children[socketId].addIceCandidate(
            new RTCIceCandidate(candidate),
        )
            .then(candidate => console.log("[PeerConnection] Add candidate"))
            .catch(candidate => console.log("[PeerConnection] Error candidate", candidate));
    }

    constructor(
        rtcConfig: RTCConfiguration,
        socketConfig: SocketConfig | SocketType,
        stream: MediaStream,
    ) {
        this.peer = new RTCPeerConnection(rtcConfig);

        this.socket = !!(socketConfig as SocketType)?.io
            ? (socketConfig as SocketType)
            : IO.connect(
                socketConfig as Partial<
                    IO.ManagerOptions & IO.SocketOptions
                    >,
            );

        Object.keys(this.routes).forEach((eventKey) =>
            this.socket.on(eventKey, this.routes[eventKey]),
        );

        this.RTCConfiguration = rtcConfig;
        this.Stream = stream;
    }
}

function init(
    config: RTCConfiguration,
    socketConfig: SocketConfig | SocketType,
): PeerConnectionVisitor;
function init(
    config: RTCConfiguration,
    socketConfig: SocketConfig | SocketType,
    owner: false,
    stream?: undefined,
): PeerConnectionVisitor;
function init(
    config: RTCConfiguration,
    socketConfig: SocketConfig | SocketType,
    owner: true,
    stream: MediaStream | any,
): PeerConnectionOwner;
function init(
    config: RTCConfiguration,
    socketConfig: SocketConfig | SocketType,
    owner?: boolean,
    stream?: MediaStream | any,
) {
    if (owner) return new PeerConnectionOwner(config, socketConfig, stream);
    else return new PeerConnectionVisitor(config, socketConfig);
}

export { PeerConnectionVisitor, PeerConnectionOwner };
export default init;
