#include "button_sample.h"
#include "shell_input_policy.h"
#include <algorithm>
#include <atomic>
#include <cassert>
#include <cstdio>
#include <deque>
#include <functional>
#include <vector>
#include <string>

// Deterministic RTOS/GPIO stubs; the task and consumer below are production code.
using TickType_t = unsigned;
constexpr TickType_t pdMS_TO_TICKS(unsigned ms) { return ms / 10; }
constexpr int pdTRUE=1;
constexpr int BUTTON_A_PIN=2, BUTTON_B_PIN=1;
constexpr const char *TAG="test";
int s_button_policy_lock=0;
#define portENTER_CRITICAL(lock) ((void)(lock))
#define portEXIT_CRITICAL(lock) ((void)(lock))
void ESP_LOGI(const char *,const char *,...) {}
void ESP_LOGW(const char *,const char *,...) {}
uint64_t now_ms=0, end_ms=0;
size_t capacity=32;
std::deque<ButtonSample> queue;
void *s_button_events=&queue;
ButtonInputPolicy s_button_policy;
std::atomic<uint32_t> s_button_queue_generation{0};
uint64_t s_ui_epoch=1;
struct { uint64_t generation=0; bool modal() const { return false; } } s_approval;
InputModal s_input_modal=InputModal::SETTINGS;
bool overlay=false;
std::string s_voice_phase;
unsigned cancellations=0;
void cancel_input(bool,const char *) {
    ++cancellations;s_voice_phase.clear();s_input_modal=InputModal::NONE;++s_ui_epoch;
}
bool page_overlay_visible() { return overlay || s_input_modal!=InputModal::NONE; }
std::function<bool(bool,uint64_t)> gpio;
std::function<void(uint64_t)> tick;
uint64_t esp_timer_get_time() { return now_ms*1000; }
int gpio_get_level(int pin) { return gpio(pin==BUTTON_A_PIN,now_ms) ? 0 : 1; }
TickType_t xTaskGetTickCount() { return TickType_t(now_ms/10); }
struct Stop {};
void vTaskDelayUntil(TickType_t *wake,TickType_t period) {
    *wake+=period;now_ms=uint64_t(*wake)*10;
    if(tick) tick(now_ms);
    if(now_ms>=end_ms) throw Stop{};
}
int xQueueSend(void *,const ButtonSample *event,unsigned) {
    if(queue.size()>=capacity) return 0;
    queue.push_back(*event);return pdTRUE;
}
int xQueueReceive(void *,ButtonSample *event,unsigned) {
    if(queue.empty()) return 0;
    *event=queue.front();queue.pop_front();return pdTRUE;
}
std::vector<ButtonSample> applied;
void publish_button_policy();
void handle_gesture(bool a,Gesture gesture,uint64_t epoch,uint64_t sampled_ms) {
    if(gesture==Gesture::NONE) return;
    // Same ownership gate as the production UI arbiter (covered separately by
    // shell_input_test): stale clicks cannot act on a different owner.
    if(gesture!=Gesture::LONG_RELEASE && epoch!=s_ui_epoch+s_approval.generation) return;
    applied.push_back({epoch,sampled_ms,0,gesture,a,false,false});
    ++s_ui_epoch;publish_button_policy();
}
#include "button_sampler.inc"

void reset(uint64_t end) {
    now_ms=0;end_ms=end;capacity=32;queue.clear();applied.clear();
    s_button_queue_generation=0;s_ui_epoch=1;s_approval.generation=0;
    s_input_modal=InputModal::SETTINGS;overlay=false;tick={};s_voice_phase.clear();cancellations=0;
    publish_button_policy();
}
void run() { try { button_sample_task(nullptr); } catch(const Stop &) {} }
size_t clicks() {
    return std::count_if(applied.begin(),applied.end(),[](const ButtonSample &s) { return s.gesture==Gesture::CLICK; });
}
int main() {
    // A/B raw pulses occur entirely inside a 520ms main/display stall. Sampling
    // continues, and both DOWN/UP are queued before main resumes.
    for(bool a : {true,false}) {
        reset(700);
        gpio=[a](bool button,uint64_t t) { return button==a && t>=200 && t<270; };
        run();
        assert(applied.empty() && queue.size()==2);
        assert(queue[0].edge && queue[0].down && queue[0].sampled_ms==230);
        assert(queue[1].edge && !queue[1].down && queue[1].sampled_ms==300);
        assert(queue[1].gesture==Gesture::CLICK);
        consume_button_events();
        assert(clicks()==1 && applied[0].a==a && applied[0].sampled_ms==300);
    }
    std::puts("70ms A/B press wholly inside display stall survives queue PASS");
    // A stale buffered event must not be replayed after a session/modal change.
    reset(700);gpio=[](bool a,uint64_t t) { return a && t>=200 && t<270; };
    run();++s_ui_epoch;publish_button_policy();consume_button_events();assert(applied.empty());
    std::puts("queued click remains bound to original UI owner PASS");
    // Two physical clicks on normal shell still resolve to one double click.
    reset(700);s_input_modal=InputModal::NONE;publish_button_policy();
    gpio=[](bool a,uint64_t t) { return !a && ((t>=100 && t<170)||(t>=270 && t<340)); };
    run();consume_button_events();assert(applied.size()==1 && applied[0].gesture==Gesture::DOUBLE_CLICK);
    // Settings keeps immediate click (at the debounced release, no double gap).
    reset(400);gpio=[](bool a,uint64_t t) { return a && t>=100 && t<170; };
    tick=[](uint64_t t) { if(t==210) consume_button_events(); };
    run();assert(clicks()==1 && applied[0].sampled_ms==200);
    std::puts("shell double click / immediate Settings click preserved PASS");
    // Long opens an owner; release must reach that owner despite epoch advance.
    reset(1000);gpio=[](bool a,uint64_t t) { return a && t>=100 && t<800; };
    tick=[](uint64_t t) { if(t==750) consume_button_events(); };
    run();consume_button_events();assert(applied.size()==2);
    assert(applied[0].gesture==Gesture::LONG && applied[1].gesture==Gesture::LONG_RELEASE);
    assert(clicks()==0);
    std::puts("long and long-release survive owner change without trailing click PASS");
    // Buffer overflow fails closed: discard the incomplete sequence, do not
    // re-arm a held button. A subsequent fresh click works after release.
    reset(1500);capacity=1;
    gpio=[](bool a,uint64_t t) { return a && ((t>=100 && t<900)||(t>=1200 && t<1270)); };
    tick=[](uint64_t t) { if(t==950) {consume_button_events();capacity=32;} };
    run();assert(s_button_queue_generation==1);consume_button_events();
    assert(clicks()==1 && applied.size()==1 && applied[0].sampled_ms==1300);
    std::puts("queue overflow discards incomplete generation and recovers only after release PASS");
    // Lost LONG_RELEASE must not leave an already-started microphone recording.
    reset(100);consume_button_events();
    s_input_modal=InputModal::VOICE;s_voice_phase="RECORDING";
    ++s_button_queue_generation;consume_button_events();
    assert(cancellations==1 && s_voice_phase.empty());
    consume_button_events();assert(cancellations==1);
    std::puts("overflow cancels active recording if its physical release was lost PASS");
}
