#include "agent_navigator.h"
#include "approval_control.h"
#include "ui_state.h"
#include "button_gesture.h"
#include "shell_input_policy.h"
#include "cJSON.h"
#include <string>
#include <vector>
#include <cassert>
#include <atomic>
using NavPage=wearable::AgentNavigator::Page;
wearable::AgentNavigator s_nav;
std::array<wearable::BoundedText<33>,16> s_creation_efforts{};
uint8_t s_creation_effort_count=0;
bool s_existing_permissions=false;
wearable::UiState s_ui;
ApprovalControl s_approval;
std::atomic<bool> s_approval_pending_hint{false};
InputModal s_input_modal=InputModal::SETTINGS;
std::string s_settings_phase="NAV",s_settings_id="settings",s_settings_target="agent:codex:session";
std::string s_voice_context,s_voice_id,s_voice_phase,s_modal_error;
uint64_t s_ui_epoch=1,s_voice_serial=0,s_voice_started=0;
wearable::VoiceSendMode s_voice_send_mode=wearable::VoiceSendMode::Confirm;
bool s_cloud_connected=true;
struct {char agent[9]="codex";} s_pi;
std::vector<int> s_models;
int s_page_click=0;constexpr int BOARD_BUTTON_NONE=0;
unsigned starts=0;std::vector<std::string> sent;
uint64_t esp_timer_get_time(){return 1000000;}
std::string js(cJSON* root,const char* key) {auto* v=cJSON_GetObjectItemCaseSensitive(root,key);return cJSON_IsString(v)?v->valuestring:"";}
std::string new_identity(uint64_t* serial=nullptr) {static unsigned n=0;if(serial)*serial=n+1;return std::to_string(++n);}
void queue_control(cJSON* root) {auto* wire=cJSON_PrintUnformatted(root);sent.emplace_back(wire);cJSON_free(wire);cJSON_Delete(root);}
void cancel_input(bool,const char*) {s_input_modal=InputModal::NONE;}
// Wi-Fi provisioning stubs: the Human Gate preempts provisioning. Kept
// controllable so the navigator test can assert the preemption call happens.
bool host_setup_running=false;
unsigned host_preempts=0;
bool board_network_setup_running() {return host_setup_running;}
bool board_network_candidate_active() {return false;}
void board_network_preempt_for_approval() {host_setup_running=false;++host_preempts;}
void lock_display() {}
void unlock_display() {}
void sync_display_page() {}
void stop_speech(){}
bool audio_backend_record_start(uint64_t) {++starts;return true;}
#include "navigator_input.inc"
void receive(const std::string& text) {auto* root=cJSON_Parse(text.c_str());assert(root);navigator_message(root,js(root,"type"));cJSON_Delete(root);}
std::string with_request(const std::string& content) {return std::string("{\"request_id\":\"")+s_nav.request.c_str()+"\","+content+"}";}
void click(bool a) {assert(navigator_gesture(a,Gesture::CLICK,1000));}
int main() {
    s_nav.catalog=s_nav.target_select=s_nav.create=s_nav.native=true;
    // A migrated legacy target legitimately starts at generation zero.
    // Repeated monitor snapshots are not a selection ACK and cannot close any
    // Settings/Navigator/Voice owner or force the backing app to Agent.
    s_nav.generation=0;s_ui.app=wearable::App::Watch;
    for(const auto page:{NavPage::Projects,NavPage::Sessions,NavPage::Model,NavPage::Permissions,NavPage::NativeInput}) {
        s_input_modal=InputModal::SETTINGS;s_settings_phase="NAV";s_nav.page=page;
        for(int tick=0;tick<8;++tick) {
            assert(board_accept_target("{\"target_generation\":0,\"agent\":\"pi\"}"));
            assert(s_input_modal==InputModal::SETTINGS && s_nav.page==page && s_ui.app==wearable::App::Watch);
        }
    }
    s_input_modal=InputModal::VOICE;
    assert(board_accept_target("{\"target_generation\":0,\"agent\":\"pi\"}"));assert(s_input_modal==InputModal::VOICE);
    s_input_modal=InputModal::SETTINGS;s_settings_phase="NAV";
    openAgentSessionPicker();assert(s_nav.busy());assert(sent.back().find("catalog_get")!=std::string::npos);
    receive(with_request("\"type\":\"catalog_page\",\"status\":\"Ready\",\"snapshot\":\"snap\",\"items\":[{\"project_id\":\"p\",\"name\":\"Project\"}]"));
    assert(s_nav.page==NavPage::Projects && s_nav.count==1);click(false);assert(s_nav.busy());
    receive(with_request("\"type\":\"catalog_page\",\"status\":\"Empty\",\"snapshot\":\"sessions\",\"items\":[]"));
    assert(s_nav.page==NavPage::Sessions && !s_nav.count);click(false);assert(s_nav.page==NavPage::Agent);
    click(true);click(false);assert(!std::strcmp(s_nav.agent.c_str(),"codex") && s_nav.busy());
    receive(with_request("\"type\":\"creation_options\",\"items\":[{\"id\":\"model\",\"name\":\"Model\",\"available_efforts\":[\"low\",\"medium\",\"high\",\"xhigh\",\"max\",\"ultra\"]}]"));
    click(false);assert(s_nav.page==NavPage::Effort && s_nav.count==4 && s_nav.next_cursor==4);
    for(int i=0;i<4;++i)click(true);
    click(false);assert(s_nav.count==2 && !std::strcmp(s_nav.items[1].id.c_str(),"ultra"));
    click(true);click(false);assert(s_nav.page==NavPage::Permissions);click(true);click(false);assert(s_nav.page==NavPage::FirstMessage);
    navigator_gesture(true,Gesture::LONG,1000);assert(starts==1 && s_input_modal==InputModal::VOICE);
    auto* context=cJSON_Parse(s_voice_context.c_str());assert(js(context,"kind")=="new_session" && js(context,"project_id")=="p");
    auto* settings=cJSON_GetObjectItemCaseSensitive(context,"settings");assert(js(settings,"effort")=="ultra" && js(settings,"permissions")=="auto_review");cJSON_Delete(context);
    receive("{\"type\":\"user_input_request\",\"request_id\":\"native\",\"question_id\":\"q\",\"question\":\"Pick\",\"options\":[\"A\",\"B\",\"C\"]}");
    assert(s_nav.page==NavPage::NativeInput && s_nav.option_count==3);click(true);click(false);
    auto* answer=cJSON_Parse(sent.back().c_str());assert(js(answer,"type")=="user_input_response" && cJSON_GetObjectItemCaseSensitive(answer,"choice_index")->valueint==1);cJSON_Delete(answer);
    receive("{\"type\":\"user_input_request\",\"request_id\":\"secret\",\"question_id\":\"q\",\"question\":\"Answer on desktop\",\"secret\":true,\"free_text\":false}");
    navigator_gesture(true,Gesture::LONG,1000);assert(starts==1);
    receive("{\"type\":\"user_input_request\",\"request_id\":\"free\",\"question_id\":\"q\",\"question\":\"Explain\",\"free_text\":true}");
    navigator_gesture(true,Gesture::LONG,1000);assert(starts==2);context=cJSON_Parse(s_voice_context.c_str());assert(js(context,"kind")=="user_input" && js(context,"request_id")=="free");cJSON_Delete(context);
    receive("{\"type\":\"user_input_cancelled\",\"request_id\":\"free\",\"question_id\":\"q\"}");assert(s_input_modal==InputModal::NONE);
    // Human Gate preemption: an approval arriving while provisioning is active
    // stops the SoftAP session (screen and buttons both belong to the Approval).
    host_setup_running=true;const auto preempts=host_preempts;
    receive("{\"type\":\"approval_request\",\"approval_id\":\"gate-1\",\"agent\":\"codex\",\"title\":\"Approve\",\"detail\":\"Run tests\",\"choices\":[\"Approve\",\"Reject\"]}");
    assert(host_preempts==preempts+1 && !host_setup_running);
    assert(s_approval.modal());
    receive("{\"type\":\"hello_ack\",\"target_generation\":8}");assert(!s_nav.catalog && !s_nav.native && s_ui.agent_count==5); // Effort from selected Codex, unsupported features hidden.
    s_nav.target_select=true;s_nav.generation=8;
    assert(!board_accept_target("{\"target_generation\":7,\"agent\":\"codex\"}"));
    assert(board_accept_target("{\"target_generation\":8,\"agent\":\"codex\"}"));
    s_input_modal=InputModal::VOICE;
    assert(board_accept_target("{\"target_generation\":9,\"agent\":\"codex\"}"));
    assert(s_input_modal==InputModal::NONE && s_nav.generation==9);
    s_input_modal=InputModal::SETTINGS;s_ui.app=wearable::App::Watch;
    // A real ACK closes the picker only after its exact-generation snapshot,
    // once; later equal-generation snapshots preserve newly opened Settings.
    receive(with_request("\"type\":\"target_selected\",\"target_generation\":10"));assert(s_nav.ack_pending);
    assert(board_accept_target("{\"target_generation\":9,\"agent\":\"codex\"}"));assert(s_input_modal==InputModal::SETTINGS);
    assert(board_accept_target("{\"target_generation\":10,\"agent\":\"codex\"}"));assert(s_input_modal==InputModal::NONE && s_ui.app==wearable::App::Agent && !s_nav.ack_pending);
    s_input_modal=InputModal::SETTINGS;s_ui.app=wearable::App::Watch;
    assert(board_accept_target("{\"target_generation\":10,\"agent\":\"codex\"}"));assert(s_input_modal==InputModal::SETTINGS && s_ui.app==wearable::App::Watch);
    receive("{\"type\":\"target_selected\",\"request_id\":\"stale\",\"target_generation\":11}");assert(!s_nav.ack_pending);
    std::puts("Production Navigator JSON/Project/New Session/all Efforts/native choice/free Voice/secret/old Gateway PASS");
}
