import * as React from "react";
import {useCallback, useContext, useEffect, useRef, useState} from "react";
import {ClientIconRenderer} from "tc-shared/ui/react-elements/Icons";
import {ClientIcon} from "svg-sprites/client-icons";
import {Registry} from "tc-shared/events";
import {
    ChannelVideoEvents, ChannelVideoInfo,
    ChannelVideoStreamState,
    kLocalVideoId, makeVideoAutoplay,
    VideoStreamState,
    VideoSubscribeInfo
} from "./Definitions";
import {Translatable} from "tc-shared/ui/react-elements/i18n";
import {LoadingDots} from "tc-shared/ui/react-elements/LoadingDots";
import ResizeObserver from "resize-observer-polyfill";
import {LogCategory, logTrace, logWarn} from "tc-shared/log";
import {spawnContextMenu} from "tc-shared/ui/ContextMenu";
import {VideoBroadcastType} from "tc-shared/connection/VideoConnection";
import {ErrorBoundary} from "tc-shared/ui/react-elements/ErrorBoundary";
import {joinClassList, useTr} from "tc-shared/ui/react-elements/Helper";
import {Spotlight, SpotlightDimensions, SpotlightDimensionsContext} from "./RendererSpotlight";
import * as _ from "lodash";
import {ClientTag} from "tc-shared/ui/tree/EntryTags";
import {tra} from "tc-shared/i18n/localize";


const SubscribeContext = React.createContext<VideoSubscribeInfo>(undefined);
const EventContext = React.createContext<Registry<ChannelVideoEvents>>(undefined);
const HandlerIdContext = React.createContext<string>(undefined);
export const VideoIdContext = React.createContext<string>(undefined);

export const RendererVideoEventContext = EventContext;

const cssStyle = require("./Renderer.scss");

const ExpendArrow = React.memo(() => {
    const events = useContext(EventContext);

    const [ expended, setExpended ] = useState(() => {
        events.fire("query_expended");
        return false;
    });

    events.reactUse("notify_expended", event => setExpended(event.expended), undefined, [ setExpended ]);

    return (
        <div className={cssStyle.expendArrow} onClick={() => events.fire("action_toggle_expended", { expended: !expended })}>
            <ClientIconRenderer icon={ClientIcon.DoubleArrow} className={cssStyle.icon} />
        </div>
    );
});

const VideoViewerCount = React.memo(() => {
    const videoId = useContext(VideoIdContext);
    const events = useContext(EventContext);
    if(videoId !== kLocalVideoId) {
        /* Currently one we can see our own video viewer */
        return null;
    }

    const [ viewer, setViewer ] = useState<{ camera: number | undefined, screen: number | undefined }>(() => {
        events.fire("query_viewer_count");
        return { screen: undefined, camera: undefined };
    });

    events.reactUse("notify_viewer_count", event => setViewer({ camera: event.camera, screen: event.screen }));

    let info = [];
    if(typeof viewer.camera === "number") {
        info.push(
            <div className={cssStyle.entry} key={"camera"} title={tra("{} Camera viewers", viewer.camera)}>
                <div className={cssStyle.value}>{viewer.camera}</div>
                <ClientIconRenderer icon={ClientIcon.VideoMuted} className={cssStyle.icon} />
            </div>
        );
    }

    if(typeof viewer.screen === "number") {
        info.push(
            <div className={cssStyle.entry} key={"screen"} title={tra("{} Screen viewers", viewer.screen)}>
                <div className={cssStyle.value}>{viewer.screen}</div>
                <ClientIconRenderer icon={ClientIcon.ShareScreen} className={cssStyle.icon} />
            </div>
        );
    }

    if(info.length === 0) {
        /* We're not streaming any video */
        return null;
    }

    return (
        <div
            className={cssStyle.videoViewerCount}
            onClick={() => events.fire("action_show_viewers")}
            onDoubleClick={event => event.preventDefault()}
        >
            {info}
        </div>
    )
});

const VideoClientInfo = React.memo((props: { videoId: string }) => {
    const events = useContext(EventContext);
    const handlerId = useContext(HandlerIdContext);

    const [ info, setInfo ] = useState<"loading" | ChannelVideoInfo>(() => {
        events.fire("query_video_info", { videoId: props.videoId });
        return "loading";
    });

    const [ statusIcon, setStatusIcon ] = useState<ClientIcon>(ClientIcon.PlayerOff);

    events.reactUse("notify_video_info", event => {
        if(event.videoId === props.videoId) {
            setInfo(event.info);
            setStatusIcon(event.info.statusIcon);
        }
    });

    events.reactUse("notify_video_info_status", event => {
        if(event.videoId === props.videoId) {
            setStatusIcon(event.statusIcon);
        }
    });

    let clientName;
    if(info === "loading") {
        clientName = (
            <div className={cssStyle.name} key={"loading"}>
                <Translatable>loading</Translatable> {props.videoId} <LoadingDots />
            </div>
        );
    } else {
        clientName = <ClientTag clientName={info.clientName} clientUniqueId={info.clientUniqueId} clientId={info.clientId} handlerId={handlerId} className={cssStyle.name} key={"loaded"} />;
    }

    return (
        <div className={joinClassList(cssStyle.info, props.videoId === kLocalVideoId && cssStyle.local)}>
            <ClientIconRenderer icon={statusIcon} className={cssStyle.icon} />
            {clientName}
        </div>
    );
});

const VideoSubscribeContextProvider = (props: { children?: React.ReactElement | React.ReactElement[] }) => {
    const events = useContext(EventContext);

    const [ subscribeInfo, setSubscribeInfo ] = useState<VideoSubscribeInfo>(() => {
        events.fire("query_subscribe_info");
        return {
            totalSubscriptions: 0,
            subscribedStreams: {
                screen: 0,
                camera: 0
            },
            subscribeLimits: {},
            maxSubscriptions: undefined
        };
    });
    events.reactUse("notify_subscribe_info", event => setSubscribeInfo(event.info));

    return (
        <SubscribeContext.Provider value={subscribeInfo}>
            {props.children}
        </SubscribeContext.Provider>
    );
}

const canSubscribe = (subscribeInfo: VideoSubscribeInfo, target: VideoBroadcastType) : boolean => {
    if(typeof subscribeInfo.maxSubscriptions === "number" && subscribeInfo.maxSubscriptions <= subscribeInfo.totalSubscriptions) {
        return false;
    }

    return typeof subscribeInfo.subscribeLimits[target] !== "number" || subscribeInfo.subscribeLimits[target] > subscribeInfo.subscribedStreams[target];
};

const VideoGeneralAvailableRenderer = (props: { videoId: string, haveScreen: boolean, haveCamera: boolean, className?: string }) => {
    const events = useContext(EventContext);

    const subscribeInfo = useContext(SubscribeContext);
    if((props.haveCamera && canSubscribe(subscribeInfo, "camera")) || (props.haveScreen && canSubscribe(subscribeInfo, "screen"))) {
        return (
            <div className={cssStyle.text + " " + props.className} key={"video-muted"}>
                <div className={cssStyle.videoAvailable}>
                    <Translatable>Video available</Translatable>
                    <div className={cssStyle.buttons}>
                        <div className={cssStyle.button2} onClick={() => events.fire("action_toggle_mute", { videoId: props.videoId, broadcastType: undefined, muted: false })}>
                            <Translatable>Watch</Translatable>
                        </div>
                    </div>
                </div>
            </div>
        );
    } else {
        return (
            <div className={cssStyle.text + " " + props.className} key={"limit-reached"}>
                <div className={cssStyle.videoAvailable}>
                    <Translatable>Stream subscribe limit reached</Translatable>
                    {/* TODO: Name the failed permission */}
                </div>
            </div>
        );
    }
};

const VideoStreamAvailableRenderer = (props: { videoId: string, mode: VideoBroadcastType , className?: string }) => {
    const events = useContext(EventContext);

    const subscribeInfo = useContext(SubscribeContext);
    if(canSubscribe(subscribeInfo, props.mode)) {
        return (
            <div className={cssStyle.text + " " + props.className} key={"video-muted"}>
                <div className={cssStyle.videoAvailable}>
                    <Translatable>Video available</Translatable>
                    <div className={cssStyle.buttons}>
                        <div className={cssStyle.button2} onClick={() => events.fire("action_toggle_mute", { videoId: props.videoId, broadcastType: props.mode, muted: false })}>
                            <Translatable>Watch</Translatable>
                        </div>
                        <div className={cssStyle.button2} key={"ignore"} onClick={() => events.fire("action_dismiss", { videoId: props.videoId, broadcastType: props.mode })}>
                            <Translatable>Ignore</Translatable>
                        </div>
                    </div>
                </div>
            </div>
        );
    } else {
        return (
            <div className={cssStyle.text + " " + props.className} key={"limit-reached"}>
                <div className={cssStyle.videoAvailable}>
                    <Translatable>Stream subscribe limit reached</Translatable>
                    {/* TODO: Name the failed permission */}
                </div>
            </div>
        );
    }
};

const MediaStreamVideoRenderer = React.memo((props: { stream: MediaStream | undefined, className: string, title: string }) => {
    const refVideo = useRef<HTMLVideoElement>();

    useEffect(() => {
        let cancelAutoplay;
        const video = refVideo.current;
        if(props.stream) {
            video.style.opacity = "1";
            video.srcObject = props.stream;
            video.muted = true;
            cancelAutoplay = makeVideoAutoplay(video);
        } else {
            video.style.opacity = "0";
        }

        return () => {
            const video = refVideo.current;
            if(video) {
                video.onpause = undefined;
                video.onended = undefined;
            }

            if(cancelAutoplay) {
                cancelAutoplay();
            }
        }
    }, [ props.stream ]);

    return (
        <video ref={refVideo} className={cssStyle.video + " " + props.className} title={props.title} />
    )
});

const VideoStreamPlayer = (props: { videoId: string, streamType: VideoBroadcastType, className?: string }) => {
    const events = useContext(EventContext);
    const [ state, setState ] = useState<VideoStreamState>(() => {
        events.fire("query_video_stream", { videoId: props.videoId, broadcastType: props.streamType });
        return { state: "disconnected", }
    });
    events.reactUse("notify_video_stream", event => {
        if(event.videoId === props.videoId && event.broadcastType === props.streamType) {
            setState(event.state);
        }
    });

    switch (state.state) {
        case "disconnected":
            return (
                <div className={cssStyle.text} key={"no-video-stream"}>
                    <div>
                        <Translatable>No video stream</Translatable>
                    </div>
                </div>
            );

        case "connecting":
            return (
                <div className={cssStyle.text} key={"info-initializing"}>
                    <div>
                        <Translatable>connecting</Translatable> <LoadingDots />
                    </div>
                </div>
            );

        case "connected":
            return (
                <MediaStreamVideoRenderer
                    stream={state.stream}
                    className={props.className}
                    title={props.streamType === "camera" ? tr("Camera") : tr("Screen")}
                    key={"connected"}
                />
            );

        case "failed":
            return (
                <div className={joinClassList(cssStyle.text, cssStyle.error)} key={"error"}>
                    <div><Translatable>Stream replay failed</Translatable></div>
                </div>
            );

        case "available":
            return (
                <div className={cssStyle.text} key={"no-video-stream"}>
                    <div><Translatable>Video available</Translatable></div>
                </div>
            );
    }
}

const VideoPlayer = React.memo((props: { videoId: string, cameraState: ChannelVideoStreamState, screenState: ChannelVideoStreamState }) => {
    const streamElements = [];
    const streamClasses = [ cssStyle.videoPrimary, cssStyle.videoSecondary ];

    if(props.cameraState === "none" && props.screenState === "none") {
        /* No video available. Will be handled bellow */
    } else if(props.cameraState !== "streaming" && props.screenState !== "streaming") {
        /* We're not streaming any video nor we don't have any video. Show general show video button. */
        streamElements.push(
            <VideoGeneralAvailableRenderer
                key={"video-available"}
                videoId={props.videoId}
                haveCamera={props.cameraState !== "none"}
                haveScreen={props.screenState !== "none"}
                className={streamClasses.pop_front()}
            />
        );
    } else {
        if(props.screenState === "available") {
            streamElements.push(
                <VideoStreamAvailableRenderer
                    key={"video-available-screen"}
                    videoId={props.videoId}
                    mode={"screen"}
                    className={streamClasses.pop_front()}
                />
            );
        } else if(props.screenState === "streaming") {
            streamElements.push(
                <VideoStreamPlayer key={"stream-screen"} videoId={props.videoId} streamType={"screen"} className={streamClasses.pop_front()} />
            );
        }


        if(props.cameraState === "available") {
            streamElements.push(
                <VideoStreamAvailableRenderer
                    key={"video-available-camera"}
                    videoId={props.videoId}
                    mode={"camera"}
                    className={streamClasses.pop_front()}
                />
            );
        } else if(props.cameraState === "streaming") {
            streamElements.push(
                <VideoStreamPlayer key={"stream-camera"} videoId={props.videoId} streamType={"camera"} className={streamClasses.pop_front()} />
            );
        }
    }

    if(streamElements.length === 0){
        return (
            <div className={cssStyle.text} key={"no-video-stream"}>
                <div>
                    {props.videoId === kLocalVideoId ?
                        <Translatable key={"own"}>You're not broadcasting video</Translatable> :
                        <Translatable key={"general"}>No Video</Translatable>
                    }
                </div>
            </div>
        );
    }

    return <>{streamElements}</>;
});

const VideoToggleButton = React.memo((props: { videoId: string, broadcastType: VideoBroadcastType, target: boolean }) => {
    const events = useContext(EventContext);

    let title;
    let icon: ClientIcon;
    if(props.broadcastType === "camera") {
        title = props.target ? useTr("Unmute screen video") : useTr("Mute screen video");
        icon = ClientIcon.ShareScreen;
    } else {
        title = props.target ? useTr("Unmute camera video") : useTr("Mute camera video");
        icon = ClientIcon.VideoMuted;
    }

    return (
        <div className={joinClassList(cssStyle.iconContainer, cssStyle.toggle, !props.target && cssStyle.disabled)}
             onClick={() => events.fire("action_toggle_mute", { videoId: props.videoId, broadcastType: props.broadcastType, muted: props.target })}
             title={title}
        >
            <ClientIconRenderer className={cssStyle.icon} icon={icon} />
        </div>
    )
});

const VideoControlButtons = React.memo((props: {
    videoId: string,
    cameraState: ChannelVideoStreamState,
    screenState: ChannelVideoStreamState,
    isSpotlight: boolean,
    fullscreenMode: "none" | "unavailable" | "set"
}) => {
    const events = useContext(EventContext);

    let buttons = [];
    if(props.videoId !== kLocalVideoId) {
        switch (props.screenState) {
            case "available":
            case "ignored":
                buttons.push(
                    <VideoToggleButton videoId={props.videoId} target={false} broadcastType={"screen"} key={"screen-disabled"} />
                );
                break;

            case "streaming":
                buttons.push(
                    <VideoToggleButton videoId={props.videoId} target={true} broadcastType={"screen"} key={"screen-enabled"} />
                );
                break;

            case "none":
            default:
                break;
        }

        switch (props.cameraState) {
            case "available":
            case "ignored":
                buttons.push(
                    <VideoToggleButton videoId={props.videoId} target={false} broadcastType={"camera"} key={"camera-disabled"} />
                );
                break;

            case "streaming":
                buttons.push(
                    <VideoToggleButton videoId={props.videoId} target={true} broadcastType={"camera"} key={"camera-enabled"} />
                );
                break;

            case "none":
            default:
                break;
        }
    }

    buttons.push(
        <div className={cssStyle.iconContainer + " " + (props.fullscreenMode === "unavailable" ? cssStyle.hidden : "")}
             key={"spotlight"}
             onClick={() => {
                 if(props.isSpotlight) {
                     events.fire("action_set_fullscreen", { videoId: props.fullscreenMode === "set" ? undefined : props.videoId });
                 } else {
                     events.fire("action_toggle_spotlight", { videoIds: [ props.videoId ], expend: true, enabled: true });
                     events.fire("action_focus_spotlight", { });
                 }
             }}
             title={props.isSpotlight ? tr("Toggle fullscreen") : tr("Toggle spotlight")}
        >
            <ClientIconRenderer className={cssStyle.icon} icon={ClientIcon.Fullscreen} />
        </div>
    );

    return (
        <div className={cssStyle.actionIcons}>
            {buttons}
        </div>
    );
});

export const VideoContainer = React.memo((props: { isSpotlight: boolean }) => {
    const videoId = useContext(VideoIdContext);

    const events = useContext(EventContext);
    const refContainer = useRef<HTMLDivElement>();
    const fullscreenCapable = "requestFullscreen" in HTMLElement.prototype;

    const [ isFullscreen, setFullscreen ] = useState(false);

    const [ cameraState, setCameraState ] = useState<ChannelVideoStreamState>("none");
    const [ screenState, setScreenState ] = useState<ChannelVideoStreamState>(() => {
        events.fire("query_video", { videoId: videoId });
        return "none";
    });

    events.reactUse("notify_video", event => {
        if(event.videoId === videoId) {
            setCameraState(event.cameraStream);
            setScreenState(event.screenStream);
        }
    });

    useEffect(() => {
        if(!isFullscreen) { return; }

        if(document.fullscreenElement !== refContainer.current) {
            setFullscreen(false);
            return;
        }

        const listener = () => {
            if(document.fullscreenElement !== refContainer.current) {
                setFullscreen(false);
            }
        };

        document.addEventListener("fullscreenchange", listener);
        return () => document.removeEventListener("fullscreenchange", listener);
    }, [ isFullscreen ]);

    events.reactUse("action_set_fullscreen", event => {
        if(event.videoId === videoId) {
            if(!refContainer.current) { return; }

            refContainer.current.requestFullscreen().then(() => {
                setFullscreen(true);
            }).catch(error => {
                logWarn(LogCategory.GENERAL, tr("Failed to request fullscreen: %o"), error);
            });
        } else {
            if(document.fullscreenElement === refContainer.current) {
                document.exitFullscreen().then(undefined);
            }

            setFullscreen(false);
        }
    });

    return (
        <div
            className={cssStyle.videoContainer + " " + cssStyle.outlined}
            onDoubleClick={() => {
                if(isFullscreen) {
                    events.fire("action_set_fullscreen", { videoId: undefined });
                } else if(props.isSpotlight) {
                    events.fire("action_set_fullscreen", { videoId: videoId });
                } else {
                    events.fire("action_toggle_spotlight", { videoIds: [ videoId ], expend: true, enabled: true });
                    events.fire("action_focus_spotlight", { });
                }
            }}
            onContextMenu={event => {
                const streamType = (event.target as HTMLElement).getAttribute("x-stream-type");

                event.preventDefault();
                spawnContextMenu({
                    pageY: event.pageY,
                    pageX: event.pageX
                }, [
                    {
                        type: "normal",
                        label: tr("Popout Video"),
                        icon: ClientIcon.Fullscreen,
                        click: () => {
                            events.fire("action_set_pip", { videoId: videoId, broadcastType: streamType as any });
                        },
                        visible: !!streamType && "requestPictureInPicture" in HTMLVideoElement.prototype
                    },
                    {
                        type: "normal",
                        label: isFullscreen ? tr("Release fullscreen") : tr("Show in fullscreen"),
                        icon: ClientIcon.Fullscreen,
                        click: () => {
                            events.fire("action_set_fullscreen", { videoId: isFullscreen ? undefined : videoId });
                        }
                    },
                    {
                        type: "normal",
                        label: props.isSpotlight ? tr("Release spotlight") : tr("Put client in spotlight"),
                        icon: ClientIcon.Fullscreen,
                        click: () => {
                            events.fire("action_toggle_spotlight", { videoIds: [ videoId ], expend: true, enabled: !props.isSpotlight });
                            events.fire("action_focus_spotlight", { });
                        }
                    }
                ]);
            }}
            ref={refContainer}
        >
            <VideoPlayer videoId={videoId} cameraState={cameraState} screenState={screenState} />
            <VideoClientInfo videoId={videoId} />
            <VideoViewerCount />
            <VideoControlButtons
                videoId={videoId}
                cameraState={cameraState}
                screenState={screenState}
                isSpotlight={props.isSpotlight}
                fullscreenMode={fullscreenCapable ? isFullscreen ? "set" : "none" : "unavailable"}
            />
        </div>
    );
});

const VideoBarArrow = React.memo((props: { direction: "left" | "right", shown: boolean, containerRef: React.RefObject<HTMLDivElement> }) => {
    const events = useContext(EventContext);

    return (
        <div className={cssStyle.arrow + " " + cssStyle[props.direction] + " " + (props.shown ? "" : cssStyle.hidden)} ref={props.containerRef}>
            <div className={cssStyle.iconContainer} onClick={() => events.fire("action_video_scroll", { direction: props.direction })}>
                <ClientIconRenderer icon={ClientIcon.SimpleArrow} className={cssStyle.icon} />
            </div>
        </div>
    );
});

const VideoBar = React.memo(() => {
    const events = useContext(EventContext);
    const refVideos = useRef<HTMLDivElement>();
    const refArrowRight = useRef<HTMLDivElement>();
    const refArrowLeft = useRef<HTMLDivElement>();

    const [ arrowLeftShown, setArrowLeftShown ] = useState(false);
    const [ arrowRightShown, setArrowRightShown ] = useState(false);

    const [ videos, setVideos ] = useState<string[]>(() => {
        events.fire("query_videos");
        return [];
    });
    events.reactUse("notify_videos", event => setVideos(event.videoIds));

    const updateScrollButtons = useCallback(() => {
        const container = refVideos.current;
        if(!container) { return; }

        const rightEndReached = container.scrollLeft + container.clientWidth + 1 >= container.scrollWidth;
        const leftEndReached = container.scrollLeft <= .9;
        setArrowLeftShown(!leftEndReached);
        setArrowRightShown(!rightEndReached);
    }, [ refVideos ]);

    events.reactUse("action_video_scroll", event => {
        const container = refVideos.current;
        const arrowLeft = refArrowLeft.current;
        const arrowRight = refArrowRight.current;
        if(!container || !arrowLeft || !arrowRight) {
            return;
        }

        const children = [...container.children] as HTMLElement[];
        if(event.direction === "left") {
            const currentCutOff = container.scrollLeft;
            const element = children.filter(element => element.offsetLeft >= currentCutOff)
                .sort((a, b) => a.offsetLeft - b.offsetLeft)[0];

            container.scrollLeft = (element.offsetLeft + element.clientWidth) - (container.clientWidth - arrowRight.clientWidth);
        } else {
            const currentCutOff = container.scrollLeft + container.clientWidth;
            const element = children.filter(element => element.offsetLeft <= currentCutOff)
                .sort((a, b) => a.offsetLeft - b.offsetLeft)
                .last();

            container.scrollLeft = element.offsetLeft - arrowLeft.clientWidth;
        }
        updateScrollButtons();
    }, undefined, [ updateScrollButtons ]);

    useEffect(() => {
        updateScrollButtons();
    }, [ videos ]);

    useEffect(() => {
        const animationRequest = { current: 0 };
        const observer = new ResizeObserver(() => {
            if(animationRequest.current) {
                return;
            }

            animationRequest.current = requestAnimationFrame(() => {
                animationRequest.current = 0;
                updateScrollButtons();
            })
        });
        observer.observe(refVideos.current);
        return () => observer.disconnect();
    }, [ refVideos ]);

    return (
        <div className={cssStyle.videoBar}>
            <div className={cssStyle.videos} ref={refVideos}>
                {videos.map(videoId => (
                    <ErrorBoundary key={videoId}>
                        <VideoIdContext.Provider value={videoId}>
                            <VideoContainer isSpotlight={false} />
                        </VideoIdContext.Provider>
                    </ErrorBoundary>
                ))}
            </div>
            <VideoBarArrow direction={"left"} containerRef={refArrowLeft} shown={arrowLeftShown} />
            <VideoBarArrow direction={"right"} containerRef={refArrowRight} shown={arrowRightShown} />
        </div>
    )
});


const PanelContainer = (props: { children }) => {
    const refSpotlightContainer = useRef<HTMLDivElement>();
    const [ spotlightDimensions, setSpotlightDimensions ] = useState<SpotlightDimensions>({ width: 1200, height: 900 });

    useEffect(() => {
        const resizeObserver = new ResizeObserver(entries => {
            const entry = entries.last();
            const newDimensions = { height: entry.contentRect.height, width: entry.contentRect.width };

            if(newDimensions.width === 0) {
                /* div most likely got removed or something idk... */
                return;
            }

            if(_.isEqual(newDimensions, spotlightDimensions)) {
                return;
            }

            setSpotlightDimensions(newDimensions);
            logTrace(LogCategory.VIDEO, tr("New spotlight dimensions: %o"), entry.contentRect);
        });

        resizeObserver.observe(refSpotlightContainer.current);
        return () => resizeObserver.disconnect();
    }, []);

    return (
        <SpotlightDimensionsContext.Provider value={spotlightDimensions}>
            <div className={cssStyle.panel}>
                {props.children}
            </div>
            <div className={cssStyle.heightProvider}>
                <div className={cssStyle.videoBar} />
                <div className={cssStyle.spotlight} ref={refSpotlightContainer} />
            </div>
        </SpotlightDimensionsContext.Provider>
    );
}

const VisibilityHandler = React.memo((props: {
    children
}) => {
    const events = useContext(EventContext);
    const [ streamingCount, setStreamingCount ] = useState<number>(() => {
        events.fire("query_videos");
        return 0;
    });
    const [ expanded, setExpanded ] = useState<boolean>(() => {
        events.fire("query_expended");
        return false;
    })

    events.reactUse("notify_videos", event => setStreamingCount(event.videoActiveCount));
    events.reactUse("notify_expended", event => setExpanded(event.expended));
    return (
        <div className={joinClassList(cssStyle.container, streamingCount === 0 && cssStyle.hidden, expanded && cssStyle.expended)}>
            {props.children}
        </div>
    )
});

export const ChannelVideoRenderer = React.memo((props: { handlerId: string, events: Registry<ChannelVideoEvents> }) => {
    return (
        <EventContext.Provider value={props.events}>
            <HandlerIdContext.Provider value={props.handlerId}>
                <VisibilityHandler>
                    <PanelContainer>
                        <VideoSubscribeContextProvider>
                            <VideoBar />
                            <ExpendArrow />
                            <ErrorBoundary>
                                <Spotlight />
                            </ErrorBoundary>
                        </VideoSubscribeContextProvider>
                    </PanelContainer>
                </VisibilityHandler>
            </HandlerIdContext.Provider>
        </EventContext.Provider>
    );
});