import * as React from "react";
import { useState } from "react";
import * as ReactDOM from "react-dom"
import { useFetch } from "react-async";

const formatter = new Intl.NumberFormat(undefined, { maximumFractionDigits: 2 });

type Language = {
    name: string;
    class: string;
    embeddable: boolean;
    standalone: boolean;
}

type LanguageSelectorProps = {
    language: string;
    onSelect(language: Language): void;
}

const LanguageSelector = ({ language, onSelect }: LanguageSelectorProps) => {
    const { data: languages, isPending } = useFetch<Language[]>("/languages", { headers: { "Accept": "application/json" }});

    const regex    = language && new RegExp(`${language}`);
    const matching = languages && languages.filter(current => !language || current.name.match(regex));
    const isValidLanguage = languages && typeof languages.find(current => current.name === language) !== "undefined";

    return isPending ? null : <ul className="demo__languages">
        { (isValidLanguage ? languages : matching).map(language => <li key={ language.name }>
            <button type="button" className="btn btn-outline-primary btn-sm" onClick={ () => onSelect(language) }>
                { language.name }
            </button>
        </li>) }
    </ul>
};

type HighlightFormProps = {
    onSubmit(code: string, language: string);
}

const HighlightForm = ({ onSubmit }: HighlightFormProps) => {
    const [code, setCode] = useState<string>("");
    const [language, setLanguage] = useState<string>("");

    const handleReset = () => {
        setCode("");
        setLanguage("");
    };

    const handleSubmit = () => onSubmit && onSubmit(code, language);

    const handleLanguageSelection = language => setLanguage(language.name);

    return (
        <form className="demo__form">
            <div className="form-group">
                <label htmlFor="form_source">
                    <span className="far fa-code" aria-hidden="true"/> try it
                </label>
                <textarea name="source" className="form-control" id="form_source" rows={ 15 }
                          onChange={ ev => setCode((ev.target as HTMLTextAreaElement).value) }
                          value={ code }
                />
            </div>
            <div className="form-group row">
                <label htmlFor="form_language" className="col-md-2 col-form-label">
                    <span className="far fa-lightbulb" aria-hidden="true"/> language
                </label>
                <div className="col-md-10">
                    <input type="text" name="language" id="form_language" className="form-control" required
                           value={ language }
                           onChange={ ev => setLanguage((ev.target as HTMLInputElement).value) }
                    />
                </div>
            </div>
            <div className="form-group">
                <LanguageSelector language={ language } onSelect={ handleLanguageSelection }/>
            </div>
            <div className="demo__actions">
                <button type="button" className="btn btn-sm btn-outline-danger" onClick={ handleReset }>
                    <span className="far fa-trash fa-fw" aria-hidden="true"/> reset
                </button>
                <button type="button" className="btn btn-sm btn-outline-primary" onClick={ handleSubmit }>
                    <span className="far fa-code fa-fw" aria-hidden="true"/> highlight
                </button>
            </div>
        </form>
    )
};

const Loading = () => <div className="loading">
    <span className="fad fa-spinner-third fa-spin fa-10x" />
</div>;

type HighlightedCodeProps = {
    highlighted: HighlightedResponse;
    onClose?(): void;
}

const tooltip = text => ({
    'data-toggle': 'tooltip',
    'title': text,
    ref: el => $(el).tooltip(),
});

const HighlightedCode = ({ highlighted, onClose }: HighlightedCodeProps) => {
    const widths = {
        tokenization: `${ highlighted.times.tokenization / highlighted.times.total * 100 }%`,
        processing: `${ highlighted.times.processing / highlighted.times.total * 100 }%`,
        formatting: `${ highlighted.times.formatting / highlighted.times.total * 100 }%`,
    };

    return <>
        <pre className="keylighter" dangerouslySetInnerHTML={ { __html: highlighted.html } }/>

        <div className="d-flex align-items-center">
            <div className="demo__times">
                <div className="demo__time-splits">
                    <div className="demo__time-split demo__time-split--tokenization"
                         style={ { width: widths.tokenization } }
                         { ...tooltip(`Tokenization took ${formatter.format(highlighted.times.tokenization)}ms`) }
                    />
                    <div className="demo__time-split demo__time-split--processing"
                         style={ { width: widths.processing } }
                         { ...tooltip(`Processing took ${formatter.format(highlighted.times.processing)}ms`) }
                    />
                    <div className="demo__time-split demo__time-split--formatting"
                         style={ { width: widths.formatting } }
                         { ...tooltip(`Formatting took ${formatter.format(highlighted.times.processing)}ms`) }
                    />
                </div>
                <div className="demo__time-total">
                    Total time: { formatter.format(highlighted.times.total) }ms
                </div>
            </div>
            <div className="demo__actions ml-auto">
                <button type="button" className="btn btn-sm btn-outline-primary" onClick={ () => onClose && onClose() }>
                    <span className="far fa-sync fa-fw mr-1" aria-hidden="true"/>
                    try another
                </button>
            </div>
        </div>
    </>;
};

type DemoState = "form" | "submitting" | "show";
type HighlightedResponse = {
    html: string;
    times: {
        total: number;
        tokenization: number;
        processing: number;
        formatting: number;
    }
}

const KeylighterDemo = props => {
    const [state, setState] = useState<DemoState>("form");
    const [highlighted, setHighlighted] = useState<HighlightedResponse>(null);

    const handleCodeHighlight = async (source, language) => {
        setState("submitting");
        setHighlighted(await fetch('/highlight', {
            credentials: "include",
            method: "POST",
            body: JSON.stringify({ source, language }),
            headers: {
                'Content-Type': 'application/json'
            }
        }).then(res => res.json()));
        setState("show");
    };

    return (
        <div id="try-form" className="demo">
            <div className="container">
                { state === "form" && <HighlightForm onSubmit={ handleCodeHighlight }/> }
                { state === "submitting" && <Loading /> }
                { state === "show" && <HighlightedCode highlighted={ highlighted } onClose={ () => setState("form") } /> }
            </div>
        </div>
    )
};

export function replace<T extends Element>(component: any, element: HTMLElement, callback?: () => void) {
    const parent = document.createElement('div');

    ReactDOM.render(component, parent, () => {
        element.replaceWith(...Array.from(parent.childNodes));
        callback && callback();
    });
}

const root = document.getElementById('try-form');
replace(<KeylighterDemo />, root);
