import { Device } from '@twilio/voice-sdk';

let device = null;
let activeCall = null;
let currentToken = null;
let callTimer = null;
let callSeconds = 0;

function emitBrowser(name, detail = {}) {
    window.dispatchEvent(new CustomEvent(name, { detail }));
    if (window.Livewire) {
        window.Livewire.dispatch(name, detail);
    }
}

async function fetchVoiceToken() {
    const response = await fetch('/twilio/sdk/token', {
        method: 'GET',
        headers: {
            'Accept': 'application/json',
            'X-Requested-With': 'XMLHttpRequest',
        },
        credentials: 'same-origin',
    });

    if (!response.ok) {
        throw new Error(`Failed to fetch Twilio token (${response.status})`);
    }

    const data = await response.json();

    if (!data?.token || typeof data.token !== 'string') {
        throw new Error('Token response did not include a valid token string.');
    }

    return data;
}

function startDurationTimer() {
    stopDurationTimer();
    callSeconds = 0;

    callTimer = window.setInterval(() => {
        callSeconds++;
        emitBrowser('twilio-call-duration-tick', { seconds: callSeconds });
    }, 1000);
}

function stopDurationTimer() {
    if (callTimer) {
        clearInterval(callTimer);
        callTimer = null;
    }
}

function bindCallEvents(call) {
    call.on('ringing', () => {
        emitBrowser('twilio-call-ringing');
    });

    call.on('accept', () => {
        activeCall = call;
        startDurationTimer();
        emitBrowser('twilio-call-accepted');
    });

    call.on('disconnect', () => {
        stopDurationTimer();
        activeCall = null;
        emitBrowser('twilio-call-disconnected');
    });

    call.on('cancel', () => {
        stopDurationTimer();
        activeCall = null;
        emitBrowser('twilio-call-cancelled');
    });

    call.on('reject', () => {
        stopDurationTimer();
        activeCall = null;
        emitBrowser('twilio-call-rejected');
    });

    call.on('error', (error) => {
        stopDurationTimer();
        activeCall = null;

        emitBrowser('twilio-call-error', {
            message: error?.message ?? 'Twilio call error.',
            code: error?.code ?? null,
        });
    });
}

export async function initTwilioVoice() {
    emitBrowser('twilio-device-registering');

    const tokenData = await fetchVoiceToken();
    currentToken = tokenData.token;

    device = new Device(currentToken, {
        logLevel: 1,
        codecPreferences: ['opus', 'pcmu'],
        closeProtection: true,
    });

    device.on('registered', () => {
        emitBrowser('twilio-device-registered');
    });

    device.on('unregistered', () => {
        emitBrowser('twilio-device-unregistered');
    });

    device.on('error', (error) => {
        emitBrowser('twilio-device-error', {
            message: error?.message ?? 'Twilio device error.',
            code: error?.code ?? null,
        });
    });

    await device.register();
}

export async function startBrowserCall(callId) {
    if (!device) {
        throw new Error('Twilio Device is not initialised.');
    }

    emitBrowser('twilio-call-outgoing-starting');

    const call = await device.connect({
        params: {
            call_id: String(callId),
        },
    });

    bindCallEvents(call);

    return call;
}

export function hangupBrowserCall() {
    stopDurationTimer();

    if (activeCall) {
        activeCall.disconnect();
        activeCall = null;
    } else if (device) {
        device.disconnectAll();
    }
}

export function setMute(muted) {
    if (!activeCall) {
        return;
    }

    activeCall.mute(!!muted);
}

export function bootstrapTwilioVoice() {
    window.addEventListener('DOMContentLoaded', async () => {
        try {
            await initTwilioVoice();
        } catch (error) {
            emitBrowser('twilio-device-error', {
                message: error?.message ?? 'Failed to initialise Twilio.',
            });
        }
    });

    window.addEventListener('twilio-start-browser-call', async (event) => {
        try {
            await startBrowserCall(event.detail.call_id);
        } catch (error) {
            emitBrowser('twilio-call-error', {
                message: error?.message ?? 'Failed to start browser call.',
            });
        }
    });

    window.addEventListener('twilio-hangup-browser-call', () => {
        hangupBrowserCall();
    });

    window.addEventListener('twilio-mute-browser-call', () => {
        setMute(true);
    });

    window.addEventListener('twilio-unmute-browser-call', () => {
        setMute(false);
    });

    window.addEventListener('beforeunload', () => {
        stopDurationTimer();

        if (device) {
            try {
                device.destroy();
            } catch (e) {
                console.warn('Failed to destroy Twilio device cleanly', e);
            }
        }
    });
}