#include "agent_target.h"
#include "button_gesture.h"
#include "speech_control.h"
#include "input_sender_timing.h"
#include "voice_capture_limits.h"
#include "page_redraw_policy.h"
#include <cassert>
#include <cstring>

// Host composition of production gesture/menu/identity code. Display drawing,
// task scheduling and USB must still be checked on the physical device.
struct Device {
    enum class Page { PI, NETWORK } page = Page::PI;
    enum class Modal { NONE, SETTINGS, VOICE } modal = Modal::NONE;
    unsigned epoch = 1, cancellations = 0;
    bool recording = false;
    SpeechMenu menu;
    void snapshot(const char *agent, const char *session) {
        if (agent_target_changed(true, "pi", "session-one", agent, session)) {
            ++cancellations;
            modal = Modal::NONE;
            recording = false;
            page = Page::PI;
        }
    }
};

static Gesture click(ButtonGesture &button, unsigned epoch, uint64_t start) {
    assert(button.update(true,start,epoch,false)==Gesture::NONE);
    assert(!button.down());
    assert(button.update(true,start+30,epoch,false)==Gesture::NONE);
    assert(button.down());
    assert(button.update(false,start+100,epoch,false)==Gesture::NONE);
    assert(button.down());
    const auto result=button.update(false,start+130,epoch,false);
    assert(!button.down());
    return result;
}

int main() {
    for (const auto page:{DisplayPage::OVERVIEW,DisplayPage::CPU,DisplayPage::MEMORY,DisplayPage::DISK,DisplayPage::NETWORK}) {
        assert(server_snapshot_needs_redraw(page,false));
        assert(!server_snapshot_needs_redraw(page,true));
        assert(!agent_snapshot_needs_redraw(page,false,true));
    }
    assert(!server_snapshot_needs_redraw(DisplayPage::PI,false));
    assert(!agent_snapshot_needs_redraw(DisplayPage::PI,false,false));
    assert(!agent_snapshot_needs_redraw(DisplayPage::PI,true,true));
    assert(agent_snapshot_needs_redraw(DisplayPage::PI,false,true));
    // Actual ESP-IDF config: 100 Hz, so 5 ms truncates to zero ticks.
    // A runnable priority-5 sender can starve the priority-1 button loop.
    constexpr unsigned tick_hz = 100;
    constexpr unsigned converted = 5U * tick_hz / 1000U;
    static_assert(converted == 0);
    static_assert(input_sender_idle_ticks(converted) == 1);
    static_assert(input_sender_idle_ticks(5U * 1000U / 1000U) == 5);
    // The old eight-frame queue covered only 320 ms, less than the observed
    // startup write stalls. Buffer at least the actual bounded send timeout.
    static_assert(8U * VOICE_CAPTURE_FRAME_MS < VOICE_SEND_TIMEOUT_MS);
    static_assert(VOICE_CAPTURE_QUEUE_FRAMES * VOICE_CAPTURE_FRAME_MS > VOICE_SEND_TIMEOUT_MS);

    // A: PI -> B CLICK -> NETWORK; repeated same target must not reset it.
    Device d;
    ButtonGesture b;
    assert(click(b,d.epoch,0)==Gesture::CLICK);
    d.page=Device::Page::NETWORK;
    for (unsigned i=0;i<10;++i) d.snapshot("pi","session-one");
    assert(d.page==Device::Page::NETWORK && d.cancellations==0);

    // B: long B -> Settings; release has no trailing select; A advances root.
    b.update(true,200,d.epoch,true);
    b.update(true,230,d.epoch,true);
    assert(b.update(true,830,d.epoch,true)==Gesture::LONG);
    d.modal=Device::Modal::SETTINGS;d.menu.open();++d.epoch;
    b.update(false,900,d.epoch,true);
    assert(b.update(false,930,d.epoch,true)==Gesture::LONG_RELEASE);
    assert(d.modal==Device::Modal::SETTINGS && d.menu.label()=="AGENT SETTINGS");
    ButtonGesture a;
    assert(click(a,d.epoch,1000)==Gesture::CLICK);
    assert(d.menu.click(true)==SpeechMenuAction::NONE);
    assert(d.menu.label()=="SPEECH");

    // C: B selects speech; selecting the first root item requests agent settings.
    assert(click(b,d.epoch,1200)==Gesture::CLICK);
    assert(d.menu.click(false)==SpeechMenuAction::LOAD_SPEECH);
    assert(d.menu.page==SpeechMenu::Page::SPEECH);
    d.menu.open();
    assert(click(b,d.epoch,1400)==Gesture::CLICK);
    assert(d.menu.click(false)==SpeechMenuAction::AGENT_SETTINGS);

    // D: long A -> recording; same-target snapshots preserve the Voice modal.
    d.modal=Device::Modal::NONE;
    a.update(true,1600,d.epoch,true);a.update(true,1630,d.epoch,true);
    assert(a.update(true,2230,d.epoch,true)==Gesture::LONG);
    d.modal=Device::Modal::VOICE;d.recording=true;++d.epoch;
    for (unsigned i=0;i<10;++i) d.snapshot("pi","session-one");
    assert(d.modal==Device::Modal::VOICE && d.recording && d.cancellations==0);
    a.update(false,2300,d.epoch,false);
    assert(a.update(false,2330,d.epoch,false)==Gesture::LONG_RELEASE);

    // Real target changes must retain the cancellation/reset contract.
    d.snapshot("codex","session-one");
    assert(d.page==Device::Page::PI && d.modal==Device::Modal::NONE);
    assert(!d.recording && d.cancellations==1);
    assert(agent_target_changed(true,"pi","session-one","pi","session-two"));
    assert(agent_target_changed(false,"pi","session-one","pi","session-one"));
    assert(!agent_target_changed(true,"pi","unknown",nullptr,nullptr));
}
