#include "board.h"
#include "ui_state.h"
#include "button_gesture.h"
#include "shell_input_policy.h"
#include "approval_control.h"
#include "speech_control.h"
#include <atomic>
#include <cassert>
#include <cstdio>
#include <string>
#include <vector>
using namespace wearable;
constexpr const char* TAG="host";
void ESP_LOGI(const char*,const char*,...) {}
InputModal s_input_modal=InputModal::NONE;
UiState s_ui;
ApprovalControl s_approval;ApprovalAction s_approval_action;
LoadedPreferences s_saved_preferences;
uint64_t s_ui_epoch=1,s_voice_serial=0,s_voice_started=0;
std::string s_settings_phase,s_settings_id,s_settings_target,s_preference_feedback,s_voice_id,s_voice_phase,s_modal_error;
std::string s_read_screen;
SpeechMenu s_speech_menu;
bool s_cloud_connected=false,s_message_visible=false;
struct {bool selected=true;} s_pi;
std::atomic<bool> s_vibration_enabled{true};
struct QuickModel {std::string id,default_effort;std::vector<std::string> efforts;};
std::vector<QuickModel> s_models;
size_t s_model_index=0,s_effort_index=0;
board_button_t s_page_click=BOARD_BUTTON_NONE;
int s_page=0;
const char* page_name(int) {return "host";}
const char* modal_name(InputModal) {return "modal";}
const char* gesture_name(Gesture) {return "gesture";}
unsigned record_start=0,record_stop=0,saves=0,remote_requests=0,voice_send=0,voice_cancel=0;
unsigned speech_requests=0;
bool record_cancelled=false;
bool save_success=false;
struct {unsigned brightness=0;void setPanelBrightness(unsigned v) {brightness=v;}} s_display;
void lock_display() {}
void unlock_display() {}
void draw_status() {}
void log_modal_transition(InputModal,const char*) {}
void log_stable_edge(bool,bool,const ButtonGesture&) {}
void stop_speech() {}
bool audio_backend_record_start(uint64_t) {++record_start;return true;}
void audio_backend_record_stop(bool cancelled) {++record_stop;record_cancelled=cancelled;}
std::string new_identity(uint64_t* serial=nullptr) {if(serial) *serial=17;return "test-id";}
std::string selected_target() {return "agent:pi:retained";}
void voice_command(const char*,const char* decision) {if(std::string(decision)=="send") ++voice_send;else ++voice_cancel;}
void speech_command(const char*) {++speech_requests;}
std::string read_screen_text() {return "screen";}
void settings_command(const char*) {++remote_requests;}
void cancel_input(bool,const char*) {s_input_modal=InputModal::NONE;s_ui.close_settings();++s_ui_epoch;}
esp_err_t preferences_save(LoadedPreferences& saved,const DevicePreferences& value) {
    ++saves;if(save_success) {saved.value=value;return ESP_OK;}return ESP_FAIL;
}
void sync_display_page() {}
#include "shell_input.inc"
void gesture(bool a,Gesture g) {handle_gesture(a,g,s_ui_epoch+s_approval.generation,1000);}
// Poll the extracted production GPIO -> policy -> ButtonGesture -> reducer.
// Every press is raw active-low GPIO translated to down=true, as on the board.
void raw_hold(bool a) {
    static uint64_t now=10000;
    ButtonGesture button;
    const auto epoch=s_ui_epoch;const auto modal=s_input_modal;const auto phase=s_settings_phase;
    const auto starts=record_start;
    poll_shell_button(button,a,true,now);
    poll_shell_button(button,a,true,now+29);assert(!button.down());
    poll_shell_button(button,a,true,now+30);assert(button.down());
    poll_shell_button(button,a,true,now+629);
    assert(s_ui_epoch==epoch && s_input_modal==modal && s_settings_phase==phase && record_start==starts);
    poll_shell_button(button,a,true,now+631);
    const auto after_epoch=s_ui_epoch;const auto after_modal=s_input_modal;const auto after_starts=record_start;
    poll_shell_button(button,a,false,now+700);
    poll_shell_button(button,a,false,now+730);assert(!button.down());
    poll_shell_button(button,a,false,now+1000);
    assert(s_ui_epoch==after_epoch && s_input_modal==after_modal && record_start==after_starts);
    assert(s_page_click==BOARD_BUTTON_NONE);
    now+=2000;
}
void raw_settings_regression() {
    s_input_modal=InputModal::NONE;s_page_click=BOARD_BUTTON_NONE;s_ui.close_settings();
    s_ui.app=App::Monitor;
    const auto starts=record_start;const auto writes=saves;
    for(auto menu:{Menu::Root,Menu::Agent,Menu::Device,Menu::Display,Menu::System}) {
        raw_hold(false);assert(s_input_modal==InputModal::SETTINGS);
        s_ui.menu=menu;raw_hold(true);
        assert(s_ui.app==App::Monitor && record_start==starts && saves==writes);
        if(menu==Menu::Root) assert(s_input_modal==InputModal::NONE && !s_ui.settings_open);
        else {assert(s_ui.menu==Menu::Root);raw_hold(false);assert(s_input_modal==InputModal::NONE);}
    }
    raw_hold(false);s_ui.menu=Menu::Device;s_ui.index=0;gesture(false,Gesture::CLICK);
    const auto brightness=s_ui.preferences.brightness;
    gesture(true,Gesture::CLICK);assert(s_ui.draft.brightness!=brightness);
    raw_hold(true);
    assert(s_ui.menu==Menu::Device && s_ui.draft.brightness==brightness);
    assert(s_display.brightness==panel_value(brightness) && saves==writes);
    for(auto field:{Field::Vibration,Field::HomeScreen,Field::Text,Field::Theme}) {
        s_ui.menu=Menu::Edit;s_ui.editor_parent=Menu::Display;s_ui.field=field;
        gesture(true,Gesture::CLICK);raw_hold(true);
        assert(s_ui.menu==Menu::Display && valid(s_ui.draft) && saves==writes);
    }
    s_settings_phase="EFFORT";raw_hold(true);assert(s_settings_phase=="MODEL");
    raw_hold(true);assert(s_settings_phase=="MENU" && s_ui.menu==Menu::Agent);
    for(const char* phase:{"MODEL UPDATED","UPDATE FAILED"}) {
        s_settings_phase=phase;s_settings_id="result";raw_hold(true);
        assert(s_settings_phase=="MENU" && s_ui.menu==Menu::Agent && s_settings_id.empty());
    }
    s_settings_phase="SPEECH";s_speech_menu.load("OFF");s_speech_menu.open_speech();s_speech_menu.click(false);
    gesture(true,Gesture::CLICK);raw_hold(true);
    assert(s_settings_phase=="SPEECH" && s_speech_menu.page==SpeechMenu::Page::SPEECH && s_speech_menu.mode==0);
    raw_hold(true);assert(s_settings_phase=="MENU" && s_ui.menu==Menu::Agent);
    for(const char* phase:{"LOADING","APPLYING","SAVING SPEECH"}) {
        s_settings_phase=phase;s_settings_id="pending";const auto requests=remote_requests;
        raw_hold(true);assert(s_settings_phase==phase && s_settings_id=="pending" && remote_requests==requests);
        raw_hold(false);assert(s_input_modal==InputModal::NONE);raw_hold(false);
    }
    raw_hold(false);assert(s_input_modal==InputModal::NONE);
    assert(record_start==starts && saves==writes); // Every Settings Hold A: Voice starts=0.
    raw_hold(true);assert(record_start==starts+1 && s_input_modal==InputModal::VOICE);
    assert(s_voice_phase=="TRANSCRIBING"); // LONG_RELEASE reached the Voice owner once.
    s_voice_phase="AWAITING_CONFIRMATION";
    const auto sends=voice_send;ButtonGesture voice;
    poll_shell_button(voice,true,true,80000);poll_shell_button(voice,true,true,80030);
    poll_shell_button(voice,true,true,80700);
    assert(record_start==starts+1 && voice_send==sends && s_input_modal==InputModal::VOICE);
    poll_shell_button(voice,true,false,80800);poll_shell_button(voice,true,false,80830);
    // Voice owns the release, preserving the existing explicit SEND behavior.
    assert(record_start==starts+1 && voice_send==sends+1 && s_voice_phase=="SENDING");
    cancel_input(false,"host");
    // A hold begun before Approval arrives cannot answer that new request.
    ButtonGesture a;poll_shell_button(a,true,true,90000);poll_shell_button(a,true,true,90030);
    s_approval_action={};s_approval.receive({"raw-approval","pi","command","details",0},90100);
    poll_shell_button(a,true,true,90700);poll_shell_button(a,true,false,90800);poll_shell_button(a,true,false,90830);
    assert(s_approval_action.id.empty() && record_start==starts+1 && s_approval.current());
    // Inside Approval, no LONG action or navigation: only its authorized release.
    ButtonGesture approval;const auto app=s_ui.app;
    poll_shell_button(approval,true,true,91000);poll_shell_button(approval,true,true,91030);poll_shell_button(approval,true,true,91700);
    assert(s_approval_action.id.empty() && s_input_modal==InputModal::NONE && s_ui.app==app && record_start==starts+1);
    poll_shell_button(approval,true,false,91800);poll_shell_button(approval,true,false,91830);
    assert(s_approval_action.id=="raw-approval" && s_approval_action.approve);
    s_approval.disconnect();
    std::puts("Production raw button polling / all Settings Back paths / Settings Voice starts=0 / Approval ownership PASS (host)");
}
int main() {
    s_ui.boot({});
    // Offline Long B opens local settings, with no remote request or flash write.
    gesture(false,Gesture::LONG);assert(s_input_modal==InputModal::SETTINGS && s_ui.settings_open && !remote_requests && !saves);
    gesture(false,Gesture::LONG_RELEASE);assert(s_ui.menu==Menu::Root && s_ui.index==0);
    gesture(true,Gesture::CLICK);gesture(false,Gesture::CLICK);assert(s_ui.menu==Menu::Device);
    gesture(false,Gesture::CLICK);gesture(true,Gesture::CLICK);assert(!saves && s_ui.preferences.brightness==80);
    gesture(false,Gesture::CLICK);assert(saves==1 && s_display.brightness==230 && s_preference_feedback=="APPLIED / NOT SAVED");
    gesture(false,Gesture::LONG);assert(s_input_modal==InputModal::NONE && s_ui.app==App::Watch);
    board_previous_page();assert(s_ui.app==App::Help);
    board_next_page();assert(s_ui.app==App::Watch);
    board_next_page();assert(s_ui.app==App::Agent && s_pi.selected);
    // Every stable Settings page supports Long A, without invoking Voice.
    for(auto menu:{Menu::Root,Menu::Agent,Menu::Device,Menu::Display,Menu::System}) {
        gesture(false,Gesture::LONG);s_ui.menu=menu;
        gesture(true,Gesture::LONG);
        assert(record_start==0 && saves==1);
        if(menu==Menu::Root) assert(!s_ui.settings_open && s_input_modal==InputModal::NONE);
        else {assert(s_ui.menu==Menu::Root);gesture(false,Gesture::LONG);}
    }
    gesture(false,Gesture::LONG);s_ui.menu=Menu::Device;
    gesture(false,Gesture::CLICK); // Brightness editor starts from applied 90.
    const auto applied=s_ui.preferences.brightness;const auto save_count=saves;
    gesture(true,Gesture::CLICK);assert(s_ui.draft.brightness==100 && s_display.brightness==255);
    gesture(true,Gesture::LONG);
    assert(s_ui.menu==Menu::Device && s_ui.preferences.brightness==applied && s_ui.draft.brightness==applied);
    assert(s_display.brightness==panel_value(applied) && saves==save_count);
    save_success=true;
    gesture(false,Gesture::CLICK);gesture(true,Gesture::CLICK);gesture(false,Gesture::CLICK);
    assert(s_ui.menu==Menu::Device && s_ui.preferences.brightness==100 && saves==save_count+1);
    assert(s_saved_preferences.value.brightness==100 && s_preference_feedback=="APPLIED / SAVED");
    gesture(false,Gesture::CLICK);gesture(true,Gesture::CLICK); // 100 -> draft 10.
    assert(s_display.brightness==panel_value(10));
    gesture(true,Gesture::LONG);
    assert(s_saved_preferences.value.brightness==100 && s_ui.preferences.brightness==100);
    assert(s_display.brightness==255 && saves==save_count+1 && s_ui.menu==Menu::Device);
    s_ui.menu=Menu::Display;s_ui.index=1;gesture(false,Gesture::CLICK);
    gesture(true,Gesture::CLICK);gesture(true,Gesture::LONG);assert(s_ui.menu==Menu::Display && s_ui.draft.text_size==s_ui.preferences.text_size);
    s_models={{"model","high",{"low","high"}}};s_settings_phase="EFFORT";
    gesture(true,Gesture::LONG);assert(s_settings_phase=="MODEL");
    gesture(true,Gesture::LONG);assert(s_settings_phase=="MENU" && s_ui.menu==Menu::Agent && s_settings_id.empty());
    for(const char* phase:{"MODEL UPDATED","UPDATE FAILED"}) {
        s_settings_phase=phase;gesture(true,Gesture::LONG);assert(s_settings_phase=="MENU" && s_ui.menu==Menu::Agent);
    }
    for(const char* phase:{"LOADING","APPLYING","SAVING SPEECH"}) {
        s_settings_phase=phase;s_settings_id="pending";const auto requests=remote_requests;
        gesture(true,Gesture::LONG);assert(s_settings_phase==phase && s_settings_id=="pending" && remote_requests==requests);
    }
    s_settings_phase="SPEECH";s_speech_menu.load("OFF");s_speech_menu.open_speech();s_speech_menu.click(false);
    gesture(true,Gesture::CLICK);assert(s_speech_menu.mode==1);
    gesture(true,Gesture::LONG);assert(s_speech_menu.page==SpeechMenu::Page::SPEECH && s_speech_menu.mode==0);
    gesture(true,Gesture::LONG);assert(s_settings_phase=="MENU" && s_ui.menu==Menu::Agent);
    s_settings_phase="SPEECH";s_speech_menu.open();gesture(true,Gesture::LONG);assert(s_ui.menu==Menu::Agent);
    gesture(false,Gesture::LONG);assert(s_input_modal==InputModal::NONE && record_start==0);
    // Navigation never alters current Agent. Voice uses the existing capture path.
    s_cloud_connected=true;gesture(true,Gesture::LONG);assert(record_start==1 && s_input_modal==InputModal::VOICE);
    const auto app=s_ui.app;board_previous_page();assert(s_ui.app==app);
    gesture(true,Gesture::LONG_RELEASE);assert(record_stop==1 && !record_cancelled && s_voice_phase=="TRANSCRIBING");
    s_voice_phase="AWAITING_CONFIRMATION";gesture(true,Gesture::CLICK);assert(voice_send==1 && s_voice_phase=="SENDING" && !s_page_click);
    cancel_input(false,"host");s_input_modal=InputModal::VOICE;s_voice_phase="AWAITING_CONFIRMATION";
    gesture(false,Gesture::CLICK);assert(voice_cancel==1 && s_input_modal==InputModal::NONE);
    // Approval owns A/B regardless of the backing app and suppresses long presses.
    s_approval.receive({"approval-a","pi","command","details",0},1000);
    gesture(true,Gesture::LONG);assert(record_start==1);
    gesture(true,Gesture::CLICK);assert(s_approval_action.id=="approval-a" && s_approval_action.approve);
    s_approval.disconnect();s_approval.receive({"approval-b","pi","command","details",0},1000);
    gesture(false,Gesture::CLICK);assert(s_approval_action.id=="approval-b" && !s_approval_action.approve);
    s_approval.disconnect();
    const auto epoch=s_ui_epoch+s_approval.generation;
    handle_gesture(false,Gesture::CLICK,epoch-1,1000);assert(s_page_click==BOARD_BUTTON_NONE);
    raw_settings_regression();
    std::puts("Production shell gestures / offline settings / Apply failure / Voice / Approval modal priority PASS (host)");
}
