#include "board.h"
#include "wearable_renderer.h"
#ifdef HAS_FINAL_TEXT
#include "agent_display.h"
#endif
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <cstdarg>
#include <string>
#include <vector>

[[maybe_unused]] constexpr uint16_t TFT_WHITE=1, TFT_BLACK=2, TFT_CYAN=3, TFT_GREEN=4, TFT_RED=5, TFT_ORANGE=6;
namespace textdatum_t { enum { middle_center, middle_left, middle_right, top_center, top_left }; }
// The real default GLCD Font0 has only 8-bit codepoints. CN/TW glyph coverage is
// checked separately against the linked ELF by transcript_glyph_test.py.
struct Font { bool chinese; } default_font{false}, s_transcript_font{true};
struct Display {
    struct Draw { std::string text; bool chinese; };
    const Font *font=&default_font;
    std::vector<Draw> drawn;
    int width() { return 468; }
    void setTextDatum(int) {}
    void setTextColor(int, int) {}
    void setTextSize(float) {}
    int fontHeight() {return 16;}
    void fillRoundRect(int,int,int,int,int,int) {}
    void drawFastHLine(int, int, int, int) {}
    const Font *getFont() { return font; }
    void setFont(const Font *value) { font=value; }
    void drawString(const char *value, int, int) { drawn.push_back({value,font->chinese}); }
    int textWidth(const char *value) {
        int width=0;
        for (auto *p=value;*p;++p) {
            const auto c=static_cast<unsigned char>(*p);
            if ((c&0xc0)!=0x80) width+=c<128?8:(font->chinese?16:6);
        }
        return width;
    }
} s_display;
struct Fonts {
    template<class Gfx> void apply(Gfx& gfx,wearable::TypeRole,wearable::TextSize,int,int=0) const {
        gfx.setFont(&s_transcript_font);
    }
} s_fonts;
#include "agent_state.inc"
PiState s_pi;
constexpr size_t PI_LOCAL_TAIL_BYTES=6*1024;
char tail_storage[PI_LOCAL_TAIL_BYTES+1];
char *s_pi_tail=tail_storage;
size_t s_pi_tail_length=0;
bool s_pi_redraw_pending=false;
constexpr size_t PI_RENDER_SOURCE_BYTES=1400;
enum class Page { PI, CPU, MEMORY, DISK, NETWORK, OVERVIEW };
Page s_page=Page::PI;
enum class InputModal { NONE };
InputModal s_input_modal=InputModal::NONE;
const char *page_name(Page) { return "PI"; }
const char *modal_name(InputModal) { return "NONE"; }
void lock_display() {}
void unlock_display() {}
size_t strlcpy(char *out, const char *value, size_t size) {
    const auto length=std::strlen(value);
    if (size) { const auto copied=std::min(length,size-1); std::memcpy(out,value,copied);out[copied]=0; }
    return length;
}
constexpr const char *TAG="test";
std::string last_log;
void ESP_LOGI(const char *, const char *format, ...) {
    char message[1024]; va_list args; va_start(args,format);
    std::vsnprintf(message,sizeof(message),format,args); va_end(args);last_log=message;
}
struct { double cpu_percent=0, load_1m=0, load_5m=0, memory_percent=0, swap_percent=0,
                disk_percent=0, network_rx_bps=0, network_tx_bps=0; } s_server;
int64_t esp_timer_get_time() { return 5000000; }
void draw_header(const char *) {}
void draw_shell_app() {
    wearable::UiState ui;ui.preferences.text_size=wearable::TextSize::Compact;
    wearable::ShellView view;
    view.selected=s_pi.selected;view.state=s_pi.state;view.agent=s_pi.agent;
    view.gateway=true;
    view.project=view.session=s_pi.name;view.activity_type=s_pi.activity_type;
    view.activity=s_pi.latest_activity;view.recent=s_pi_tail;
    view.final_text=s_pi.has_final_text?s_pi.final_text:nullptr;
    wearable::ShellRenderer<Display,Fonts>(s_display,s_fonts,ui,view).agent();
}
#include "agent_view.inc"

unsigned failures=0;
#define CHECK(value) do { if (!(value)) { std::fprintf(stderr,"FAIL line %d: %s\n",__LINE__,#value); ++failures; } } while (false)
bool drawn(const std::string &needle, bool chinese=false) {
    for (const auto &line:s_display.drawn)
        if (line.text.find(needle)!=std::string::npos && (!chinese || line.chinese)) return true;
    return false;
}
int main() {
    s_page=Page::OVERVIEW;s_server.cpu_percent=18;s_server.memory_percent=53;s_server.disk_percent=72;
    s_server.network_rx_bps=10000;s_server.network_tx_bps=7000;
    const auto monitor_text=read_screen_text();
    for(const char* part:{"CPU 18", "Memory 53", "Disk 72", "receive 10000", "Send 7000"})
        CHECK(monitor_text.find(part)!=std::string::npos);
    s_page=Page::PI;
    s_pi.selected=true;
    std::strcpy(s_pi.state,"RUNNING");
    std::strcpy(s_pi.activity_type,"text");
    set_pi_tail("正在检查中文。");
    draw_pi();
    CHECK(drawn("RUNNING"));
    CHECK(drawn("正在检查",true));
    CHECK(s_display.getFont()==&default_font);
    CHECK(read_screen_text().find("正在检查")!=std::string::npos);
    std::strcpy(s_pi.session_id,"selected-session");
    board_agent_activity("codex","selected-session","text","wrong agent",nullptr);
    CHECK(std::string(s_pi_tail)=="正在检查中文。" && !s_pi_redraw_pending);
    CHECK(last_log.find("accepted=0")!=std::string::npos);
    board_agent_activity("pi","other-session","text","wrong session",nullptr);
    CHECK(std::string(s_pi_tail)=="正在检查中文。" && !s_pi_redraw_pending);
    board_agent_activity("pi","selected-session","text","已接收。",nullptr);
    CHECK(std::string(s_pi_tail).find("已接收。")!=std::string::npos && s_pi_redraw_pending);
    CHECK(last_log.find("accepted=1")!=std::string::npos && last_log.find("已接收")==std::string::npos);
    s_display.drawn.clear();
    std::strcpy(s_pi.activity_type,"tool");
    std::strcpy(s_pi.latest_activity,"Running\nsleep 3");
    draw_pi(); CHECK(drawn("TOOL") && drawn("sleep 3"));
#ifdef HAS_FINAL_TEXT
    s_pi.has_final_text=true;
    copy_agent_final(s_pi.final_text,"你好，检查已经完成。");
    std::strcpy(s_pi.state,"DONE");
    s_display.drawn.clear(); draw_pi();
    CHECK(drawn("你好",true) && !drawn("sleep 3") && !drawn("TOOL"));
    CHECK(read_screen_text().find("你好")!=std::string::npos);
    CHECK(read_screen_text().find("sleep 3")==std::string::npos);
    std::strcpy(s_pi.state,"FAILED");
    copy_agent_final(s_pi.final_text,"依赖不可用，尚未完成。");
    s_display.drawn.clear(); draw_pi();
    CHECK(drawn("FAILED") && drawn("依赖不可用",true));
    copy_agent_final(s_pi.final_text,"");
    s_display.drawn.clear(); draw_pi();
    CHECK(!drawn("sleep 3") && drawn("No final response"));
    // Old Gateway payloads without final_text retain the legacy activity view.
    s_pi.has_final_text=false;
    s_display.drawn.clear(); draw_pi(); CHECK(drawn("sleep 3"));
    s_pi.has_final_text=true;
    std::strcpy(s_pi.state,"RUNNING");
    copy_agent_final(s_pi.final_text,"old final");
    s_display.drawn.clear(); draw_pi(); CHECK(drawn("sleep 3") && !drawn("old final"));
    for (const char* state:{"DORMANT","IDLE"}) {
        std::strcpy(s_pi.state,state);
        copy_agent_final(s_pi.final_text,"上次回复。");
        s_display.drawn.clear(); draw_pi();
        CHECK(drawn(state) && drawn("上次回复",true) && !drawn("sleep 3"));
        CHECK(read_screen_text().find("上次回复")!=std::string::npos);
    }
    char bounded[8]; copy_agent_final(bounded,"你好🙂"); CHECK(std::string(bounded)=="你好");
    board_pi_snapshot_t next{};
    next.agent="pi"; next.session_id="unknown"; next.state="DONE"; next.activity_type="tool";
    next.latest_activity="same tool"; next.final_text="authoritative reply";
    // Final-only updates must invalidate a view even while latest_activity is unchanged.
    std::strcpy(s_pi.state,"DONE"); std::strcpy(s_pi.name,"unknown");
    std::strcpy(s_pi.latest_activity,"same tool"); copy_agent_final(s_pi.final_text,next.final_text);
    CHECK(!pi_snapshot_changes_view(next));
    next.final_text="new authoritative reply"; CHECK(pi_snapshot_changes_view(next));
#else
    CHECK(false && "board snapshot has no explicit final_text");
#endif
    if (!failures) std::puts("Agent render / Read Screen / UTF-8 / redraw selection PASS (host only)");
    return failures?1:0;
}
