#pragma once
#include <cstdint>

// Pure, host-testable touch gesture reducer. It owns no I2C and no timing
// source: the caller feeds one sample per contact poll with a millisecond clock.
//
// One contact resolves to at most one primary gesture. A resolved gesture is
// latched until the finger lifts, so Long can never be followed by Tap/Swipe and
// a Swipe can never be followed by Tap/Long.
namespace wearable {

enum class TouchGesture : uint8_t {
    None, Down, Move, Tap, SwipeLeft, SwipeRight, SwipeUp, SwipeDown, Long, LongRelease, Up
};

// Side of the screen a contact started on. The shell maps this to a virtual
// A (right) or B (left) button, keeping one reducer for every modal owner.
enum class TouchSide : uint8_t { Left, Right };

struct TouchThresholds {
    // Movement still counted as a tap.
    uint16_t tap_slop = 18;
    // Minimum travel for a swipe.
    uint16_t swipe_distance = 55;
    // Dominant axis must exceed the secondary axis by this factor (x1.5 in x10).
    uint16_t swipe_axis_ratio_x10 = 15;
    // Hold duration for a long press.
    uint32_t long_ms = 600;
    // Movement still counted as a long press.
    uint16_t long_slop = 20;
    // Contact must persist at least this long to leave Down (bounce/noise guard).
    uint32_t press_min_ms = 30;
    // Horizontal split of the screen width for the virtual A/B side.
    uint16_t split_x = 234;
};

class TouchGestureReducer {
public:
    static constexpr TouchThresholds kDefault{};

    void configure(const TouchThresholds& t) { t_ = t; }
    const TouchThresholds& thresholds() const { return t_; }

    // True while a contact is being tracked after Down was reported.
    bool active() const { return active_; }
    TouchSide side() const { return side_; }

    // Feed the debounced pressed state and, when pressed, the sample position.
    // Returns the transition for this sample, or None. A release reports the
    // single primary gesture: LongRelease after a Long, Tap when the contact
    // stayed inside the tap slop, otherwise Up.
    TouchGesture update(bool pressed, uint16_t x, uint16_t y, uint64_t now) {
        if (!pressed) {
            if (!raw_) return TouchGesture::None;
            raw_ = false; changed_ = now;
            if (!active_) return TouchGesture::None;   // never became a real contact
            active_ = false;
            const bool was_long = long_fired_;
            const bool was_tap = !resolved_ && !tap_slop_exceeded_ && !was_long;
            long_fired_ = resolved_ = tap_slop_exceeded_ = false;
            if (was_long) return TouchGesture::LongRelease;
            return was_tap ? TouchGesture::Tap : TouchGesture::Up;
        }
        if (!raw_) {
            raw_ = true; changed_ = now; start_ = now;
            // Remember the first raw sample; the debounce window must not move
            // the origin, or a fast press would hide its own travel.
            origin_x_ = x; origin_y_ = y;
            start_x_ = x; start_y_ = y; last_x_ = x; last_y_ = y;
            active_ = false; long_fired_ = false; resolved_ = false; tap_slop_exceeded_ = false;
            return TouchGesture::None;
        }
        // Bounce: ignore the raw level until the press has been stable.
        bool just_down = false;
        if (!active_) {
            if (now - changed_ < t_.press_min_ms) { last_x_ = x; last_y_ = y; return TouchGesture::None; }
            active_ = true;
            start_ = now; start_x_ = origin_x_; start_y_ = origin_y_;
            side_ = origin_x_ < t_.split_x ? TouchSide::Left : TouchSide::Right;
            just_down = true;
        }
        if (resolved_) { last_x_ = x; last_y_ = y; return TouchGesture::None; }
        last_x_ = x; last_y_ = y;
        const uint16_t dx = static_cast<uint16_t>(x > start_x_ ? x - start_x_ : start_x_ - x);
        const uint16_t dy = static_cast<uint16_t>(y > start_y_ ? y - start_y_ : start_y_ - y);
        if (dx >= t_.swipe_distance || dy >= t_.swipe_distance) {
            const auto gesture = swipe(dx, dy);
            if (gesture != TouchGesture::None) { resolved_ = true; return gesture; }
        }
        if (!long_fired_ && dx <= t_.long_slop && dy <= t_.long_slop &&
            now - start_ >= t_.long_ms) {
            long_fired_ = true; resolved_ = true;
            return TouchGesture::Long;
        }
        // A contact that left the tap slop can no longer become a Tap on release,
        // but it may still resolve as a Swipe while the finger keeps moving.
        if (dx > t_.tap_slop || dy > t_.tap_slop) tap_slop_exceeded_ = true;
        return just_down ? TouchGesture::Down : TouchGesture::Move;
    }

    // Called after a gesture sequence ends, so the next press starts clean.
    void reset() {
        active_ = long_fired_ = resolved_ = raw_ = tap_slop_exceeded_ = false;
        changed_ = start_ = 0; start_x_ = start_y_ = last_x_ = last_y_ = 0;
        origin_x_ = origin_y_ = 0;
    }

private:
    uint16_t travel() const {
        const uint16_t dx = static_cast<uint16_t>(last_x_ > start_x_ ? last_x_ - start_x_ : start_x_ - last_x_);
        const uint16_t dy = static_cast<uint16_t>(last_y_ > start_y_ ? last_y_ - start_y_ : start_y_ - last_y_);
        return dx > dy ? dx : dy;
    }
    TouchGesture swipe(uint16_t dx, uint16_t dy) const {
        // Dominant axis must be clearly larger; otherwise the direction is
        // ambiguous and the contact is not treated as a swipe.
        if (dx * 10 >= dy * t_.swipe_axis_ratio_x10 && dx >= dy) {
            return last_x_ >= start_x_ ? TouchGesture::SwipeRight : TouchGesture::SwipeLeft;
        }
        if (dy * 10 >= dx * t_.swipe_axis_ratio_x10 && dy > dx) {
            return last_y_ >= start_y_ ? TouchGesture::SwipeDown : TouchGesture::SwipeUp;
        }
        return TouchGesture::None;
    }

    TouchThresholds t_{};
    bool raw_ = false, active_ = false, long_fired_ = false, resolved_ = false, tap_slop_exceeded_ = false;
    TouchSide side_ = TouchSide::Left;
    uint64_t changed_ = 0, start_ = 0;
    uint16_t start_x_ = 0, start_y_ = 0, last_x_ = 0, last_y_ = 0, origin_x_ = 0, origin_y_ = 0;
};

} // namespace wearable