import * as React from "react";
import { Option, map, expect } from "../util/OptionUtil";
import { hotkeySystem } from "./LiveSplit";
import { Circle, Trash } from "lucide-react";

import "../css/HotkeyButton.scss";

function resolveKey(keyCode: string): Promise<string> | string {
    return expect(
        hotkeySystem,
        "The Hotkey System should always be initialized.",
    ).resolve(keyCode);
}

export interface Props {
    value: Option<string>;
    setValue: (value: Option<string>) => void;
}

export interface State {
    listener: Option<EventListenerObject>;
    intervalHandle: Option<number>;
    resolvedKey: Option<string>;
}

export default class HotkeyButton extends React.Component<Props, State> {
    constructor(props: Props) {
        super(props);

        this.state = {
            listener: null,
            intervalHandle: null,
            resolvedKey: null,
        };

        this.updateResolvedKey(props.value);
    }
    public componentDidUpdate(previousProps: Props) {
        if (previousProps.value !== this.props.value) {
            this.updateResolvedKey(this.props.value);
        }
    }

    private async updateResolvedKey(value: Option<string>): Promise<void> {
        let resolvedKey = "";
        if (value != null) {
            const matches = value.match(/(.+)\+\s*(.+)$/);
            if (matches != null) {
                resolvedKey = `${matches[1]}+ ${await resolveKey(matches[2])}`;
            } else {
                resolvedKey = await resolveKey(value);
            }
        }
        this.setState({ resolvedKey });
    }

    public render() {
        let buttonText: Option<string> | React.JSX.Element = null;
        if (this.props.value != null) {
            buttonText = this.state.resolvedKey;
        } else if (this.state.listener != null) {
            buttonText = (
                <Circle strokeWidth={0} size={16} fill="currentColor" />
            );
        }

        return (
            <div className="hotkey-box">
                <button
                    className={`hotkey-button tooltip ${
                        this.state.listener != null ? "focused" : ""
                    }`}
                    onClick={() => this.focusButton()}
                >
                    {buttonText}
                    <span className="tooltip-text">
                        Click to record a hotkey. You may also use buttons on a
                        gamepad. Global hotkeys are currently not possible.
                        Gamepad buttons work globally.
                    </span>
                </button>
                {map(this.props.value, () => (
                    <Trash
                        className="trash"
                        strokeWidth={2.5}
                        size={20}
                        onClick={() => this.props.setValue(null)}
                    />
                ))}
                {this.state.listener != null && (
                    <div
                        style={{
                            bottom: "0px",
                            left: "0px",
                            position: "fixed",
                            right: "0px",
                            top: "0px",
                            zIndex: 5,
                        }}
                        onClick={() => this.blurButton()}
                    />
                )}
            </div>
        );
    }

    private focusButton() {
        let listener = this.state.listener;
        let intervalHandle = this.state.intervalHandle;

        if (listener === null) {
            listener = {
                handleEvent: (ev: KeyboardEvent) => {
                    if (ev.repeat) {
                        return;
                    }
                    let text = "";
                    if (
                        ev.ctrlKey &&
                        ev.code !== "ControlLeft" &&
                        ev.code !== "ControlRight"
                    ) {
                        text += "Ctrl + ";
                    }
                    if (
                        ev.altKey &&
                        ev.code !== "AltLeft" &&
                        ev.code !== "AltRight"
                    ) {
                        text += "Alt + ";
                    }
                    if (
                        ev.metaKey &&
                        ev.code !== "MetaLeft" &&
                        ev.code !== "MetaRight"
                    ) {
                        text += "Meta + ";
                    }
                    if (
                        ev.shiftKey &&
                        ev.code !== "ShiftLeft" &&
                        ev.code !== "ShiftRight"
                    ) {
                        text += "Shift + ";
                    }
                    text += ev.code;
                    this.props.setValue(text);
                    ev.preventDefault();
                },
            };

            window.addEventListener("keydown", listener);
        }

        if (intervalHandle === null) {
            const oldButtonState: boolean[][] = [];
            intervalHandle = window.setInterval(() => {
                const gamepads = navigator.getGamepads();

                let gamepadIdx = 0;
                for (const gamepad of gamepads) {
                    if (gamepadIdx >= oldButtonState.length) {
                        oldButtonState[gamepadIdx] = [];
                    }

                    if (gamepad !== null) {
                        let buttonIdx = 0;
                        for (const button of gamepad.buttons) {
                            const oldState =
                                oldButtonState[gamepadIdx]?.[buttonIdx] ??
                                false;
                            if (button.pressed && !oldState) {
                                this.props.setValue(`Gamepad${buttonIdx}`);
                            }

                            oldButtonState[gamepadIdx][buttonIdx] =
                                button.pressed;

                            buttonIdx++;
                        }
                    }

                    gamepadIdx++;
                }
            }, 1000 / 60.0);
        }

        this.setState({
            listener,
            intervalHandle,
        });
    }

    private blurButton() {
        if (this.state.listener != null) {
            window.removeEventListener("keydown", this.state.listener);
        }
        if (this.state.intervalHandle != null) {
            window.clearTimeout(this.state.intervalHandle);
        }
        this.setState({
            listener: null,
            intervalHandle: null,
        });
    }
}
