import { logButtonPress } from './Logger.js';
var ButtonState;
(function (ButtonState) {
    ButtonState[ButtonState["IDLE"] = 0] = "IDLE";
    ButtonState[ButtonState["DOWN"] = 1] = "DOWN";
    ButtonState[ButtonState["UP"] = 2] = "UP";
})(ButtonState || (ButtonState = {}));
// the "double press timeout" is the amount of time you have to start the
// second press after the initial press is *released*. this is the *maximum
// dwell time*. the duration of the second press does not matter, only its
// initiation matters. after the second press is detected, the state machine is
// reset. the next press will be another initial press.
const DOUBLE_PRESS_DWELL_MS = new Map([
    ['quick', 300],
    ['default', 300],
    ['relaxed', 450],
    ['disabled', 0],
]);
// the "long press timeout" is the amount of time you must hold the button down
// for an *initial* press to count as "long". when this happens, the state
// machine is reset. the next press will be another initial press.
const LONG_PRESS_TIMEOUT_MS = new Map([
    ['quick', 300],
    ['default', 350],
    ['relaxed', 750],
    ['disabled', 0],
]);
// Up- and down-buttons on Picos (eg. PJ2-3BRL and PJ2-2BRL) appear to be
// intentionally slowed in their response:
//
// A millisecond log excerpt of pressing an "up" button as quickly as I can
// manage (with no mobility impairments):
//
// 2022-07-26,10:29:15.862 ... 'ButtonEvent': {'EventType': 'Press'}}}}
// 2022-07-26,10:29:15.956 ... 'ButtonEvent': {'EventType': 'Release'}}}}
// 2022-07-26,10:29:16.477 ... 'ButtonEvent': {'EventType': 'Press'}}}}
// 2022-07-26,10:29:16.495 ... 'ButtonEvent': {'EventType': 'Release'}}}}
//
// That's a time of 94 ms down, 521 ms dwell, 18 ms down, and 633 ms total.
//
// Now, the same excerpt from an "on" button:
//
// 2022-07-26,10:27:47.694 ... 'ButtonEvent': {'EventType': 'Press'}}}}
// 2022-07-26,10:27:47.807 ... 'ButtonEvent': {'EventType': 'Release'}}}}
// 2022-07-26,10:27:47.887 ... 'ButtonEvent': {'EventType': 'Press'}}}}
// 2022-07-26,10:27:47.965 ... 'ButtonEvent': {'EventType': 'Release'}}}}
//
// That's a time of 113 ms down, 80 ms dwell, 78 ms down, and 271 ms total.
//
// This obviously includes network propagation delay. Interestingly, I can't
// find any indication that the Smart Hub or the Pico itself will emit anything
// that looks like a *native* double-press.
//
// This means that we must handle up- and down-buttons differently, and add
// some delay for detecting double-press events. Long-press events are
// unaffected.
const UP_DOWN_BTN_DELAY_MS = 250;
export class ButtonTracker {
    shortPressCB;
    doublePressCB;
    longPressCB;
    log;
    href;
    buttonPressLogging;
    timer;
    state = ButtonState.IDLE;
    longPressTimeout;
    longPressDisabled = false;
    doublePressTimeout;
    doublePressDisabled = false;
    constructor(shortPressCB, doublePressCB, longPressCB, log, href, clickSpeedDouble = 'default', clickSpeedLong = 'default', isUpDownButton = false, 
    // Default 'debug' matches the platform's optionsFromConfig default —
    // safe even if a caller (e.g. a future device type or a test) forgets
    // to thread the option through.
    buttonPressLogging = 'debug') {
        this.shortPressCB = shortPressCB;
        this.doublePressCB = doublePressCB;
        this.longPressCB = longPressCB;
        this.log = log;
        this.href = href;
        this.buttonPressLogging = buttonPressLogging;
        log.debug(`btrk ${this.href} created speed ${clickSpeedDouble} dbl ${clickSpeedLong} long`);
        this.timer = null;
        if (clickSpeedLong === 'disabled') {
            this.longPressDisabled = true;
        }
        if (clickSpeedDouble === 'disabled') {
            this.doublePressDisabled = true;
        }
        if (!DOUBLE_PRESS_DWELL_MS.has(clickSpeedDouble)) {
            throw new Error(`Could not get dbl timing for speed ${clickSpeedDouble}`);
        }
        if (!LONG_PRESS_TIMEOUT_MS.has(clickSpeedLong)) {
            throw new Error(`Could not get long timing for speed ${clickSpeedLong}`);
        }
        this.longPressTimeout = LONG_PRESS_TIMEOUT_MS.get(clickSpeedLong);
        this.doublePressTimeout = DOUBLE_PRESS_DWELL_MS.get(clickSpeedDouble);
        if (isUpDownButton && !this.doublePressDisabled) {
            this.doublePressTimeout += UP_DOWN_BTN_DELAY_MS;
        }
    }
    reset() {
        this.state = ButtonState.IDLE;
        if (this.timer) {
            clearTimeout(this.timer);
        }
        this.timer = null;
        this.log.debug('btrk reset to IDLE');
    }
    update(action) {
        this.log.debug(`btrk ${this.href} got event ${action} in state ${this.state}`);
        // TODO this state machine is ill-formed, and relies on `this.timer`
        // implicitly being included in state decisions. refactor so the timer
        // updates the state variable. this will also make the `disabled`
        // options clearer.
        const longPressTimeoutHandler = () => {
            this.log.debug(`btrk ${this.href} long press timeout`);
            this.reset();
            if (this.longPressDisabled) {
                // unreachable
                return;
            }
            // Press lines are routed through logButtonPress() so the user's
            // 'buttonPressLogging' option (info / debug / silent) governs them
            // independently of the rest of the plugin's verbosity. See Logger.ts.
            logButtonPress(this.log, this.buttonPressLogging, `button ${this.href} got a long press`);
            this.longPressCB();
        };
        const doublePressTimeoutHandler = () => {
            this.log.debug(`btrk ${this.href} double press expiry`);
            this.reset();
            logButtonPress(this.log, this.buttonPressLogging, `button ${this.href} got a short press`);
            this.shortPressCB();
        };
        switch (this.state) {
            case ButtonState.IDLE: {
                if (action === 'Press') {
                    this.state = ButtonState.DOWN;
                    if (this.longPressDisabled) {
                        logButtonPress(this.log, this.buttonPressLogging, `button ${this.href} long press disabled. suppressing.`);
                    }
                    else {
                        this.timer = setTimeout(longPressTimeoutHandler, this.longPressTimeout);
                    }
                    this.log.debug(`btrk ${this.href} now in state DOWN`);
                }
                else {
                    // no-op
                    this.log.debug(`btrk ${this.href} no-op IDLE action ${action}`);
                }
                break;
            }
            case ButtonState.DOWN: {
                if (action === 'Release') {
                    this.state = ButtonState.UP;
                    if (this.timer) {
                        clearTimeout(this.timer);
                        this.log.debug(`btrk ${this.href} cleared timer`);
                    }
                    this.timer = setTimeout(() => {
                        doublePressTimeoutHandler();
                    }, this.doublePressTimeout);
                    this.log.debug(`btrk ${this.href} now in UP state`);
                }
                else {
                    // action == "Press"
                    this.log.error(`btrk invalid action ${action} for state ${this.state}. resetting`);
                    this.reset();
                }
                break;
            }
            case ButtonState.UP: {
                if (action === 'Press' && this.timer) {
                    // the button was pressed again before the timer fired
                    this.log.debug(`btrk ${this.href} pressed before double-tap expiry`);
                    this.reset();
                    if (this.doublePressDisabled) {
                        logButtonPress(this.log, this.buttonPressLogging, `button ${this.href} double press disabled. suppressing.`);
                        return;
                    }
                    logButtonPress(this.log, this.buttonPressLogging, `button ${this.href} got a double press`);
                    this.doublePressCB();
                }
                else {
                    this.log.error(`btrk invalid action ${action} for state ${this.state}. resetting`);
                    this.reset();
                }
                break;
            }
        }
    }
}
//# sourceMappingURL=ButtonState.js.map