import React, { useCallback, useState } from "react";
import { Renderer, RendererError } from "./Renderer";
import { ShaderError } from "./Shader";
import classNames from "classnames";

import { useAppSelector } from "../../hooks";
import { PlayState, musicPlayer } from "../musicplayer/musicPlayerSlice";

function Visualizer() {
    const playing = useAppSelector((state) => state.musicPlayer.playing);
    const rendererState = useState<Renderer | null>(null);
    let renderer = rendererState[0];
    const setRenderer = rendererState[1];
    const [renderError, setRenderError] = useState<JSX.Element | null>(null);

    const visualizer = useCallback(
        (visualizer: HTMLDivElement | null) => {
            // TODO(tlater): Clean up state management. This is all
            // but trivial; there's seemingly no good place to keep
            // these big api objects (WebGLRenderingcontext or
            // AudioContext).
            //
            // It's tricky, too, because obviously react expects to be
            // in control of the DOM, and be allowed to delete our
            // canvas and create a new one.
            //
            // For the moment, this works, but it's a definite hack.
            if (renderer) {
                return;
            }

            // Until we start playing music, there is nothing to render.
            if (playing !== PlayState.Playing) {
                return;
            }

            if (musicPlayer.audioAnalyser === undefined) {
                throw new Error("MusicPlayer analyser was not set up on time");
            }

            // If we're rendering an error message, we won't be
            // setting up the visualizer.
            //
            // Also, nonintuitively, renderError will be null here on
            // subsequent iterations, so we can't rely on it to
            // identify errors.
            if (visualizer === null) {
                return;
            }

            const canvas = visualizer.children[0];
            const overlay = visualizer.children[1];

            if (
                !(canvas instanceof HTMLCanvasElement) ||
                !(overlay instanceof HTMLSpanElement)
            ) {
                throw new Error(
                    "react did not create our visualizer div correctly"
                );
            }

            if (renderer === null) {
                renderer = new Renderer(
                    musicPlayer.audioAnalyser,
                    canvas,
                    overlay
                );
                setRenderer(renderer);
            }

            try {
                renderer.initializeScene();
            } catch (error) {
                // Log so we don't lose the stack trace
                console.log(error);

                if (error instanceof ShaderError) {
                    setRenderError(
                        <span>
                            Failed to compile shader; This is a bug, feel free
                            to contact me with this error message:
                            <pre>
                                <code className="has-text-danger">
                                    {error.message}
                                </code>
                            </pre>
                        </span>
                    );
                } else if (error instanceof RendererError) {
                    setRenderError(
                        <span>
                            This browser does not support WebGL 2, sadly. This
                            demo uses WebGL and specifically instanced drawing,
                            so unfortunately this means it can't run on your
                            browser/device.
                        </span>
                    );
                } else if (error instanceof Error) {
                    setRenderError(
                        <span>
                            Something went very wrong; apologies, either your
                            browser is not behaving or there's a serious bug.
                            You can contact me with this error message:
                            <pre>
                                <code className="has-text-danger">
                                    {error.message}
                                </code>
                            </pre>
                        </span>
                    );
                } else {
                    setRenderError(
                        <span>
                            Something went very wrong; apologies, either your
                            browser is not behaving or there's a serious bug.
                        </span>
                    );
                }
            }
        },
        [playing, renderer, musicPlayer.audioAnalyser]
    );

    if (renderError === null) {
        return (
            <div
                ref={visualizer}
                className={classNames({
                    "is-flex-grow-1": true,
                    "is-clipped": true,
                    "is-relative": true,
                    "is-flex": true,
                    "is-align-items-center": true,
                    "is-justify-content-center": true,
                })}
            >
                <canvas className="is-block is-absolute is-border-box"></canvas>
                <span className="is-bottom-left"></span>
            </div>
        );
    } else {
        return renderError;
    }
}

export default Visualizer;
