<script setup>
import { computed, onMounted, onUnmounted, ref } from 'vue';
import {
    Room,
    RoomEvent,
    Track,
    ParticipantKind,
    LocalAudioTrack,
    LocalVideoTrack,
} from 'livekit-client';
import { getConfig, getLang } from '../utils/config';
import { fetchMeetingToken } from '../api/meetings';

const config = getConfig();
const meetingId = config.meetingId;
const meetingsUrl = config.routes?.meetings?.index || '/communication/meetings';
const translationLanguages = config.translationLanguages || { fa: 'فارسی', en: 'English' };
const defaultLanguage = config.defaultMeetingLanguage || 'fa';
const translationFeatureEnabled = config.translationEnabled !== false;

const room = new Room({ adaptiveStream: true, dynacast: true });
const tiles = new Map();
const attachedAudioEls = new Map();
const TRANSLATION_TRACK_PREFIX = 'tx:';

const showPrejoin = ref(true);
const showOverlay = ref(false);
const overlayMessage = ref('');
const overlayError = ref(false);
const overlayShowBack = ref(false);

const prejoinMicOn = ref(true);
const prejoinCamOn = ref(true);
const micEnabled = ref(false);
const camEnabled = ref(false);
const controlsEnabled = ref(false);
const prejoinHint = ref('');
const noVideoText = ref('');
const showNoVideo = ref(false);

const myLanguage = ref(defaultLanguage);
const translationEnabled = ref(translationFeatureEnabled);
const showCaptions = ref(true);
const captions = ref({});

const cameras = ref([]);
const microphones = ref([]);
const selectedCamera = ref('');
const selectedMic = ref('');
const videoGrid = ref(null);
const prejoinPreview = ref(null);

const languageOptions = computed(() =>
    Object.entries(translationLanguages).map(([code, label]) => ({ code, label }))
);

let prejoinStream = null;
let isLeaving = false;

function stopStream(stream) {
    stream?.getTracks().forEach((t) => t.stop());
}

function mediaErrorMessage(err) {
    const name = err?.name || '';
    if (name === 'NotAllowedError' || name === 'PermissionDeniedError') {
        return getLang('permission_denied');
    }
    if (name === 'NotReadableError' || name === 'AbortError') {
        return getLang('camera_in_use');
    }
    return err?.message || getLang('no_camera_preview');
}

function getSpeakerLang(participant) {
    return participant?.attributes?.lang || participant?.attributes?.language || defaultLanguage;
}

function isAgentParticipant(participant) {
    return participant?.kind === ParticipantKind.AGENT || participant?.identity === 'agent';
}

function parseTranslationTrackName(name) {
    if (!name?.startsWith(TRANSLATION_TRACK_PREFIX)) return null;
    const parts = name.slice(TRANSLATION_TRACK_PREFIX.length).split(':');
    if (parts.length < 2) return null;
    const targetLang = parts.pop();
    const sourceIdentity = parts.join(':');
    if (!sourceIdentity || !targetLang) return null;
    return { sourceIdentity, targetLang };
}

function findParticipantByIdentity(identity) {
    if (room.localParticipant.identity === identity) return room.localParticipant;
    return Array.from(room.remoteParticipants.values()).find((p) => p.identity === identity) || null;
}

function setRoomStatus(text, state = '') {
    const el = document.getElementById('room-status');
    if (!el) return;
    el.textContent = text;
    el.className = `room-status${state ? ` ${state}` : ''}`;
}

function setCaption(participantKey, text) {
    if (!text?.trim()) return;
    captions.value = { ...captions.value, [participantKey]: text };
}

async function loadDevices() {
    if (!navigator.mediaDevices?.enumerateDevices) return;
    try {
        const devices = await navigator.mediaDevices.enumerateDevices();
        cameras.value = devices.filter((d) => d.kind === 'videoinput');
        microphones.value = devices.filter((d) => d.kind === 'audioinput');
        if (!selectedCamera.value && cameras.value[0]) selectedCamera.value = cameras.value[0].deviceId;
        if (!selectedMic.value && microphones.value[0]) selectedMic.value = microphones.value[0].deviceId;
    } catch (e) {
        console.warn('enumerateDevices:', e);
    }
}

function buildPrejoinConstraints() {
    const constraints = { audio: false, video: false };
    if (prejoinMicOn.value) {
        constraints.audio = selectedMic.value ? { deviceId: { exact: selectedMic.value } } : true;
    }
    if (prejoinCamOn.value) {
        constraints.video = selectedCamera.value
            ? { deviceId: { exact: selectedCamera.value }, width: { ideal: 640 }, height: { ideal: 480 } }
            : { width: { ideal: 640 }, height: { ideal: 480 } };
    }
    return constraints;
}

async function startPrejoinPreview() {
    stopStream(prejoinStream);
    prejoinStream = null;
    prejoinHint.value = '';
    showNoVideo.value = false;

    if (!prejoinMicOn.value && !prejoinCamOn.value) {
        showNoVideo.value = true;
        noVideoText.value = getLang('media_permission_denied');
        return;
    }

    if (!navigator.mediaDevices?.getUserMedia) {
        prejoinHint.value = getLang('secure_context_required');
        return;
    }

    try {
        prejoinStream = await navigator.mediaDevices.getUserMedia(buildPrejoinConstraints());
        if (prejoinCamOn.value && prejoinStream.getVideoTracks().length && prejoinPreview.value) {
            prejoinPreview.value.srcObject = prejoinStream;
        } else {
            showNoVideo.value = true;
            noVideoText.value = getLang('no_camera_preview');
        }
        await loadDevices();
    } catch (e) {
        prejoinHint.value = mediaErrorMessage(e);
        showNoVideo.value = true;
        noVideoText.value = mediaErrorMessage(e);
    }
}

function getOrCreateTile(participant, label, isLocal) {
    const id = `${participant.sid}-main`;
    if (tiles.has(id)) return tiles.get(id);
    const tile = document.createElement('div');
    tile.className = `video-tile${isLocal ? ' local' : ''}`;
    tile.dataset.participantKey = participant.identity;
    tile.innerHTML = `
        <div class="tile-caption"></div>
        <div class="tile-label">${label}</div>
    `;
    videoGrid.value?.appendChild(tile);
    tiles.set(id, tile);
    return tile;
}

function updateTileCaption(participantKey, text) {
    const tile = Array.from(tiles.values()).find((el) => el.dataset.participantKey === participantKey);
    if (!tile) return;
    const captionEl = tile.querySelector('.tile-caption');
    if (!captionEl) return;
    if (showCaptions.value && text) {
        captionEl.textContent = text;
        captionEl.classList.add('visible');
    } else {
        captionEl.classList.remove('visible');
    }
}

function detachAudioForParticipant(participantKey) {
    const existing = attachedAudioEls.get(participantKey);
    if (existing) {
        existing.remove();
        attachedAudioEls.delete(participantKey);
    }
}

function attachAudio(track, participant, isTranslated = false) {
    const participantKey = participant.identity;
    const label = participant === room.localParticipant
        ? getLang('you')
        : (participant.name || participant.identity);
    getOrCreateTile(participant, label, participant === room.localParticipant);

    detachAudioForParticipant(participantKey);
    const media = track.attach();
    media.dataset.translated = isTranslated ? 'true' : 'false';
    document.body.appendChild(media);
    attachedAudioEls.set(participantKey, media);
}

function attachVideo(track, participant, isLocal) {
    const label = isLocal ? getLang('you') : (participant.name || participant.identity);
    const tile = getOrCreateTile(participant, label, isLocal);
    tile.querySelector('video')?.remove();
    const media = track.attach();
    tile.insertBefore(media, tile.firstChild);
}

function applyHumanSubscriptions(participant) {
    const theirLang = getSpeakerLang(participant);
    const hearNative = !translationEnabled.value || theirLang === myLanguage.value;

    participant.audioTrackPublications.forEach((pub) => {
        if (pub.source !== Track.Source.Microphone) return;
        if (pub.isSubscribed !== hearNative) {
            pub.setSubscribed(hearNative);
        }
    });
}

function applyAgentSubscriptions(agent, peerLangs) {
    agent.audioTrackPublications.forEach((pub) => {
        const parsed = parseTranslationTrackName(pub.trackName);
        if (!parsed) return;

        const matchesMe = parsed.targetLang === myLanguage.value;
        const speakerLang = peerLangs.get(parsed.sourceIdentity);
        const speakerNotMyLang = speakerLang && speakerLang !== myLanguage.value;
        const desired = translationEnabled.value && matchesMe && speakerNotMyLang;

        if (pub.isSubscribed !== desired) {
            pub.setSubscribed(desired);
        }
    });
}

function applyTranslationRouting() {
    if (!translationEnabled.value) return;

    const remotes = Array.from(room.remoteParticipants.values());
    const peerLangs = new Map();

    remotes.forEach((p) => {
        if (!isAgentParticipant(p)) {
            peerLangs.set(p.identity, getSpeakerLang(p));
        }
    });

    remotes.forEach((p) => {
        if (isAgentParticipant(p)) {
            applyAgentSubscriptions(p, peerLangs);
        } else {
            applyHumanSubscriptions(p);
        }
    });
}

function handleAudioTrack(track, publication, participant) {
    if (track.kind !== Track.Kind.Audio) return;

    if (isAgentParticipant(participant)) {
        const parsed = parseTranslationTrackName(publication.trackName);
        if (!parsed || parsed.targetLang !== myLanguage.value) return;
        const speaker = findParticipantByIdentity(parsed.sourceIdentity);
        if (!speaker) return;
        attachAudio(track, speaker, true);
        return;
    }

    if (publication.source !== Track.Source.Microphone) return;

    const theirLang = getSpeakerLang(participant);
    if (translationEnabled.value && theirLang !== myLanguage.value) {
        detachAudioForParticipant(participant.identity);
        return;
    }

    attachAudio(track, participant, false);
}

function setupRoomEvents() {
    room.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {
        if (track.kind === Track.Kind.Video) {
            attachVideo(track, participant, participant === room.localParticipant);
            return;
        }
        if (track.kind === Track.Kind.Audio) {
            handleAudioTrack(track, publication, participant);
        }
    });

    room.on(RoomEvent.TrackUnsubscribed, (track, publication, participant) => {
        track.detach().forEach((el) => el.remove());
        if (track.kind !== Track.Kind.Audio) return;

        if (isAgentParticipant(participant)) {
            const parsed = parseTranslationTrackName(publication.trackName);
            if (parsed) detachAudioForParticipant(parsed.sourceIdentity);
            return;
        }

        if (publication.source === Track.Source.Microphone) {
            detachAudioForParticipant(participant.identity);
        }
    });

    room.on(RoomEvent.LocalTrackPublished, (publication) => {
        if (!publication.track) return;
        if (publication.track.kind === Track.Kind.Video) {
            attachVideo(publication.track, room.localParticipant, true);
        }
    });

    room.on(RoomEvent.ParticipantConnected, (participant) => {
        if (!isAgentParticipant(participant)) {
            participant.trackPublications.forEach((publication) => {
                if (publication.track && publication.isSubscribed) {
                    if (publication.kind === Track.Kind.Video) {
                        attachVideo(publication.track, participant, false);
                    } else if (publication.kind === Track.Kind.Audio) {
                        handleAudioTrack(publication.track, publication, participant);
                    }
                }
            });
        }
        applyTranslationRouting();
    });

    room.on(RoomEvent.ParticipantDisconnected, (participant) => {
        detachAudioForParticipant(participant.identity);
        const nextCaptions = { ...captions.value };
        delete nextCaptions[participant.identity];
        captions.value = nextCaptions;
        tiles.forEach((tile, id) => {
            if (id.startsWith(participant.sid)) {
                tile.remove();
                tiles.delete(id);
            }
        });
        applyTranslationRouting();
    });

    const routingEvents = [
        RoomEvent.ParticipantAttributesChanged,
        RoomEvent.TrackPublished,
        RoomEvent.TrackUnpublished,
        RoomEvent.TrackMuted,
        RoomEvent.TrackUnmuted,
    ];
    routingEvents.forEach((event) => {
        room.on(event, () => applyTranslationRouting());
    });

    room.on(RoomEvent.ParticipantAttributesChanged, () => {
        room.remoteParticipants.forEach((participant) => {
            if (isAgentParticipant(participant)) return;
            participant.trackPublications.forEach((publication) => {
                if (publication.track && publication.kind === Track.Kind.Audio) {
                    handleAudioTrack(publication.track, publication, participant);
                }
            });
        });
    });

    room.registerTextStreamHandler('lk.translation', async (reader) => {
        try {
            const attrs = reader.info?.attributes || {};
            const targetLang = attrs.target_lang;
            const sourceIdentity = attrs.source_identity;
            if (!translationEnabled.value || targetLang !== myLanguage.value || !sourceIdentity) return;

            let text = '';
            for await (const chunk of reader) {
                text += chunk;
            }
            if (!text.trim()) return;

            const prev = captions.value[sourceIdentity] || '';
            const next = attrs.final === 'true' ? text.trim() : (prev + text);
            setCaption(sourceIdentity, next);
            updateTileCaption(sourceIdentity, next);
        } catch (e) {
            console.warn('translation caption stream:', e);
        }
    });

    room.on(RoomEvent.Disconnected, () => {
        if (isLeaving) {
            window.location.href = meetingsUrl;
            return;
        }
        showOverlay.value = true;
        overlayMessage.value = getLang('left_meeting');
        overlayShowBack.value = true;
        controlsEnabled.value = false;
    });
}

async function publishStreamTrack(kind, enabled) {
    if (!prejoinStream || !enabled) return false;
    const tracks = kind === 'video' ? prejoinStream.getVideoTracks() : prejoinStream.getAudioTracks();
    const mediaTrack = tracks[0];
    if (!mediaTrack) return false;
    const localTrack = kind === 'video' ? new LocalVideoTrack(mediaTrack) : new LocalAudioTrack(mediaTrack);
    const source = kind === 'video' ? Track.Source.Camera : Track.Source.Microphone;
    await room.localParticipant.publishTrack(localTrack, { source });
    return true;
}

async function joinMeeting() {
    showPrejoin.value = false;
    showOverlay.value = true;
    overlayError.value = false;
    overlayShowBack.value = false;
    overlayMessage.value = getLang('connecting_to_meeting');
    setupRoomEvents();

    try {
        const res = await fetchMeetingToken(meetingId, {
            language: myLanguage.value,
            translation_enabled: translationEnabled.value,
        });
        if (!res.success) {
            overlayError.value = true;
            overlayMessage.value = res.msg || getLang('connection_failed');
            overlayShowBack.value = true;
            return;
        }

        await room.connect(res.url, res.token, { autoSubscribe: true });
        await room.localParticipant.setAttributes({
            lang: myLanguage.value,
            language: myLanguage.value,
            translation: translationEnabled.value ? 'true' : 'false',
        });

        micEnabled.value = await publishStreamTrack('audio', prejoinMicOn.value);
        camEnabled.value = await publishStreamTrack('video', prejoinCamOn.value);

        stopStream(prejoinStream);
        prejoinStream = null;

        room.remoteParticipants.forEach((participant) => {
            if (isAgentParticipant(participant)) return;
            participant.trackPublications.forEach((publication) => {
                if (!publication.track || !publication.isSubscribed) return;
                if (publication.kind === Track.Kind.Video) {
                    attachVideo(publication.track, participant, false);
                } else if (publication.kind === Track.Kind.Audio) {
                    handleAudioTrack(publication.track, publication, participant);
                }
            });
        });

        applyTranslationRouting();

        showOverlay.value = false;
        controlsEnabled.value = true;
        const statusText = translationEnabled.value
            ? getLang('meeting_connected_translation')
            : getLang('meeting_connected');
        setRoomStatus(statusText, 'connected');
    } catch (err) {
        overlayError.value = true;
        overlayMessage.value = getLang('connection_failed') + (err.message ? ` (${err.message})` : '');
        overlayShowBack.value = true;
    }
}

async function toggleMic() {
    if (micEnabled.value) {
        await room.localParticipant.setMicrophoneEnabled(false);
        micEnabled.value = false;
    } else {
        await room.localParticipant.setMicrophoneEnabled(true);
        micEnabled.value = true;
    }
}

async function toggleCam() {
    if (camEnabled.value) {
        await room.localParticipant.setCameraEnabled(false);
        camEnabled.value = false;
    } else {
        await room.localParticipant.setCameraEnabled(true);
        camEnabled.value = true;
    }
}

function toggleCaptions() {
    showCaptions.value = !showCaptions.value;
    Object.entries(captions.value).forEach(([key, text]) => updateTileCaption(key, text));
}

async function leaveMeeting() {
    if (isLeaving) return;
    isLeaving = true;
    stopStream(prejoinStream);
    try {
        await room.disconnect(true);
    } catch (e) {
        console.warn(e);
    }
    window.location.href = meetingsUrl;
}

function goBack() {
    window.location.href = meetingsUrl;
}

onMounted(async () => {
    if (!window.isSecureContext) {
        prejoinHint.value = getLang('secure_context_required');
    }
    await loadDevices();
    await startPrejoinPreview();
    window.addEventListener('beforeunload', () => {
        stopStream(prejoinStream);
        room.disconnect();
    });
});

onUnmounted(() => {
    stopStream(prejoinStream);
    room.disconnect();
});
</script>

<template>
    <main class="room-main">
        <div id="video-grid" ref="videoGrid"></div>

        <div v-if="showPrejoin" class="room-overlay">
            <div class="prejoin-panel">
                <h2>{{ getLang('prejoin_title') }}</h2>
                <p>{{ getLang('prejoin_subtitle') }}</p>
                <div class="prejoin-preview-wrap">
                    <video v-show="!showNoVideo" ref="prejoinPreview" autoplay muted playsinline></video>
                    <div v-show="showNoVideo" class="prejoin-no-video">{{ noVideoText }}</div>
                </div>
                <div class="prejoin-devices">
                    <div>
                        <label>{{ getLang('select_camera') }}</label>
                        <select v-model="selectedCamera" @change="startPrejoinPreview">
                            <option v-for="(d, i) in cameras" :key="d.deviceId" :value="d.deviceId">{{ d.label || `Camera ${i + 1}` }}</option>
                        </select>
                    </div>
                    <div>
                        <label>{{ getLang('select_microphone') }}</label>
                        <select v-model="selectedMic" @change="startPrejoinPreview">
                            <option v-for="(d, i) in microphones" :key="d.deviceId" :value="d.deviceId">{{ d.label || `Mic ${i + 1}` }}</option>
                        </select>
                    </div>
                    <div v-if="translationFeatureEnabled">
                        <label>{{ getLang('my_language') }}</label>
                        <select v-model="myLanguage">
                            <option v-for="opt in languageOptions" :key="opt.code" :value="opt.code">{{ opt.label }}</option>
                        </select>
                    </div>
                    <div v-if="translationFeatureEnabled" class="prejoin-toggle-row">
                        <label class="toggle-label">
                            <input v-model="translationEnabled" type="checkbox" />
                            {{ getLang('enable_live_translation') }}
                        </label>
                        <span class="toggle-hint">{{ getLang('translation_hint') }}</span>
                    </div>
                </div>
                <div class="prejoin-actions">
                    <button type="button" class="toolbar-btn mic" :class="{ off: !prejoinMicOn }" @click="prejoinMicOn = !prejoinMicOn; startPrejoinPreview()">
                        <i :class="prejoinMicOn ? 'fa fa-microphone' : 'fa fa-microphone-slash'"></i>
                    </button>
                    <button type="button" class="toolbar-btn cam" :class="{ off: !prejoinCamOn }" @click="prejoinCamOn = !prejoinCamOn; startPrejoinPreview()">
                        <i :class="prejoinCamOn ? 'fa fa-video-camera' : 'fa fa-ban'"></i>
                    </button>
                    <button type="button" class="btn-join" @click="joinMeeting">{{ getLang('join_meeting_room') }}</button>
                </div>
                <p class="prejoin-hint">{{ prejoinHint }}</p>
            </div>
        </div>

        <div v-if="showOverlay" class="room-overlay" :class="{ error: overlayError }">
            <i :class="overlayError ? 'fa fa-exclamation-circle' : 'fa fa-spinner fa-spin'" class="main-icon"></i>
            <p>{{ overlayMessage }}</p>
            <button v-if="overlayShowBack" type="button" class="overlay-back" @click="goBack">
                {{ getLang('back_to_meetings') }}
            </button>
        </div>
    </main>

    <footer class="room-toolbar">
        <button type="button" class="toolbar-btn mic" :class="{ off: !micEnabled }" :disabled="!controlsEnabled" @click="toggleMic">
            <i :class="micEnabled ? 'fa fa-microphone' : 'fa fa-microphone-slash'"></i>
        </button>
        <button type="button" class="toolbar-btn cam" :class="{ off: !camEnabled }" :disabled="!controlsEnabled" @click="toggleCam">
            <i :class="camEnabled ? 'fa fa-video-camera' : 'fa fa-ban'"></i>
        </button>
        <button
            v-if="translationFeatureEnabled && translationEnabled"
            type="button"
            class="toolbar-btn captions"
            :class="{ off: !showCaptions }"
            :disabled="!controlsEnabled"
            :title="getLang('toggle_captions')"
            @click="toggleCaptions"
        >
            <i class="fa fa-font"></i>
        </button>
        <button type="button" class="toolbar-btn leave" :disabled="!controlsEnabled" @click="leaveMeeting">
            <i class="fa fa-phone"></i> {{ getLang('leave_meeting') }}
        </button>
    </footer>
</template>
