const DISPLAY_AUDIO_KEY = 'care_display_audio';

function localeToSpeechLang(locale) {
    if (locale === 'fr') return 'fr-FR';
    if (locale === 'tw') return 'ak-GH';

    return 'en-US';
}

function pickVoice(voices, locale, preference) {
    const list = voices.length ? voices : window.speechSynthesis?.getVoices() ?? [];
    if (! list.length) {
        return null;
    }

    const lang = localeToSpeechLang(locale).toLowerCase();
    const langMatches = list.filter((voice) => voice.lang?.toLowerCase().startsWith(lang.slice(0, 2)));
    const pool = langMatches.length ? langMatches : list;
    const wantFemale = preference !== 'male';

    const scored = pool.map((voice) => {
        const name = voice.name.toLowerCase();
        const female = /female|woman|zira|samantha|karen|victoria|fiona|moira|tessa|veena|lekha/.test(name);
        const male = /male|man|david|daniel|james|fred|thomas|lee|ralph/.test(name);

        let score = 0;
        if (wantFemale && female) score += 2;
        if (! wantFemale && male) score += 2;
        if (voice.default) score += 1;
        if (voice.localService) score += 1;

        return { voice, score };
    });

    scored.sort((a, b) => b.score - a.score);

    return scored[0]?.voice ?? pool[0] ?? null;
}


export function registerCareDisplay(Alpine) {
    Alpine.data('displayBoard', (config = {}) => ({
        payload: config.initial ?? null,
        announcedIds: {},
        servingSignatures: {},
        servingPrimed: false,
        pendingLocalAnnouncements: [],
        isAnnouncing: false,
        speechReady: false,
        voices: [],
        clock: '',
        dateLabel: '',
        pollError: null,
        dataUrl: typeof config === 'string' ? config : config.dataUrl,
        playedUrlTemplate: config.playedUrl ?? null,
        pollMs: config.pollMs ?? 1200,
        csrf: config.csrf ?? document.querySelector('meta[name="csrf-token"]')?.content ?? '',

        formatCount(value) {
            return value === null || value === undefined ? '—' : String(value);
        },

        formatWait(minutes) {
            return minutes === null || minutes === undefined ? '—' : String(minutes);
        },

        async poll() {
            try {
                const res = await fetch(this.dataUrl, { headers: { Accept: 'application/json' } });
                if (! res.ok) {
                    throw new Error(`Display poll failed (${res.status})`);
                }
                this.payload = await res.json();
                this.pollError = null;
                this.trackServingChanges(
                    this.payload?.now_serving ?? [],
                    this.payload?.announcements ?? [],
                );
                this.maybeAnnounce(this.payload?.announcements ?? []);
            } catch (e) {
                this.pollError = e.message || 'Could not refresh display data';
                console.error('Display poll failed', e);
            }
        },

        servingPointKey(item) {
            return item?.point_key
                || item?.destination
                || item?.counter
                || item?.queue_name
                || item?.ticket_number
                || 'unknown';
        },

        trackServingChanges(nowServing, serverAnnouncements = []) {
            const nextSignatures = {};
            const changed = [];

            for (const item of nowServing) {
                if (! item?.ticket_number) {
                    continue;
                }
                const key = this.servingPointKey(item);
                const signature = `${item.ticket_number}|${item.called_at || ''}`;
                nextSignatures[key] = signature;

                if (this.servingPrimed && this.servingSignatures[key] !== signature) {
                    changed.push(item);
                }
            }

            this.servingSignatures = nextSignatures;
            if (! this.servingPrimed) {
                this.servingPrimed = true;
                return;
            }

            for (const item of changed) {
                const ticket = String(item.ticket_number);
                const coveredByServer = (serverAnnouncements || []).some(
                    (announcement) => announcement?.message && String(announcement.message).includes(ticket),
                );
                if (coveredByServer) {
                    continue;
                }

                const message = item.announcement_text
                    || `Ticket ${item.ticket_number}, please proceed to ${item.destination || item.counter || 'the service point'}.`;
                const localId = `local:${this.servingPointKey(item)}:${item.ticket_number}:${item.called_at || Date.now()}`;
                if (this.announcedIds[localId]) {
                    continue;
                }
                this.pendingLocalAnnouncements.push({
                    id: localId,
                    message,
                    locale: 'en',
                    voice: 'female',
                    volume: 80,
                    repeat_count: 1,
                    local: true,
                });
            }
        },
        primeSpeech() {
            if (! window.speechSynthesis) {
                return;
            }

            const loadVoices = () => {
                this.voices = window.speechSynthesis.getVoices() || [];
            };

            loadVoices();
            window.speechSynthesis.addEventListener('voiceschanged', loadVoices);
        },

        unlockSpeech() {
            if (! window.speechSynthesis) {
                return;
            }

            window.speechSynthesis.resume?.();
            window.speechSynthesis.cancel();

            const utter = new SpeechSynthesisUtterance(' ');
            utter.volume = 0;
            utter.onend = () => this.markSpeechReady();
            utter.onerror = () => this.markSpeechReady();
            window.speechSynthesis.speak(utter);
        },

        markSpeechReady() {
            this.speechReady = true;

            try {
                sessionStorage.setItem(DISPLAY_AUDIO_KEY, '1');
            } catch (e) {
                // ignore storage failures
            }

            this.maybeAnnounce(this.payload?.announcements ?? []);
        },

        maybeAnnounce(announcements) {
            if (! window.speechSynthesis || ! this.speechReady || this.isAnnouncing) {
                return;
            }

            const queue = [
                ...(Array.isArray(announcements) ? announcements : []),
                ...this.pendingLocalAnnouncements,
            ];
            const next = queue.find((item) => item?.id && item?.message && ! this.announcedIds[item.id]);
            if (! next) {
                return;
            }

            this.isAnnouncing = true;
            this.announcedIds[next.id] = true;
            this.pendingLocalAnnouncements = this.pendingLocalAnnouncements.filter((item) => item.id !== next.id);
            this.speakAnnouncement(next);
        },

        speakAnnouncement(item) {
            const repeats = Math.max(1, Number(item.repeat_count) || 1);
            const volume = Math.min(1, Math.max(0, (Number(item.volume) || 80) / 100));
            let remaining = repeats;
            let watchdog = null;

            const release = () => {
                if (watchdog) {
                    clearTimeout(watchdog);
                    watchdog = null;
                }
                this.isAnnouncing = false;
            };

            const finish = () => {
                release();
                if (! item.local) {
                    this.ackPlayed(item.id);
                }
                this.maybeAnnounce([
                    ...(this.payload?.announcements ?? []),
                    ...this.pendingLocalAnnouncements,
                ]);
            };

            const fail = (reason) => {
                console.error('Announcement speech failed', reason);
                release();
                delete this.announcedIds[item.id];
            };

            const speakOnce = () => {
                window.speechSynthesis.resume?.();
                window.speechSynthesis.cancel();

                const utter = new SpeechSynthesisUtterance(item.message);
                utter.volume = volume;
                utter.lang = localeToSpeechLang(item.locale);
                const voice = pickVoice(this.voices, item.locale, item.voice);
                if (voice) {
                    utter.voice = voice;
                }

                const estimatedMs = Math.max(4000, (item.message?.length || 24) * 140);
                watchdog = setTimeout(() => {
                    if (! this.isAnnouncing) {
                        return;
                    }
                    window.speechSynthesis.cancel();
                    remaining -= 1;
                    if (remaining > 0) {
                        speakOnce();
                    } else {
                        finish();
                    }
                }, estimatedMs);

                utter.onend = () => {
                    if (watchdog) {
                        clearTimeout(watchdog);
                        watchdog = null;
                    }
                    remaining -= 1;
                    if (remaining > 0) {
                        speakOnce();
                    } else {
                        finish();
                    }
                };
                utter.onerror = (event) => fail(event);

                window.speechSynthesis.speak(utter);
            };

            speakOnce();
        },

        async ackPlayed(id) {
            if (! this.playedUrlTemplate) {
                return;
            }

            const url = this.playedUrlTemplate.replace('__ID__', String(id));

            try {
                const res = await fetch(url, {
                    method: 'POST',
                    headers: {
                        Accept: 'application/json',
                        'X-CSRF-TOKEN': this.csrf,
                    },
                });
                if (! res.ok) {
                    throw new Error(`Ack failed (${res.status})`);
                }
            } catch (e) {
                console.error('Announcement ack failed', e);
                delete this.announcedIds[id];
            }
        },

        init() {
            this.primeSpeech();
            this.updateClock();
            setInterval(() => this.updateClock(), 1000);

            try {
                if (sessionStorage.getItem(DISPLAY_AUDIO_KEY) === '1') {
                    this.unlockSpeech();
                }
            } catch (e) {
                // ignore storage failures
            }

            // Seed signatures from SSR payload without announcing historical tickets.
            this.trackServingChanges(this.payload?.now_serving ?? []);
            this.poll();
            setInterval(() => this.poll(), this.pollMs);
        },

        updateClock() {
            const now = new Date();
            this.clock = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
            this.dateLabel = now.toLocaleDateString([], {
                weekday: 'long',
                month: 'long',
                day: 'numeric',
            });
        },
    }));
}
