"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PreJoin = exports.usePreviewDevice = exports.usePreviewTracks = void 0;
const tslib_1 = require("tslib");
const jsx_runtime_1 = require("react/jsx-runtime");
const livekit_client_1 = require("livekit-client");
const React = tslib_1.__importStar(require("react"));
const components_core_1 = require("@livekit/components-core");
const components_core_2 = require("@livekit/components-core");
const components_react_1 = require("@livekit/components-react");
const react_core_1 = require("@selfcommunity/react-core");
const ParticipantTileAvatar_1 = tslib_1.__importDefault(require("./ParticipantTileAvatar"));
const react_1 = require("react");
const TrackToggle_1 = require("./TrackToggle");
const LiveStreamProvider_1 = require("./LiveStreamProvider");
/** @alpha */
function usePreviewTracks(options, onError) {
    const [tracks, setTracks] = React.useState();
    const trackLock = React.useMemo(() => new livekit_client_1.Mutex(), []);
    React.useEffect(() => {
        let needsCleanup = false;
        let localTracks = [];
        trackLock.lock().then((unlock) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                if (options.audio || options.video) {
                    localTracks = yield (0, livekit_client_1.createLocalTracks)(options);
                    if (needsCleanup) {
                        localTracks.forEach((tr) => tr.stop());
                    }
                    else {
                        setTracks(localTracks);
                    }
                }
            }
            catch (e) {
                if (onError && e instanceof Error) {
                    onError(e);
                }
                else {
                    components_core_1.log.error(e);
                }
            }
            finally {
                unlock();
            }
        }));
        return () => {
            needsCleanup = true;
            localTracks.forEach((track) => {
                track.stop();
            });
        };
    }, [JSON.stringify(options), onError, trackLock]);
    return tracks;
}
exports.usePreviewTracks = usePreviewTracks;
/** @public */
function usePreviewDevice(enabled, deviceId, kind) {
    const [deviceError, setDeviceError] = React.useState(null);
    const [isCreatingTrack, setIsCreatingTrack] = React.useState(false);
    const devices = (0, components_react_1.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) => tslib_1.__awaiter(this, void 0, void 0, function* () {
        try {
            const track = kind === 'videoinput'
                ? yield (0, livekit_client_1.createLocalVideoTrack)({
                    deviceId: deviceId,
                    resolution: livekit_client_1.VideoPresets.h720.resolution
                })
                : yield (0, livekit_client_1.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) => tslib_1.__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) {
            components_core_1.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) {
            components_core_1.log.debug(`muting ${kind} track`);
            localTrack.mute().then(() => components_core_1.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)) {
            components_core_1.log.debug(`switching ${kind} device from`, prevDeviceId.current, selectedDevice.deviceId);
            switchDevice(localTrack, selectedDevice.deviceId);
        }
        else {
            components_core_1.log.debug(`unmuting local ${kind} track`);
            localTrack.unmute();
        }
    }, [localTrack, selectedDevice, enabled, kind]);
    React.useEffect(() => {
        return () => {
            if (localTrack) {
                components_core_1.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
    };
}
exports.usePreviewDevice = usePreviewDevice;
/**
 * 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
 */
function PreJoin(_a) {
    var { defaults = {}, onValidate, onSubmit, onError, debug, joinLabel = 'Join Room', micLabel = 'Microphone', camLabel = 'Camera', userLabel = 'Username', persistUserChoices = true } = _a, htmlProps = tslib_1.__rest(_a, ["defaults", "onValidate", "onSubmit", "onError", "debug", "joinLabel", "micLabel", "camLabel", "userLabel", "persistUserChoices"]);
    const { liveStream } = (0, LiveStreamProvider_1.useLiveStream)();
    const scUserContext = (0, react_core_1.useSCUser)();
    const [userChoices, setUserChoices] = React.useState(components_core_2.defaultUserChoices);
    const canUseAudio = (0, react_1.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 = (0, react_1.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 } = (0, components_react_1.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 === livekit_client_1.Track.Kind.Video)[0], [tracks]);
    const facingMode = React.useMemo(() => {
        if (videoTrack) {
            const { facingMode } = (0, livekit_client_1.facingModeFromLocalTrack)(videoTrack);
            return facingMode;
        }
        else {
            return 'undefined';
        }
    }, [videoTrack]);
    const audioTrack = React.useMemo(() => tracks === null || tracks === void 0 ? void 0 : tracks.filter((track) => track.kind === livekit_client_1.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]);
    (0, react_1.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 {
            components_core_1.log.warn('Validation failed with: ', userChoices);
        }
    }
    return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ className: "lk-prejoin" }, htmlProps, { children: [(0, jsx_runtime_1.jsxs)("div", Object.assign({ className: "lk-video-container" }, { children: [videoTrack && (0, jsx_runtime_1.jsx)("video", { ref: videoEl, width: "1280", height: "720", "data-lk-facing-mode": facingMode }), (!videoTrack || !videoEnabled) && ((0, jsx_runtime_1.jsx)("div", Object.assign({ className: "lk-camera-off-note" }, { children: (0, jsx_runtime_1.jsx)(ParticipantTileAvatar_1.default, { user: scUserContext.user }) })))] })), (0, jsx_runtime_1.jsxs)("div", Object.assign({ className: "lk-button-group-container" }, { children: [(0, jsx_runtime_1.jsxs)("div", Object.assign({ className: "lk-button-group audio" }, { children: [(0, jsx_runtime_1.jsx)(TrackToggle_1.TrackToggle, Object.assign({ disabled: !canUseAudio, initialState: audioEnabled, source: livekit_client_1.Track.Source.Microphone, onChange: (enabled) => setAudioEnabled(enabled) }, { children: micLabel })), (0, jsx_runtime_1.jsx)("div", Object.assign({ className: "lk-button-group-menu" }, { children: (0, jsx_runtime_1.jsx)(components_react_1.MediaDeviceMenu, { initialSelection: audioDeviceId, kind: "audioinput", disabled: !audioTrack || !canUseAudio, tracks: { audioinput: audioTrack }, onActiveDeviceChange: (_, id) => setAudioDeviceId(id) }) }))] })), (0, jsx_runtime_1.jsxs)("div", Object.assign({ className: "lk-button-group video" }, { children: [(0, jsx_runtime_1.jsx)(TrackToggle_1.TrackToggle, Object.assign({ disabled: !canUseVideo, initialState: videoEnabled, source: livekit_client_1.Track.Source.Camera, onChange: (enabled) => setVideoEnabled(enabled) }, { children: camLabel })), (0, jsx_runtime_1.jsx)("div", Object.assign({ className: "lk-button-group-menu" }, { children: (0, jsx_runtime_1.jsx)(components_react_1.MediaDeviceMenu, { initialSelection: videoDeviceId, kind: "videoinput", disabled: !videoTrack || !canUseVideo, tracks: { videoinput: videoTrack }, onActiveDeviceChange: (_, id) => setVideoDeviceId(id) }) }))] }))] })), (0, jsx_runtime_1.jsx)("form", Object.assign({ className: "lk-username-container" }, { children: (0, jsx_runtime_1.jsx)("button", Object.assign({ className: "lk-button lk-join-button", type: "submit", onClick: handleSubmit, disabled: !isValid }, { children: joinLabel })) })), debug && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("strong", { children: "User Choices:" }), (0, jsx_runtime_1.jsxs)("ul", Object.assign({ className: "lk-list", style: { overflow: 'hidden', maxWidth: '15rem' } }, { children: [(0, jsx_runtime_1.jsxs)("li", { children: ["Username: ", `${userChoices.username}`] }), (0, jsx_runtime_1.jsxs)("li", { children: ["Video Enabled: ", `${userChoices.videoEnabled}`] }), (0, jsx_runtime_1.jsxs)("li", { children: ["Audio Enabled: ", `${userChoices.audioEnabled}`] }), (0, jsx_runtime_1.jsxs)("li", { children: ["Video Device: ", `${userChoices.videoDeviceId}`] }), (0, jsx_runtime_1.jsxs)("li", { children: ["Audio Device: ", `${userChoices.audioDeviceId}`] })] }))] }))] })));
}
exports.PreJoin = PreJoin;
