import {type Handler} from '../handler_manager';
import type {Map} from '../map';
import {TransformProvider} from './transform-provider';
import {evaluateZoomSnap} from '../../util/util';

const defaultOptions = {
    panStep: 100,
    bearingStep: 15,
    pitchStep: 10
};

/**
 * The `KeyboardHandler` allows the user to zoom, rotate, and pan the map using
 * the following keyboard shortcuts:
 *
 * - `=` / `+`: Increase the zoom level by 1.
 * - `Shift-=` / `Shift-+`: Increase the zoom level by 2.
 * - `-`: Decrease the zoom level by 1.
 * - `Shift--`: Decrease the zoom level by 2.
 * - Arrow keys: Pan by 100 pixels.
 * - `Shift+⇢`: Increase the rotation by 15 degrees.
 * - `Shift+⇠`: Decrease the rotation by 15 degrees.
 * - `Shift+⇡`: Increase the pitch by 10 degrees.
 * - `Shift+⇣`: Decrease the pitch by 10 degrees.
 *
 * @group Handlers
 */
export class KeyboardHandler implements Handler {
    _tr: TransformProvider;
    _enabled: boolean;
    _active: boolean;
    _panStep: number;
    _bearingStep: number;
    _pitchStep: number;
    _rotationDisabled: boolean;

    /** @internal */
    constructor(map: Map) {
        this._tr = new TransformProvider(map);
        const stepOptions = defaultOptions;
        this._panStep = stepOptions.panStep;
        this._bearingStep = stepOptions.bearingStep;
        this._pitchStep = stepOptions.pitchStep;
        this._rotationDisabled = false;
    }

    reset() {
        this._active = false;
    }

    keydown(e: KeyboardEvent) {
        if (e.altKey || e.ctrlKey || e.metaKey) return;

        let zoomDir = 0;
        let bearingDir = 0;
        let pitchDir = 0;
        let xDir = 0;
        let yDir = 0;

        switch (e.keyCode) {
            case 61:
            case 107:
            case 171:
            case 187:
                zoomDir = 1;
                break;

            case 189:
            case 109:
            case 173:
                zoomDir = -1;
                break;

            case 37:
                if (e.shiftKey) {
                    bearingDir = -1;
                } else {
                    e.preventDefault();
                    xDir = -1;
                }
                break;

            case 39:
                if (e.shiftKey) {
                    bearingDir = 1;
                } else {
                    e.preventDefault();
                    xDir = 1;
                }
                break;

            case 38:
                if (e.shiftKey) {
                    pitchDir = 1;
                } else {
                    e.preventDefault();
                    yDir = -1;
                }
                break;

            case 40:
                if (e.shiftKey) {
                    pitchDir = -1;
                } else {
                    e.preventDefault();
                    yDir = 1;
                }
                break;

            default:
                return;
        }

        if (this._rotationDisabled) {
            bearingDir = 0;
            pitchDir = 0;
        }

        return {
            cameraAnimation: (map: Map) => {
                const tr = this._tr;
                map.easeTo({
                    duration: 300,
                    easeId: 'keyboardHandler',
                    easing: easeOut,

                    zoom: zoomDir ? evaluateZoomSnap(tr.zoom + zoomDir * (e.shiftKey ? 2 : 1), map.getZoomSnap()) : tr.zoom,
                    bearing: tr.bearing + bearingDir * this._bearingStep,
                    pitch: tr.pitch + pitchDir * this._pitchStep,
                    offset: [-xDir * this._panStep, -yDir * this._panStep],
                    center: tr.center
                }, {originalEvent: e});
            }
        };
    }

    /**
     * Enables the "keyboard rotate and zoom" interaction.
     *
     * @example
     * ```ts
     * map.keyboard.enable();
     * ```
     */
    enable() {
        this._enabled = true;
    }

    /**
     * Disables the "keyboard rotate and zoom" interaction.
     *
     * @example
     * ```ts
     * map.keyboard.disable();
     * ```
     */
    disable() {
        this._enabled = false;
        this.reset();
    }

    /**
     * Returns a Boolean indicating whether the "keyboard rotate and zoom"
     * interaction is enabled.
     *
     * @returns `true` if the "keyboard rotate and zoom"
     * interaction is enabled.
     */
    isEnabled() {
        return this._enabled;
    }

    /**
     * Returns true if the handler is enabled and has detected the start of a
     * zoom/rotate gesture.
     *
     * @returns `true` if the handler is enabled and has detected the
     * start of a zoom/rotate gesture.
     */
    isActive() {
        return this._active;
    }

    /**
     * Disables the "keyboard pan/rotate" interaction, leaving the
     * "keyboard zoom" interaction enabled.
     *
     * @example
     * ```ts
     * map.keyboard.disableRotation();
     * ```
     */
    disableRotation() {
        this._rotationDisabled = true;
    }

    /**
     * Enables the "keyboard pan/rotate" interaction.
     *
     * @example
     * ```ts
     * map.keyboard.enable();
     * map.keyboard.enableRotation();
     * ```
     */
    enableRotation() {
        this._rotationDisabled = false;
    }
}

function easeOut(t: number) {
    return t * (2 - t);
}
