import { __awaiter, __rest } from "tslib";
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { createLocalAudioTrack, createLocalTracks, createLocalVideoTrack, facingModeFromLocalTrack, Track, VideoPresets, Mutex } from 'livekit-client';
import * as React from 'react';
import { log } from '@livekit/components-core';
import { defaultUserChoices } from '@livekit/components-core';
import { MediaDeviceMenu, useMediaDevices, usePersistentUserChoices } from '@livekit/components-react';
import { useSCUser } from '@selfcommunity/react-core';
import ParticipantTileAvatar from './ParticipantTileAvatar';
import { useEffect, useMemo } from 'react';
import { TrackToggle } from './TrackToggle';
import { useLiveStream } from './LiveStreamProvider';
/** @alpha */
export function usePreviewTracks(options, onError) {
    const [tracks, setTracks] = React.useState();
    const trackLock = React.useMemo(() => new Mutex(), []);
    React.useEffect(() => {
        let needsCleanup = false;
        let localTracks = [];
        trackLock.lock().then((unlock) => __awaiter(this, void 0, void 0, function* () {
            try {
                if (options.audio || options.video) {
                    localTracks = yield createLocalTracks(options);
                    if (needsCleanup) {
                        localTracks.forEach((tr) => tr.stop());
                    }
                    else {
                        setTracks(localTracks);
                    }
                }
            }
            catch (e) {
                if (onError && e instanceof Error) {
                    onError(e);
                }
                else {
                    log.error(e);
                }
            }
            finally {
                unlock();
            }
        }));
        return () => {
            needsCleanup = true;
            localTracks.forEach((track) => {
                track.stop();
            });
        };
    }, [JSON.stringify(options), onError, trackLock]);
    return tracks;
}
/** @public */
export function usePreviewDevice(enabled, deviceId, kind) {
    const [deviceError, setDeviceError] = React.useState(null);
    const [isCreatingTrack, setIsCreatingTrack] = React.useState(false);
    const devices = useMediaDevices({ kind });
    const [selectedDevice, setSelectedDevice] = React.useState(undefined);
    const [localTrack, setLocalTrack] = React.useState();
    const [localDeviceId, setLocalDeviceId] = React.useState(deviceId);
    React.useEffect(() => {
        setLocalDeviceId(deviceId);
    }, [deviceId]);
    const createTrack = (deviceId, kind) => __awaiter(this, void 0, void 0, function* () {
        try {
            const track = kind === 'videoinput'
                ? yield createLocalVideoTrack({
                    deviceId: deviceId,
                    resolution: VideoPresets.h720.resolution
                })
                : yield createLocalAudioTrack({ deviceId });
            const newDeviceId = yield track.getDeviceId();
            if (newDeviceId && deviceId !== newDeviceId) {
                prevDeviceId.current = newDeviceId;
                setLocalDeviceId(newDeviceId);
            }
            setLocalTrack(track);
        }
        catch (e) {
            if (e instanceof Error) {
                setDeviceError(e);
            }
        }
    });
    const switchDevice = (track, id) => __awaiter(this, void 0, void 0, function* () {
        yield track.setDeviceId(id);
        prevDeviceId.current = id;
    });
    const prevDeviceId = React.useRef(localDeviceId);
    React.useEffect(() => {
        if (enabled && !localTrack && !deviceError && !isCreatingTrack) {
            log.debug('creating track', kind);
            setIsCreatingTrack(true);
            createTrack(localDeviceId, kind).finally(() => {
                setIsCreatingTrack(false);
            });
        }
    }, [enabled, localTrack, deviceError, isCreatingTrack]);
    // switch camera device
    React.useEffect(() => {
        if (!localTrack) {
            return;
        }
        if (!enabled) {
            log.debug(`muting ${kind} track`);
            localTrack.mute().then(() => log.debug(localTrack.mediaStreamTrack));
        }
        else if ((selectedDevice === null || selectedDevice === void 0 ? void 0 : selectedDevice.deviceId) && prevDeviceId.current !== (selectedDevice === null || selectedDevice === void 0 ? void 0 : selectedDevice.deviceId)) {
            log.debug(`switching ${kind} device from`, prevDeviceId.current, selectedDevice.deviceId);
            switchDevice(localTrack, selectedDevice.deviceId);
        }
        else {
            log.debug(`unmuting local ${kind} track`);
            localTrack.unmute();
        }
    }, [localTrack, selectedDevice, enabled, kind]);
    React.useEffect(() => {
        return () => {
            if (localTrack) {
                log.debug(`stopping local ${kind} track`);
                localTrack.stop();
                localTrack.mute();
            }
        };
    }, []);
    React.useEffect(() => {
        setSelectedDevice(devices === null || devices === void 0 ? void 0 : devices.find((dev) => dev.deviceId === localDeviceId));
    }, [localDeviceId, devices]);
    return {
        selectedDevice,
        localTrack,
        deviceError
    };
}
/**
 * The `PreJoin` prefab component is normally presented to the user before he enters a room.
 * This component allows the user to check and select the preferred media device (camera und microphone).
 * On submit the user decisions are returned, which can then be passed on to the `LiveKitRoom` so that the user enters the room with the correct media devices.
 *
 * @remarks
 * This component is independent of the `LiveKitRoom` component and should not be nested within it.
 * Because it only accesses the local media tracks this component is self-contained and works without connection to the LiveKit server.
 *
 * @example
 * ```tsx
 * <PreJoin />
 * ```
 * @public
 */
export function PreJoin(_a) {
    var { defaults = {}, onValidate, onSubmit, onError, debug, joinLabel = 'Join Room', micLabel = 'Microphone', camLabel = 'Camera', userLabel = 'Username', persistUserChoices = true } = _a, htmlProps = __rest(_a, ["defaults", "onValidate", "onSubmit", "onError", "debug", "joinLabel", "micLabel", "camLabel", "userLabel", "persistUserChoices"]);
    const { liveStream } = useLiveStream();
    const scUserContext = useSCUser();
    const [userChoices, setUserChoices] = React.useState(defaultUserChoices);
    const canUseAudio = useMemo(() => { var _a; return scUserContext.user && liveStream && (liveStream.host.id === scUserContext.user.id || (liveStream && !((_a = liveStream === null || liveStream === void 0 ? void 0 : liveStream.settings) === null || _a === void 0 ? void 0 : _a.muteParticipants))); }, [scUserContext, liveStream]);
    const canUseVideo = useMemo(() => { var _a; return scUserContext.user && liveStream && (liveStream.host.id === scUserContext.user.id || (liveStream && !((_a = liveStream === null || liveStream === void 0 ? void 0 : liveStream.settings) === null || _a === void 0 ? void 0 : _a.disableVideo))); }, [scUserContext, liveStream]);
    // TODO: Remove and pipe `defaults` object directly into `usePersistentUserChoices` once we fully switch from type `LocalUserChoices` to `UserChoices`.
    const partialDefaults = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (defaults.audioDeviceId !== undefined && { audioDeviceId: defaults.audioDeviceId })), (defaults.videoDeviceId !== undefined && { videoDeviceId: defaults.videoDeviceId })), (defaults.audioEnabled !== undefined && { audioEnabled: defaults.audioEnabled })), (defaults.videoEnabled !== undefined && { videoEnabled: defaults.videoEnabled })), (defaults.username !== undefined && { username: defaults.username }));
    const { userChoices: initialUserChoices, saveAudioInputDeviceId, saveAudioInputEnabled, saveVideoInputDeviceId, saveVideoInputEnabled, saveUsername } = usePersistentUserChoices({
        defaults: partialDefaults,
        preventSave: !persistUserChoices,
        preventLoad: !persistUserChoices
    });
    // Initialize device settings
    const [audioEnabled, setAudioEnabled] = React.useState(initialUserChoices.audioEnabled && canUseAudio);
    const [videoEnabled, setVideoEnabled] = React.useState(initialUserChoices.videoEnabled && canUseVideo);
    const [audioDeviceId, setAudioDeviceId] = React.useState(initialUserChoices.audioDeviceId);
    const [videoDeviceId, setVideoDeviceId] = React.useState(initialUserChoices.videoDeviceId);
    const [username, setUsername] = React.useState(initialUserChoices.username);
    // Save user choices to persistent storage.
    React.useEffect(() => {
        saveAudioInputEnabled(audioEnabled && canUseAudio);
    }, [audioEnabled, saveAudioInputEnabled, canUseAudio]);
    React.useEffect(() => {
        saveVideoInputEnabled(videoEnabled && canUseVideo);
    }, [videoEnabled, saveVideoInputEnabled, canUseVideo]);
    React.useEffect(() => {
        saveAudioInputDeviceId(audioDeviceId);
    }, [audioDeviceId, saveAudioInputDeviceId]);
    React.useEffect(() => {
        saveVideoInputDeviceId(videoDeviceId);
    }, [videoDeviceId, saveVideoInputDeviceId]);
    React.useEffect(() => {
        if (scUserContext.user) {
            saveUsername(scUserContext.user.username);
        }
    }, [username, saveUsername, scUserContext.user]);
    const tracks = usePreviewTracks({
        audio: audioEnabled ? { deviceId: initialUserChoices.audioDeviceId } : false,
        video: videoEnabled ? { deviceId: initialUserChoices.videoDeviceId } : false
    }, onError);
    const videoEl = React.useRef(null);
    const videoTrack = React.useMemo(() => tracks === null || tracks === void 0 ? void 0 : tracks.filter((track) => track.kind === Track.Kind.Video)[0], [tracks]);
    const facingMode = React.useMemo(() => {
        if (videoTrack) {
            const { facingMode } = facingModeFromLocalTrack(videoTrack);
            return facingMode;
        }
        else {
            return 'undefined';
        }
    }, [videoTrack]);
    const audioTrack = React.useMemo(() => tracks === null || tracks === void 0 ? void 0 : tracks.filter((track) => track.kind === Track.Kind.Audio)[0], [tracks]);
    React.useEffect(() => {
        if (videoEl.current && videoTrack) {
            videoTrack.unmute();
            videoTrack.attach(videoEl.current);
        }
        return () => {
            videoTrack === null || videoTrack === void 0 ? void 0 : videoTrack.detach();
        };
    }, [videoTrack]);
    const [isValid, setIsValid] = React.useState();
    const handleValidation = React.useCallback((values) => {
        if (typeof onValidate === 'function') {
            return onValidate(values);
        }
        else {
            return values.username !== '';
        }
    }, [onValidate]);
    useEffect(() => {
        const newUserChoices = {
            username,
            videoEnabled,
            videoDeviceId,
            audioEnabled,
            audioDeviceId
        };
        setUserChoices(newUserChoices);
        setIsValid(handleValidation(newUserChoices));
    }, [username, scUserContext.user, videoEnabled, handleValidation, audioEnabled, audioDeviceId, videoDeviceId]);
    function handleSubmit(event) {
        event.preventDefault();
        if (handleValidation(userChoices)) {
            if (typeof onSubmit === 'function') {
                onSubmit(userChoices);
            }
        }
        else {
            log.warn('Validation failed with: ', userChoices);
        }
    }
    return (_jsxs("div", Object.assign({ className: "lk-prejoin" }, htmlProps, { children: [_jsxs("div", Object.assign({ className: "lk-video-container" }, { children: [videoTrack && _jsx("video", { ref: videoEl, width: "1280", height: "720", "data-lk-facing-mode": facingMode }), (!videoTrack || !videoEnabled) && (_jsx("div", Object.assign({ className: "lk-camera-off-note" }, { children: _jsx(ParticipantTileAvatar, { user: scUserContext.user }) })))] })), _jsxs("div", Object.assign({ className: "lk-button-group-container" }, { children: [_jsxs("div", Object.assign({ className: "lk-button-group audio" }, { children: [_jsx(TrackToggle, Object.assign({ disabled: !canUseAudio, initialState: audioEnabled, source: Track.Source.Microphone, onChange: (enabled) => setAudioEnabled(enabled) }, { children: micLabel })), _jsx("div", Object.assign({ className: "lk-button-group-menu" }, { children: _jsx(MediaDeviceMenu, { initialSelection: audioDeviceId, kind: "audioinput", disabled: !audioTrack || !canUseAudio, tracks: { audioinput: audioTrack }, onActiveDeviceChange: (_, id) => setAudioDeviceId(id) }) }))] })), _jsxs("div", Object.assign({ className: "lk-button-group video" }, { children: [_jsx(TrackToggle, Object.assign({ disabled: !canUseVideo, initialState: videoEnabled, source: Track.Source.Camera, onChange: (enabled) => setVideoEnabled(enabled) }, { children: camLabel })), _jsx("div", Object.assign({ className: "lk-button-group-menu" }, { children: _jsx(MediaDeviceMenu, { initialSelection: videoDeviceId, kind: "videoinput", disabled: !videoTrack || !canUseVideo, tracks: { videoinput: videoTrack }, onActiveDeviceChange: (_, id) => setVideoDeviceId(id) }) }))] }))] })), _jsx("form", Object.assign({ className: "lk-username-container" }, { children: _jsx("button", Object.assign({ className: "lk-button lk-join-button", type: "submit", onClick: handleSubmit, disabled: !isValid }, { children: joinLabel })) })), debug && (_jsxs(_Fragment, { children: [_jsx("strong", { children: "User Choices:" }), _jsxs("ul", Object.assign({ className: "lk-list", style: { overflow: 'hidden', maxWidth: '15rem' } }, { children: [_jsxs("li", { children: ["Username: ", `${userChoices.username}`] }), _jsxs("li", { children: ["Video Enabled: ", `${userChoices.videoEnabled}`] }), _jsxs("li", { children: ["Audio Enabled: ", `${userChoices.audioEnabled}`] }), _jsxs("li", { children: ["Video Device: ", `${userChoices.videoDeviceId}`] }), _jsxs("li", { children: ["Audio Device: ", `${userChoices.audioDeviceId}`] })] }))] }))] })));
}
