import type { DataConnection, PeerJSOption } from "peerjs";
import type Peer from "peerjs";

import { MODULES } from "./engine_modules.js";
import { type ConstructorConcrete } from "./engine_types.js";

let peerOptions: PeerJSOption | undefined = undefined;

export function getPeerOptions() {
    return peerOptions;
}
export function setPeerOptions(opts: PeerJSOption) {
    peerOptions = opts;
}
export async function getPeerjsInstance(id?: string, opts?: PeerJSOption): Promise<Peer> {
    const peerjs = await MODULES.PEERJS.load();
    const PeerConstructor = peerjs.default;
    if (!opts) opts = {}
    opts = {
        ...peerOptions,
        ...opts,
    }
    if (id)
        return new PeerConstructor(id, opts);
    return new PeerConstructor(opts);
}



async function importPeer(): Promise<ConstructorConcrete<Peer>> {
    const pkg = await MODULES.PEERJS.load();
    if (pkg.default === undefined) return pkg as any;
    return pkg.default;
}

enum MessageType {
    ConnectionList = "connection-list"
}

declare class InternalPeerMessages {
    type: MessageType
}

export class PeerNetworking {
    get isHost(): boolean {
        return this._host !== undefined;
    }

    private _host?: PeerHost;

    private _client!: Peer;
    private _clientData?: DataConnection;

    constructor() {
        this.onEnable();
    }

    onEnable() {
        const hostId = "HOST-5980e65c-8438-453e-8b35-f13c736dcd81";
        this.trySetupHost(hostId);
    }

    private async trySetupHost(id: string) {
        const Peer = await importPeer();
        const host = new Peer(id);
        host.on("error", err => {
            console.error(err);
            this._host = undefined;
            this.trySetupClient(id);
        });
        host.on("open", _id => {
            this._host = new PeerHost(host);
        });
    }

    private async trySetupClient(hostId: string) {
        const Peer = await importPeer();
        this._client = new Peer();
        this._client.on("error", err => {
            console.error("Client error", err);
        });
        this._client.on("open", id => {
            console.log("client connected", id);
            this._clientData = this._client.connect(hostId, { metadata: { id: id } });
            this._clientData.on("open", () => {
                console.log("Connected to host");
            })
            this._clientData.on("data", data => {
                console.log("<<", data);
            });
        });
    }
}

abstract class AbstractPeerHandler {
    protected _peer: Peer;

    constructor(peer: Peer) {
        this._peer = peer;
    }

    abstract get isHost(): boolean;
    protected abstract onConnection(con: DataConnection);
}

//@ts-ignore
class PeerClient extends AbstractPeerHandler {
    get isHost() { return false; }

    protected onConnection(_con: DataConnection) {
    }
}

class PeerHost extends AbstractPeerHandler {

    get isHost() { return true; }

    private _connections: DataConnection[] = [];

    constructor(peer: Peer) {
        super(peer);
        console.log("I AM THE HOST");
        this._peer?.on("connection", this.onConnection.bind(this));
        this._peer.on("close", () => {
            this.broadcast("BYE");
        });
        setInterval(() => {
            this.broadcast("HELLO");
        }, 2000);
    }

    protected onConnection(con: DataConnection) {
        console.log("host connection", con);
        con.on("open", () => {
            this._connections.push(con);
            this.broadcastConnection(con);
        });
    }

    private broadcastConnection(_con: DataConnection) {
        const connectionIds: string[] = this._connections.map(c => c.metadata?.id).filter(id => id !== undefined);
        this.broadcast({ "type": MessageType.ConnectionList, "connections": connectionIds });
    }

    private broadcast(msg: any) {
        if (msg === undefined || msg === null) return;
        console.log(">>", msg);
        for (const cur in this._peer.connections) {
            const curCon = this._peer.connections[cur];
            if (!curCon) continue;
            if (Array.isArray(curCon)) {
                for (const entry of curCon) {
                    if (!entry) continue;
                    entry.send(msg);
                }
            }
            else {
                console.warn(curCon);
            }
        }
    }
}