#include <epictuner/sdk/read.hpp>
#include <epictuner/sdk/ui.hpp>
#include <deque>
#include <memory>
using namespace et::sdk;
namespace { std::string copy(et_string s) { return {s.data?s.data:"",s.size}; } }
struct Telemetry : Plugin {
    static constexpr auto id="org.epictuner.examples.telemetry";
    static constexpr uint64_t capabilities=ET_CAP_LOG|ET_CAP_EVENTS|ET_CAP_UI|ET_CAP_PROJECT_READ|ET_CAP_CHANNEL_READ;
    Ui ui; Reads project,channels; std::unique_ptr<Context> host;
    et_handle panel{},subscription{}; uint32_t revision=1,page=0; uint64_t generation=0,gaps=0;
    bool patching=false,connected=false,dirty=false,subscribePending=false,resubscribe=false; double lastRender=-1; int selected=0;
    std::vector<std::string> ids,units; std::deque<double> points;
    std::string status="Open a project and connect to start telemetry.",options,trace,gauge="No sample",derived;
    ReadCallback events{data,this},catalog{metadata,this},subscribed{subscribedResult,this},ignored{ignore,this};
    et_async_options patched{sizeof(et_async_options),ET_OPERATION_TIMEOUT_MS,0,0,this,patchDone};
    static et_result ET_CALL ignore(void*,const et_read_reply*) { return ET_OK; }
    static void ET_CALL patchDone(void* p,const et_completion* r) noexcept {
        try { auto& s=*static_cast<Telemetry*>(p);s.patching=false;
        if(r->code==ET_OK&&r->value.kind==ET_VALUE_U64)s.revision=std::max(s.revision,uint32_t(r->value.data.u64));
        if(s.dirty)s.render(); } catch (...) {}
    }
    void render() {
        if(!panel.slot)return;if(patching){dirty=true;return;}dirty=false;
        trace.clear();for(double v:points){if(!trace.empty())trace+='\n';trace+=std::to_string(v);}
        patching=ui.patch(panel,{Ui::text("status",ET_UI_TITLE,status),Ui::text("channel",ET_UI_OPTIONS,options),
            Ui::number("channel",ET_UI_VALUE,selected),Ui::text("picker",ET_UI_OPTIONS,options),Ui::text("host-live",ET_UI_TARGET,ids.empty()?"":ids[selected]),Ui::text("gauge",ET_UI_TITLE,gauge),Ui::text("chart",ET_UI_DATA,trace)},revision,0,&patched)==ET_OK;
    }
    void refresh() {
        if(!generation)return;
        auto r=Reads::make(ET_READ_CHANNELS,generation);r.offset=page;r.count=16;channels.request(r,catalog);
    }
    void subscribe() {
        if(subscribePending){resubscribe=true;return;}
        if(subscription.slot){auto r=Reads::make(ET_READ_UNSUBSCRIBE,generation);r.handle=subscription;channels.request(r,ignored);subscription={};}
        if(!panel.slot||ids.empty())return;
        auto r=Reads::make(ET_READ_SUBSCRIBE,generation);auto channel=string(ids[selected]);r.channels=&channel;r.channel_count=1;r.flags=ET_READ_LATEST;
        subscribePending=channels.request(r,subscribed)==ET_OK;points.clear();gaps=0;lastRender=-1;
    }
    static et_result ET_CALL subscribedResult(void* p,const et_read_reply* r) {
        auto& s=*static_cast<Telemetry*>(p);s.subscribePending=false;if(r->code==ET_OK&&r->project_generation==s.generation)s.subscription=r->handle;
        if(s.resubscribe||!s.panel.slot){s.resubscribe=false;s.subscribe();}return ET_OK;
    }
    static et_result ET_CALL metadata(void* p,const et_read_reply* r) {
        auto& s=*static_cast<Telemetry*>(p);if(r->code!=ET_OK||r->project_generation!=s.generation)return ET_OK;
        s.ids.clear();s.units.clear();s.options.clear();s.selected=0;
        for(uint32_t i=0;i<r->item_count;++i){
            if(r->items[i].flags&ET_READ_DERIVED)continue;
            auto name=copy(r->items[i].id);if(s.options.size()+name.size()+1>3500)break;
            if(!s.options.empty())s.options+='\n';s.options+=name;s.ids.push_back(name);s.units.push_back(copy(r->items[i].units));
        }
        s.subscribe();s.render();return ET_OK;
    }
    static et_result ET_CALL data(void* p,const et_read_reply* r) {
        auto& s=*static_cast<Telemetry*>(p);
        if(r->event==ET_READ_EVENT_PROJECT){
            const bool changed=s.generation!=r->project_generation;s.generation=r->project_generation;s.connected=r->flags&ET_READ_CONNECTED;
            s.status=s.connected?"Connected · waiting for samples":"Disconnected · values are stale";
            if(changed){s.subscription={};s.page=0;s.refresh();s.derived.clear();}
            s.gauge="No valid sample";s.points.clear();s.render();return ET_OK;
        }
        if(r->project_generation!=s.generation||r->handle.slot!=s.subscription.slot||!r->item_count)return ET_OK;
        s.gaps+=r->gaps;const auto& v=r->items[r->item_count-1];
        if(s.lastRender>=0&&v.timestamp-s.lastRender<0.1)return ET_OK;s.lastRender=v.timestamp;
        const bool valid=v.flags&ET_READ_VALID;
        s.gauge=valid?std::to_string(v.value)+" "+copy(v.units):"Stale / invalid";
        s.status="Timestamp "+std::to_string(v.timestamp)+" · sequence "+std::to_string(v.sequence)+" · skipped "+std::to_string(s.gaps);
        if(valid){s.points.push_back(v.value);if(s.points.size()>100)s.points.pop_front();}
        // Demonstrate a compiled derived channel with immutable source dependencies.
        if(copy(v.id)=="rpm"&&valid){
            const auto name=std::string(id)+":rpm-half";
            if(s.derived.empty()){
                auto d=Reads::make(ET_READ_DERIVE,s.generation);d.id=string(name);d.text=string("rpm");auto dep=string("rpm");d.channels=&dep;d.channel_count=1;
                s.channels.request(d,s.ignored);s.derived=name;
            }
            auto d=Reads::make(ET_READ_PUBLISH,s.generation);d.id=string(name);d.value=v.value/2;d.timestamp=v.timestamp;d.flags=ET_READ_VALID;s.channels.request(d,s.ignored);
        }
        s.render();return ET_OK;
    }
    static et_result ET_CALL input(void* p,const et_ui_event* e){
        auto& s=*static_cast<Telemetry*>(p);s.revision=e->revision;
        if(e->type==ET_UI_OPENED){s.panel=e->panel;s.refresh();}
        if(e->type==ET_UI_CLOSED){s.panel={};s.subscribe();}
        if(e->type==ET_UI_EVENT_SELECTED&&copy(e->node)=="channel"&&e->value.kind==ET_VALUE_F64){
            const auto index=int(e->value.data.f64);if(index>=0&&index<int(s.ids.size())){s.selected=index;s.subscribe();}
        }
        if(e->type==ET_UI_COMMIT&&copy(e->node)=="picker"&&e->value.kind==ET_VALUE_STRING){const auto value=copy(e->value.data.string);const auto found=std::find(s.ids.begin(),s.ids.end(),value);if(found!=s.ids.end()){s.selected=int(found-s.ids.begin());s.subscribe();s.render();}}
        if(e->type==ET_UI_CLICK){if(copy(e->node)=="next")s.page+=16;else if(copy(e->node)=="previous")s.page=s.page>=16?s.page-16:0;s.refresh();}
        return ET_OK;
    }
    et_result start(Context& c){
        host=std::make_unique<Context>(c);
        if(ui.connect(c,input,this)!=ET_OK||project.connect(c,ET_SERVICE_PROJECT)!=ET_OK||channels.connect(c,ET_SERVICE_CHANNELS)!=ET_OK)return ET_ERROR_STATE;
        project.listen(events);
        PanelBuilder b("org.epictuner.examples.telemetry.main","Live telemetry");b.definition.flags=0;b.definition.menu=b.keep("Examples/Live telemetry");
        b.add(ET_UI_COLUMN,"root");b.add(ET_UI_BANNER,"status","root").text(ET_UI_TITLE,status);
        b.add(ET_UI_SELECT,"channel","root").text(ET_UI_TITLE,"Channel").text(ET_UI_OPTIONS,"").number(ET_UI_VALUE,0);
        b.add(ET_UI_ROW,"pages","root");b.add(ET_UI_BUTTON,"previous","pages").text(ET_UI_TITLE,"Previous channels");b.add(ET_UI_BUTTON,"next","pages").text(ET_UI_TITLE,"Next channels");
        b.add(ET_UI_TABS,"views","root").number(ET_UI_VALUE,0);b.add(ET_UI_SECTION,"stream","views").text(ET_UI_TITLE,"Plugin stream");b.add(ET_UI_SECTION,"prefabs","views").text(ET_UI_TITLE,"Host prefabs");b.prefab(ET_UI_CHANNEL_PICKER,"picker",{},"prefabs").text(ET_UI_OPTIONS,"");b.prefab(ET_UI_LIVE_PANEL,"host-live","rpm","prefabs").number(ET_UI_STEP,10);
        b.add(ET_UI_LABEL,"gauge","stream").text(ET_UI_TITLE,gauge);b.add(ET_UI_CHART,"chart","stream").text(ET_UI_DATA,"").number(ET_UI_PREFERRED_HEIGHT,220);
        return ui.define(b,nullptr);
    }
};
ET_PLUGIN(Telemetry)
