import {React, DiscordModules, Settings} from "modules";

import Checkbox from "./checkbox";

const {useState, useCallback, useEffect, forwardRef, useMemo, useImperativeHandle} = React;
const ThemeStore = DiscordModules.ThemeStore;


const languages = ["abap", "abc", "actionscript", "ada", "apache_conf", "asciidoc", "assembly_x86", "autohotkey", "batchfile", "bro", "c_cpp", "c9search", "cirru", "clojure", "cobol", "coffee", "coldfusion", "csharp", "csound_document", "csound_orchestra", "csound_score", "css", "curly", "d", "dart", "diff", "dockerfile", "dot", "drools", "dummy", "dummysyntax", "eiffel", "ejs", "elixir", "elm", "erlang", "forth", "fortran", "ftl", "gcode", "gherkin", "gitignore", "glsl", "gobstones", "golang", "graphqlschema", "groovy", "haml", "handlebars", "haskell", "haskell_cabal", "haxe", "hjson", "html", "html_elixir", "html_ruby", "ini", "io", "jack", "jade", "java", "javascript", "json", "jsoniq", "jsp", "jssm", "jsx", "julia", "kotlin", "latex", "less", "liquid", "lisp", "livescript", "logiql", "lsl", "lua", "luapage", "lucene", "makefile", "markdown", "mask", "matlab", "maze", "mel", "mushcode", "mysql", "nix", "nsis", "objectivec", "ocaml", "pascal", "perl", "pgsql", "php", "pig", "powershell", "praat", "prolog", "properties", "protobuf", "python", "r", "razor", "rdoc", "red", "rhtml", "rst", "ruby", "rust", "sass", "scad", "scala", "scheme", "scss", "sh", "sjs", "smarty", "snippets", "soy_template", "space", "sql", "sqlserver", "stylus", "svg", "swift", "tcl", "tex", "text", "textile", "toml", "tsx", "twig", "typescript", "vala", "vbscript", "velocity", "verilog", "vhdl", "wollok", "xml", "xquery", "yaml", "django"];

function makeButton(button, value) {
    return <DiscordModules.Tooltip color="primary" position="top" text={button.tooltip}>
                {props => {
                    return <button {...props} className="btn btn-primary" onClick={(event) => {button.onClick(event, value?.());}}>{button.label}</button>;
                }}
            </DiscordModules.Tooltip>;
}

function makeCheckbox(checkbox) {
    return <Checkbox text={checkbox.label} onChange={checkbox.onChange} checked={checkbox.checked} />;
}

function buildControl(value, control) {
    if (control.type == "checkbox") return makeCheckbox(control);
    return makeButton(control, value);
}

export default forwardRef(function CodeEditor({value, language: requestedLang = "css", id = "bd-editor", controls = [], onChange: notifyParent}, ref) {
    const language = useMemo(() => {
        const requested = requestedLang.toLowerCase().replace(/ /g, "_");
        if (!languages.includes(requested)) return "css";
        return requested;
    }, [requestedLang]);

    const [theme, setTheme] = useState(() => ThemeStore?.theme === "light" ? "vs" : "vs-dark");
    const [editor, setEditor] = useState(null);
    const [, setBindings] = useState([]);

    const onThemeChange = useCallback(() => {
        const newTheme = ThemeStore?.theme === "light" ? "vs" : "vs-dark";
        if (newTheme === theme) return;
        if (window.monaco?.editor) window.monaco.editor.setTheme(newTheme);
        setTheme(newTheme);
    }, [theme]);

    const onChange = useCallback(() => {
        notifyParent?.(editor?.getValue());
    }, [editor, notifyParent]);
    const resize = useCallback(() => editor.layout(), [editor]);
    const showSettings = useCallback(() => editor.keyBinding.$defaultHandler.commands.showSettingsMenu.exec(editor), [editor]);

    useImperativeHandle(ref, () => {
        return {
            resize,
            showSettings,
            get value() {return editor.getValue();},
            set value(newValue) {editor.setValue(newValue);}
        };
    }, [editor, resize, showSettings]);

    useEffect(() => {
        setBindings(bins => [...bins, editor?.onDidChangeModelContent(onChange)]);
        return () => {
            setBindings(bins => {
                for (const binding of bins) binding?.dispose();
                return [];
            });
        };
    }, [editor, onChange]);

    useEffect(() => {
        let toDispose = null;
        if (window.monaco?.editor) {
            const monacoEditor = window.monaco.editor.create(document.getElementById(id), {
                value: value,
                language: language,
                theme: theme,
                fontSize: Settings.get("settings", "editor", "fontSize"),
                lineNumbers: Settings.get("settings", "editor", "lineNumbers"),
                minimap: {enabled: Settings.get("settings", "editor", "minimap")},
                hover: {enabled: Settings.get("settings", "editor", "hover")},
                quickSuggestions: {
                    other: Settings.get("settings", "editor", "quickSuggestions"),
                    comments: Settings.get("settings", "editor", "quickSuggestions"),
                    strings: Settings.get("settings", "editor", "quickSuggestions")
                },
                renderWhitespace: Settings.get("settings", "editor", "renderWhitespace")
            });

            toDispose = monacoEditor;
            setEditor(monacoEditor);
        }
        else {

            const textarea = document.createElement("textarea");
            textarea.className = "bd-fallback-editor";
            textarea.value = value;

            setEditor({
                dispose: () => textarea.remove(),
                getValue: () => textarea.value,
                setValue: (val) => textarea.value = val,
                layout: () => {},
                onDidChangeModelContent: (cb) => {
                    textarea.onchange = cb;
                    textarea.oninput = cb;
                }
            });

            document.getElementById(id).appendChild(textarea);
        }

        return () => {
            toDispose?.dispose?.();
        };
    }, [id, language, theme, value]);

    useEffect(() => {
        ThemeStore?.addChangeListener?.(onThemeChange);
        window.addEventListener("resize", resize);

        return () => {
            window.removeEventListener("resize", resize);
            ThemeStore?.removeChangeListener?.(onThemeChange);
        };
    }, [onThemeChange, resize]);


    if (editor && editor.layout) editor.layout();

    const controlsLeft = controls.filter(c => c.side != "right").map(buildControl.bind(null, () => editor?.getValue()));
    const controlsRight = controls.filter(c => c.side == "right").map(buildControl.bind(null, () => editor?.getValue()));

    return <div id="bd-editor-panel" className={theme}>
                <div id="bd-editor-controls">
                    <div className="controls-section controls-left">
                        {controlsLeft}
                    </div>
                    <div className="controls-section controls-right">
                        {controlsRight}
                    </div>
                </div>
                <div className="editor-wrapper">
                    <div id={id} className={"editor " + theme}></div>
                </div>
            </div>;
});