#pragma once
#include "ui_state.h"
#include "typography.h"
#include "agent_display.h"
#include "device_status.h"
#include "monitor_view.h"
#include "watch_status.h"
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>

namespace wearable {
struct ShellView {
    // Borrowed only for the synchronous Agent draw; no retained text/layout heap.
    const std::vector<std::string>* agent_lines=nullptr;
    bool selected=false,wifi=false,gateway=false;
    const char *agent="",*project="",*session="",*model="",*effort="",*state="IDLE";
    const char *activity_type="",*activity="",*recent="",*final_text=nullptr;
    const char *time="--:--",*date="Time unavailable",*battery="BAT --",*firmware="",*wifi_detail="",*build_detail="";
    const char *save_feedback="";
    const char *battery_detail="Battery --";
    uint64_t elapsed=0,now_ms=0,last_update_ms=0;
    int rssi=-127;
    // Watch glance rows. Labels are fixed; values are already derived text.
    const char *host_state="Offline";
    const char *task_state="None";
    const char *codex_percent="--",*codex_reset="";
    bool battery_valid=false;
    uint8_t battery_percent=0;
    ChargeState battery_charge=ChargeState::Discharging;
    // Settings > Network status text and the deduped current SSID.
    const char *wifi_state="OFFLINE";
    const char *wifi_ssid_detail="";
    // Wi-Fi Setup provisioning screen. All values are safe to display; the AP
    // password is a fixed provisioning constant, not a device credential.
    bool provisioning=false;
    const char *provision_ssid="";
    const char *provision_password="";
    const char *provision_address="";
    const char *provision_status="";
    // QR module grid is supplied by the firmware after local encoding.
    const bool *provision_qr_modules=nullptr;
    int provision_qr_size=0;
    TaskTone host_tone=TaskTone::Secondary,task_tone=TaskTone::Secondary;
    bool codex_valid=false;
    // Agent manual browse: when manual is true the body shows page page_index
    // instead of the live tail. page_count 0/1 means only one page exists.
    bool manual=false;
    size_t page_index=0, page_count=1;
};

template<class Gfx,class Fonts> class ShellRenderer {
    Gfx& g;const Fonts& fonts;const UiState& ui;const ShellView& v;Palette p;
    void setup(TypeRole role,uint16_t color,uint16_t background=0) {
        fonts.apply(g,role,ui.preferences.text_size,color,background);
    }
    static size_t character_size(const char* value,size_t remain) {
        const auto first=uint8_t(value[0]);
        const size_t wanted=(first&0xf8)==0xf0?4:(first&0xf0)==0xe0?3:(first&0xe0)==0xc0?2:1;
        return std::min(remain,wanted);
    }
    std::string fit(const char* value,int width) {
        std::string text=value?value:"";
        if(g.textWidth(text.c_str())<=width) return text;
        std::string out;
        for(size_t i=0;i<text.size();) {
            const size_t n=character_size(text.data()+i,text.size()-i);
            auto next=out+text.substr(i,n);
            if(g.textWidth((next+"...").c_str())>width) break;
            out=next;i+=n;
        }
        return out+"...";
    }
    void center(const char* text,int y,int width=304) {
        auto clipped=fit(text,width);g.drawString(clipped.c_str(),234-g.textWidth(clipped.c_str())/2,y);
    }
    std::vector<std::string> wrap(const char* value,int width) { return wrap_lines(g,value,width); }
    // Wrapping is shared with the host-visible pagination helper below so the
    // page count the UI selects and the page the renderer draws are computed by
    // exactly one implementation (same font, same width, same rules).
    static std::vector<std::string> wrap_lines(Gfx& gfx,const char* value,int width) {
        std::vector<std::string> lines;std::string current;
        if(!value) return lines;
        const size_t length=std::strlen(value);
        for(size_t i=0;i<length;) {
            if(value[i]=='\n') {lines.push_back(current);current.clear();++i;continue;}
            const size_t n=character_size(value+i,length-i);
            auto next=current+std::string(value+i,n);
            if(!current.empty() && gfx.textWidth(next.c_str())>width) {lines.push_back(current);current.assign(value+i,n);}
            else current=next;
            i+=n;
        }
        if(!current.empty()) lines.push_back(current);
        return lines;
    }
public:
    // The single source of truth for Agent body pagination. shell_view() calls
    // this with the same font the renderer will use, so AgentBrowse's page count
    // and boundaries cannot drift from what body() actually paints.
    static constexpr int kAgentBodyTop=154,kAgentBodyBottom=404;
    // One page of body text, as a half-open line range [start, end) of `lines`.
    // Page 0 is the newest page and equals the live tail exactly.
    struct BodyRange { size_t start=0,end=0; };
    // The one pagination rule, anchored at the END of the body so that every page
    // is measured backwards from the newest line. A head-anchored rule
    // (page*rows) makes page 0 the OLDEST block and makes the first Older page
    // overlap the live tail whenever line_count % rows != 0:
    //
    //   end   = N - page*rows
    //   start = max(0, end - rows)
    //
    // The oldest page may therefore be short, which is expected: pages are never
    // re-aligned to the head to make page 0 full. Both body() and the host-visible
    // layout use this single function, so live and manual cannot diverge.
    static BodyRange body_range(size_t line_count,size_t rows,size_t pages,size_t page) {
        const size_t clamped=std::min(page,pages?pages-1:0);
        const size_t end=line_count>clamped*rows?line_count-clamped*rows:0;
        return {end>rows?end-rows:0,end};
    }
    static size_t page_count(size_t line_count,size_t rows) {
        return std::max<size_t>(1,(line_count+rows-1)/rows);
    }
    struct BodyLayout {
        std::vector<std::string> lines;
        size_t rows=1,pages=1;
        BodyRange range(size_t page) const { return body_range(lines.size(),rows,pages,page); }
    };
    static BodyLayout agent_body_layout(Gfx& gfx,const Fonts& fonts,const UiState& ui,const char* text) {
        fonts.apply(gfx,TypeRole::AgentBody,ui.preferences.text_size,palette(ui.preferences.theme).primary,0);
        BodyLayout layout;
        layout.lines=wrap_lines(gfx,text,288);
        const int line_height=gfx.fontHeight()+2;
        layout.rows=std::max<size_t>(1,size_t(kAgentBodyBottom-kAgentBodyTop)/size_t(line_height));
        layout.pages=page_count(layout.lines.size(),layout.rows);
        return layout;
    }
private:
    uint16_t state_color(const char* state=nullptr) const {
        state=state?state:v.state;
        return !std::strcmp(state,"RUNNING")?p.accent:!std::strcmp(state,"DONE")?p.done:
            !std::strcmp(state,"FAILED")?p.failed:(!std::strcmp(state,"WAITING_APPROVAL") || !std::strcmp(state,"WAITING_INPUT"))?p.approval:p.secondary;
    }
    std::string duration(bool with_state=false) const {
        char text[48];
        if(with_state) std::snprintf(text,sizeof(text),"%s  %02llu:%02llu",v.state,
            (unsigned long long)(v.elapsed/60),(unsigned long long)(v.elapsed%60));
        else std::snprintf(text,sizeof(text),"%02llu:%02llu",
            (unsigned long long)(v.elapsed/60),(unsigned long long)(v.elapsed%60));
        return text;
    }
    void rule(int y) {g.fillRoundRect(96,y,276,2,1,p.selected);}
    void body(const char* text,int top,int bottom,bool tail=false) {
        setup(TypeRole::AgentBody,p.primary);
        // Use the identical wrapping/row/page computation the UI uses to size
        // AgentBrowse, so the selected page index is the page drawn here.
        std::vector<std::string> wrapped;
        if(!v.agent_lines)wrapped=wrap_lines(g,text,288);
        const auto& lines=v.agent_lines?*v.agent_lines:wrapped;
        const int line_height=g.fontHeight()+2;
        const size_t count=std::max(1,(bottom-top)/line_height);
        const size_t pages=page_count(lines.size(),count);
        // Live and manual are the SAME calculation: page 0 is the live tail.
        // There is no separate tail logic and page logic.
        //
        //   - manual browse owns v.page_index
        //   - an explicit tail (streaming) is page 0
        //   - the idle live view auto-advances through the same pages
        //
        // Because the range is anchored at the end of the body, page 0 is exactly
        // the last `count` lines, and page p+1 ends precisely where page p starts:
        // no overlap, no gap, and Older always moves strictly older.
        const size_t page = v.manual ? v.page_index
            : (tail ? 0 : (pages>1 ? (v.now_ms/6000)%pages : 0));
        const auto range=body_range(lines.size(),count,pages,page);
        for(size_t i=range.start;i<range.end;++i)
            g.drawString(lines[i].c_str(),90,top+int(i-range.start)*line_height);
        if(pages>1&&!tail) {
            // Report the user-facing page number in the same direction as the
            // index: 1/N is newest, N/N is oldest.
            const unsigned shown=unsigned(std::min(page,pages-1))+1u;
            const unsigned total=unsigned(pages);
            char page_text[40];std::snprintf(page_text,sizeof(page_text),"Result %u/%u",shown,total);
            setup(TypeRole::Footer,p.secondary);center(page_text,bottom+2,304);
        }
    }
    void settings_hint(const char* first) {
        setup(TypeRole::Footer,p.accent);center(first,365,300);
        setup(TypeRole::Footer,p.secondary);center("Hold A Back   Hold B Close",389,300);
        if(*v.save_feedback) center(v.save_feedback,413,220);
    }
public:
    ShellRenderer(Gfx& gfx,const Fonts& f,const UiState& state,const ShellView& view)
        :g(gfx),fonts(f),ui(state),v(view),p(palette(state.preferences.theme)) {}
    void monitor(const MonitorView& m) {
        setup(TypeRole::PageTitle,p.primary);center(m.hostname,38,250);
        setup(TypeRole::AgentBodyLarge,p.primary);
        if(!m.valid) {center("WAITING FOR DATA",205,324);return;}
        const char* labels[]={"CPU","MEM","DISK","NET"};
        const float values[]={m.cpu,m.memory,m.disk};
        for(unsigned i=0;i<4;++i) {
            const int y=128+int(i)*59-g.fontHeight()/2;
            g.drawString(labels[i],72,y);
            std::string value;
            if(i<3) {char text[24];std::snprintf(text,sizeof(text),"%.0f%%",values[i]);value=text;}
            else value=monitor_network_line(m.rx,m.tx,324-g.textWidth("NET")-14,
                [&](const char* text){return g.textWidth(text);});
            g.drawString(value.c_str(),396-g.textWidth(value.c_str()),y);
        }
        setup(TypeRole::Footer,p.accent);center("MONITOR",374,244);
        setup(TypeRole::Footer,p.secondary);center(v.gateway?"AGENT ONLINE":"GATEWAY RECONNECTING",404,270);
    }
    // Small watch-style battery: outline + nub, with a proportional fill.
    // Built only from existing primitives; no icon font or bitmap asset.
    // When external power is present, a drawn lightning bolt (never a Unicode
    // glyph the subset font may not carry) sits beside the icon so the user can
    // see charging at a glance. Full keeps the bolt but fills the gauge.
    void battery_indicator(int left,int center_y,int width=34,int height=17) {
        const int top=center_y-height/2;
        const int body=width-4;
        g.drawRoundRect(left,top,body,height,3,p.secondary);
        g.fillRect(left+body,center_y-3,4,6,p.secondary);
        if(v.battery_valid) {
            const int inner=body-6;
            const int filled=inner*int(v.battery_percent)/100;
            if(filled>0) g.fillRoundRect(left+3,top+3,filled,height-6,2,p.primary);
        }
    }
    // A two-triangle lightning bolt drawn with fillTriangle, scaled from the
    // center. Round-safe and independent of any glyph coverage.
    //
    // The colour is passed explicitly to every triangle: graphics primitives do
    // NOT inherit the text colour set by setup(), so relying on it made the bolt
    // invisible on hardware.
    void charging_bolt(int center_x,int center_y,uint16_t color,int half_height=11) {
        const int h=half_height;
        const int w=std::max(4,h/2);
        // Upper wedge points down-right, lower wedge points up-left; the two
        // share a short horizontal spine so the shape reads as a bolt.
        g.fillTriangle(center_x-w,center_y-h,center_x+w,center_y-h,center_x-w/2,center_y+h/6,color);
        g.fillTriangle(center_x+w,center_y+h,center_x-w,center_y+h,center_x+w/2,center_y-h/6,color);
    }
    // "[bolt][icon] 98%" or "[icon] --"; the numeric percentage always stays
    // visible. The bolt appears for Charging, Full and ExternalIdle so external
    // power is always evident, not only while current is flowing.
    void battery_row(int y) {
        setup(TypeRole::Meta,p.secondary);
        char percent[8];
        if(v.battery_valid) std::snprintf(percent,sizeof(percent),"%u%%",unsigned(v.battery_percent));
        else std::snprintf(percent,sizeof(percent),"--");
        const bool external=v.battery_charge!=ChargeState::Discharging;
        const int icon_width=34, gap=9;
        const int bolt_width=external?26:0;
        const int text_width=g.textWidth(percent);
        const int total=bolt_width+icon_width+gap+text_width;
        const int left=234-total/2;
        if(external) {
            // Charging reads as an active state (accent); Full/ExternalIdle as
            // settled (done). The colour is handed to the primitive directly.
            const uint16_t bolt_color=v.battery_charge==ChargeState::Charging?p.accent:p.done;
            charging_bolt(left+bolt_width/2,y,bolt_color);
        }
        battery_indicator(left+bolt_width,y,icon_width);
        setup(TypeRole::Meta,external?p.accent:p.secondary);
        g.drawString(percent,left+bolt_width+icon_width+gap,y-g.fontHeight()/2);
    }
    // Left label / right value rows. Label stays low-weight, value carries state.
    // All rows share both edges. The common inset is safe for the lowest row.
    void glance_row(const char* label,const char* value,int y,TaskTone tone) {
        constexpr int inset=90;
        setup(TypeRole::StatusPrimary,p.secondary);
        g.drawString(label,inset,y-g.fontHeight()/2);
        const int label_width=g.textWidth(label);
        setup(TypeRole::StatusPrimary,tone_color(tone));
        std::string shown=fit(value,468-2*inset-label_width-12);
        g.drawString(shown.c_str(),468-inset-g.textWidth(shown.c_str()),y-g.fontHeight()/2);
    }
    uint16_t tone_color(TaskTone tone) const {
        switch(tone) {
        case TaskTone::Accent:return p.accent;
        case TaskTone::Done:return p.done;
        case TaskTone::Failed:return p.failed;
        case TaskTone::Approval:return p.approval;
        case TaskTone::Secondary:return p.secondary;
        default:return p.primary;
        }
    }
    // Watch keeps "a watch first": date sits above a dominant time, and three
    // glance rows live below it. Normal Wi-Fi / "Agent online" text is gone;
    // abnormal states surface through the Host and Task rows instead.
    void watch() {
        battery_row(44);
        setup(TypeRole::WatchDate,p.secondary);center(v.date,86,300);
        setup(TypeRole::WatchTime,p.primary);center(v.time,144,340);
        std::string task=v.task_state;
        // RUNNING appends the elapsed clock when the round screen has room.
        if(!std::strcmp(v.state,"RUNNING") && v.selected && v.gateway)
            task=std::string(v.task_state)+"  "+duration(false);
        glance_row("Host",v.host_state,294,v.host_tone);
        glance_row("Task",task.c_str(),334,v.task_tone);
        std::string codex=v.codex_percent;
        if(v.codex_valid && *v.codex_reset) codex+=" "+std::string(v.codex_reset);
        glance_row("Codex",codex.c_str(),374,v.codex_valid?TaskTone::Primary:TaskTone::Secondary);
    }
    // Wi-Fi Setup provisioning screen. QR, SSID, fixed provisioning password
    // and the Portal address are all shown, because the QR alone is not a
    // sufficient entry point (docs/wifi-provisioning-plan.md section 8).
    void provisioning() {
        setup(TypeRole::PageTitle,p.primary);center("WIFI SETUP",40,300);
        if(v.provision_qr_modules && v.provision_qr_size>0) {
            constexpr int quiet=2;  // modules of quiet zone on each side
            const int modules=v.provision_qr_size+2*quiet;
            // Keep the whole symbol inside the round-safe area: the plate is
            // centred slightly above the middle so the text below stays clear
            // of the curved bottom edge.
            const int scale=std::max(2,std::min(248/modules,7));
            const int extent=modules*scale;
            const int left=234-extent/2, top=64;
            // Light plate supplies the required quiet-zone contrast.
            g.fillRoundRect(left,top,extent,extent,4,p.primary);
            for(int my=0;my<v.provision_qr_size;++my)
                for(int mx=0;mx<v.provision_qr_size;++mx)
                    if(v.provision_qr_modules[my*v.provision_qr_size+mx])
                        g.fillRect(left+(mx+quiet)*scale,top+(my+quiet)*scale,scale,scale,p.selected);
        }
        setup(TypeRole::StatusPrimary,p.accent);center(v.provision_ssid,336,300);
        setup(TypeRole::Meta,p.secondary);center("Password",356,260);
        setup(TypeRole::AgentBody,p.primary);center(v.provision_password,374,280);
        setup(TypeRole::Footer,p.secondary);center(v.provision_address,392,220);
        setup(TypeRole::Footer,p.accent);center(v.provision_status,406,180);
    }
    void agent() {
        const char* shown_state=!v.gateway?"OFFLINE":v.state;
        setup(TypeRole::PageTitle,state_color(shown_state));
        std::string heading=std::string(v.selected?v.agent:"AGENT")+"    "+
            (v.selected?(!std::strcmp(shown_state,"RUNNING")?duration(true):shown_state):"IDLE");
        center(heading.c_str(),52,240);
        setup(TypeRole::Meta,p.secondary);center(v.selected?v.session:"No session selected",80,300);
        std::string meta;
        if(v.selected) {
            meta=v.project;
            if(*v.model) meta+="  /  "+std::string(v.model);
            if(*v.effort) meta+="  /  "+std::string(v.effort);
        } else meta="Select an Agent from Admin";
        setup(TypeRole::Footer,p.secondary);center(meta.c_str(),103,310);
        rule(125);
        const auto shown=agent_display_text(v.state,v.final_text,v.activity_type,v.activity,v.recent);
        setup(TypeRole::Meta,shown.tool?p.accent:p.secondary);
        const bool last_reply=shown.terminal && (!std::strcmp(v.state,"DORMANT") || !std::strcmp(v.state,"IDLE"));
        center(last_reply?"LAST REPLY":shown.terminal?"FINAL RESPONSE":shown.tool?"TOOL ACTIVITY":"LIVE ACTIVITY",133,270);
        const char* text=shown.text;
        if(!text||!*text) text="Waiting for agent activity...";
        body(text,154,404,!shown.terminal);
        if(v.manual) {
            setup(TypeRole::Footer,p.accent);
            char hint[32];std::snprintf(hint,sizeof(hint),"BROWSE %u/%u",
                unsigned(v.page_index+1),unsigned(v.page_count));
            center(hint,368,304);
            center("SwipeDown Older   SwipeUp Newest",392,304);
        }
    }
    void help() {
        setup(TypeRole::PageTitle,p.primary);center("HELP",48,260);
        setup(TypeRole::StatusPrimary,p.accent);center("Wearable Agent Console",86,330);
        setup(TypeRole::Meta,p.secondary);center("View, control and talk to",126,310);center("your cloud AI agents.",151,310);
        rule(186);
        setup(TypeRole::AgentBody,p.primary);
        const int line=g.fontHeight()+5;int y=205;
        for(const char* value:{"A  Next page","B  Previous page","Hold A  Voice","Hold B  Settings"}) {
            center(value,y,310);y+=line;
        }
        setup(TypeRole::Footer,p.secondary);
        center("Approval: A Approve / B Reject",371,310);
        center("Voice: A Send / B Cancel",397,280);
        if(v.build_detail&&v.build_detail[0]) {
            setup(TypeRole::Meta,p.secondary);
            center(v.build_detail,176,330);
        }
    }
    void settings() {
        setup(TypeRole::PageTitle,p.primary);center("SETTINGS",48,280);
        if(ui.menu==Menu::Edit) {
            const char* title="";std::string value;
            switch(ui.field) {
            case Field::Brightness:title="Brightness";value=std::to_string(ui.draft.brightness)+"%";break;
            case Field::Volume:title="Volume";value=std::to_string(ui.draft.volume)+"%";break;
            case Field::Vibration:title="Vibration";value=ui.draft.vibration_enabled?"On":"Off";break;
            case Field::Touch:title="Touch";value=ui.draft.touch_enabled?"On":"Off";break;
            case Field::HomeScreen:title="Home Screen";value=home_name(ui.draft.home_screen);break;
            case Field::Text:title="Text Size";value=text_name(ui.draft.text_size);break;
            case Field::Theme:title="Theme";value=theme_name(ui.draft.theme);break;
            case Field::VoiceSend:title="Voice Send";value=ui.draft.voice_send_mode==VoiceSendMode::Confirm?"Confirm":"Direct";break;
            }
            setup(TypeRole::StatusSecondary,p.secondary);center(title,138);
            setup(TypeRole::AgentBodyLarge,p.accent);center(value.c_str(),205);
            setup(TypeRole::Meta,p.secondary);center(ui.field==Field::Brightness?"Live preview - B to save":"Draft - B to apply",276);
            settings_hint("A NEXT   B APPLY");return;
        }
        std::vector<std::string> rows;
        switch(ui.menu) {
        case Menu::Root:rows={"Agent","View","Device","Display","Network","System","Back"};break;
        case Menu::Agent:for(unsigned i=0;i<ui.agent_count;++i)rows.emplace_back(UiState::agent_label(ui.agent_actions[i]));break;
        case Menu::View:rows={"Current",ui.history_available?"History":"History unavailable","Back"};break;
        case Menu::Device:rows={"Brightness  "+std::to_string(ui.preferences.brightness)+"%","Volume  "+std::to_string(ui.preferences.volume)+"%",std::string("Vibration  ")+(ui.preferences.vibration_enabled?"On":"Off"),std::string("Touch  ")+(ui.preferences.touch_enabled?"On":"Off"),"Back"};break;
        case Menu::Display:rows={std::string("Home  ")+home_name(ui.preferences.home_screen),std::string("Text  ")+text_name(ui.preferences.text_size),std::string("Theme  ")+theme_name(ui.preferences.theme),"WatchFace Agent Clock","Back"};break;
        case Menu::System:{
            char wifi[64];std::snprintf(wifi,sizeof(wifi),"Wi-Fi %s  %d dBm",v.wifi?"CONNECTED":"--",v.rssi);
            rows={v.battery_detail,wifi,v.gateway?"Gateway ONLINE":"Gateway RECONNECTING",std::string("Firmware ")+v.firmware,"Back"};break;
        }
        case Menu::KnownNetworks:{
            for(unsigned i=0;i<ui.known_count;++i) rows.push_back(ui.known[i]);
            rows.push_back(ui.known_count?"Forget All":"(no known networks)");
            rows.push_back("Back");break;
        }
        case Menu::Network:{
            // Current network and status, then the two actions and Back. This
            // order is the contract the reducer in ui_state.h mirrors exactly.
            std::string current="Network  ";
            if(v.wifi_ssid_detail && *v.wifi_ssid_detail) current+=v.wifi_ssid_detail;
            else current+=v.wifi_state;
            rows={current,"Wi-Fi Setup","Known Networks","Back"};break;
        }
        case Menu::WifiSetupConfirm:{
            // Explicit confirmation for the destructive-ish "start the AP" step.
            // The reducer exposes this as its own menu, so the visible page and
            // the gesture owner always agree.
            setup(TypeRole::StatusPrimary,p.secondary);
            center("START WIFI SETUP?",138);
            setup(TypeRole::AgentBodyLarge,p.accent);
            center("B START",218);
            setup(TypeRole::AgentBodyLarge,p.secondary);
            center("A CANCEL",290);
            settings_hint("The device will host a setup network");
            return;
        }
        default:break;
        }
        size_t first=ui.index>1?ui.index-1:0;
        if(first+3>rows.size()) first=rows.size()>3?rows.size()-3:0;
        for(size_t i=first;i<std::min(first+3,rows.size());++i) {
            const int y=129+int(i-first)*72;
            if(i==ui.index) g.fillRoundRect(82,y-7,304,62,10,p.selected);
            setup(TypeRole::AgentBody,i==ui.index?p.accent:p.secondary,i==ui.index?p.selected:0);
            const auto split=rows[i].find("  ");
            if(ui.menu==Menu::System && i==0 && split!=std::string::npos && g.textWidth(rows[i].c_str())>286) {
                center(rows[i].substr(0,split).c_str(),y,286);
                setup(TypeRole::Meta,p.secondary,i==ui.index?p.selected:0);
                center(rows[i].substr(split+2).c_str(),y+33,286);
            } else center(rows[i].c_str(),y,286);
        }
        settings_hint("A NEXT   B SELECT");
        // Destructive-action confirmation is shown in place of the normal hint.
        if(ui.menu==Menu::KnownNetworks && ui.forget_confirm) {
            setup(TypeRole::Footer,p.failed);center("FORGET ALL WIFI?  A next  B confirm",365,320);
        }
    }
};
} // namespace wearable
