#pragma once
// Glance status derivation for the Watch page.
//
// Pure, host-testable helpers shared by the board renderer. They mirror the
// gateway rules in `server/device_gateway/collectors/host_state.py`; the device
// derives from its cached snapshot so it never needs a new request or timer.
#include <cstdint>
#include <cstdio>
#include <cstring>

namespace wearable {

// Keep in sync with the gateway helper.
constexpr float WATCH_BUSY_CPU_PERCENT = 70.0f;
constexpr float WATCH_BUSY_MEMORY_PERCENT = 90.0f;
constexpr float WATCH_ACTIVE_CPU_PERCENT = 20.0f;
constexpr float WATCH_HYSTERESIS_PERCENT = 5.0f;

enum class HostState : uint8_t { Offline, Idle, Active, Busy };

inline const char *host_state_label(HostState state) {
    switch (state) {
    case HostState::Busy: return "Busy";
    case HostState::Active: return "Active";
    case HostState::Idle: return "Idle";
    default: return "Offline";
    }
}

// `valid` is false while no server snapshot has ever arrived. `previous` is the
// last reported state; supplying it lowers the boundaries by the hysteresis
// band so a value hovering at a threshold cannot flip the row every 2 s.
inline HostState host_state(float cpu_percent, float memory_percent, bool valid,
                            bool gateway_online, HostState previous = HostState::Offline,
                            bool has_previous = false) {
    if (!valid || !gateway_online) return HostState::Offline;
    const float busy_cpu = previous == HostState::Busy && has_previous
        ? WATCH_BUSY_CPU_PERCENT - WATCH_HYSTERESIS_PERCENT : WATCH_BUSY_CPU_PERCENT;
    const float active_cpu = previous == HostState::Active && has_previous
        ? WATCH_ACTIVE_CPU_PERCENT - WATCH_HYSTERESIS_PERCENT : WATCH_ACTIVE_CPU_PERCENT;
    if (cpu_percent >= busy_cpu || memory_percent >= WATCH_BUSY_MEMORY_PERCENT)
        return HostState::Busy;
    if (cpu_percent >= active_cpu) return HostState::Active;
    return HostState::Idle;
}

// Agent authoritative state -> Watch "Task" value. Unknown native states fall
// back to Idle rather than inventing a new row value.
inline const char *task_state_label(const char *state, bool selected, bool gateway_online) {
    if (!gateway_online) return "Offline";
    if (!selected || state == nullptr || state[0] == '\0') return "None";
    if (!std::strcmp(state, "RUNNING")) return "Running";
    if (!std::strcmp(state, "WAITING_APPROVAL")) return "Approval";
    if (!std::strcmp(state, "WAITING_INPUT")) return "Waiting";
    if (!std::strcmp(state, "DONE")) return "Done";
    if (!std::strcmp(state, "FAILED")) return "Failed";
    if (!std::strcmp(state, "IDLE") || !std::strcmp(state, "DORMANT")) return "Idle";
    if (!std::strcmp(state, "OFFLINE")) return "Offline";
    return "Idle";
}

// Only Task label and a visible Running timer belong to the Watch. Session
// names, result text and tool activity belong to the Agent page.
inline bool watch_task_changes_display(const char *before,const char *after,
        bool before_selected,bool after_selected,bool gateway_online,
        uint64_t before_elapsed,uint64_t after_elapsed) {
    const auto *old_label=task_state_label(before,before_selected,gateway_online);
    const auto *new_label=task_state_label(after,after_selected,gateway_online);
    if(std::strcmp(old_label,new_label)) return true;
    return !std::strcmp(new_label,"Running") && before_elapsed!=after_elapsed;
}

// Per-state value color role. Kept beside the label so the palette choice is
// tested together with the mapping.
enum class TaskTone : uint8_t { Primary, Accent, Done, Failed, Approval, Secondary };

inline TaskTone task_state_tone(const char *state, bool selected, bool gateway_online) {
    if (!gateway_online || !selected) return TaskTone::Secondary;
    if (!std::strcmp(state, "RUNNING")) return TaskTone::Accent;
    if (!std::strcmp(state, "DONE")) return TaskTone::Done;
    if (!std::strcmp(state, "FAILED")) return TaskTone::Failed;
    if (!std::strcmp(state, "WAITING_APPROVAL") || !std::strcmp(state, "WAITING_INPUT"))
        return TaskTone::Approval;
    return TaskTone::Primary;
}

inline TaskTone host_state_tone(HostState state) {
    return state == HostState::Offline ? TaskTone::Secondary : TaskTone::Primary;
}

// Cached Codex quota, mirroring the snapshot fields the gateway sends.
struct CodexQuota {
    bool valid = false;
    uint8_t remaining_percent = 0;
    int64_t resets_at = 0;
    bool has_reset = false;
    bool has_window = false;
    int32_t window_minutes = 0;
};

// Current local (UTC+8) wall clock as Unix seconds, plus "today" bounds so the
// reset timestamp can be rendered as a bare time or a compact "MM/DD-HH:MM".
struct LocalClock {
    int64_t local_seconds = 0;
    bool valid = false;
};

inline bool same_local_day(int64_t a, int64_t b) {
    // Both are local (UTC+8 shifted) seconds, so integer division by 86400
    // yields the local day index directly.
    return a >= 0 && b >= 0 && a / 86400 == b / 86400;
}

// "72%" or "--" when the quota is unavailable. Never a token/context label.
inline void codex_percent_text(const CodexQuota &quota, char *out, size_t size) {
    if (quota.valid) std::snprintf(out, size, "%u%%", unsigned(quota.remaining_percent));
    else std::snprintf(out, size, "--");
}

// Reset time: bare "HH:MM" when it is today, otherwise a compact "MM/DD-HH:MM".
// Empty when the reset is missing so the row can degrade to "Codex  72%".
inline void codex_reset_text(const CodexQuota &quota, const LocalClock &now, char *out, size_t size) {
    out[0] = '\0';
    if (!quota.valid || !quota.has_reset || quota.resets_at <= 0) return;
    const int64_t reset = quota.resets_at + 8 * 3600;  // Demo UTC+8 display.
    if (now.valid && same_local_day(reset, now.local_seconds)) {
        std::snprintf(out, size, "%02d:%02d", int((reset % 86400) / 3600), int((reset % 3600) / 60));
        return;
    }
    // Days since the Unix epoch, shifted to the local (UTC+8) calendar so the
    // month/day are correct without a weekday (the watch face shows no weekday).
    int64_t days = reset / 86400;
    int month = 1, day = 1;
    static const int month_days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    int year = 1970;
    for (;;) {
        const int leap = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? 1 : 0;
        const int year_days = 365 + leap;
        if (days < year_days) break;
        days -= year_days;
        ++year;
    }
    for (month = 0; month < 12; ++month) {
        int len = month_days[month];
        if (month == 1 && (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))) len = 29;
        if (days < len) { ++month; day = int(days) + 1; break; }
        days -= len;
    }
    std::snprintf(out, size, "%02d/%02d-%02d:%02d", month, day,
                  int((reset % 86400) / 3600), int((reset % 3600) / 60));
}

// Watch only redraws when a displayed value actually changes. Comparing the
// raw snapshot is intentionally not enough: identical derived text must not
// trigger a repaint.
inline bool codex_quota_changes_display(const CodexQuota &before, const CodexQuota &after,
                                        const char *before_reset_text, const char *after_reset_text) {
    if (before.valid != after.valid) return true;
    if (!before.valid && !after.valid) return false;
    if (before.remaining_percent != after.remaining_percent) return true;
    return std::strcmp(before_reset_text ? before_reset_text : "",
                       after_reset_text ? after_reset_text : "") != 0;
}

} // namespace wearable
