/*
    * graph.hpp
    * Montauk Toolkit graph helpers
    * Copyright (c) 2026 Daniel Hammer
*/

#pragma once

#include "gui/mtk/widgets.hpp"

namespace gui::mtk {

struct GraphHistory {
    uint64_t* samples;
    int capacity;
    int count;
    int head;
};

struct GraphStyle {
    Color background;
    Color border;
    Color plot_bg;
    Color grid;
    Color label;
    Color value;
    Color line;
    Color fill;
    int radius;
    int padding;
    int grid_x;
    int grid_y;
    int line_thickness;
    bool fill_area;
};

inline GraphStyle make_graph_style(const Theme& theme, Color line) {
    return {
        theme.window_bg,
        theme.border,
        theme.surface_alt,
        mix(theme.surface_alt, line, 18),
        theme.text_subtle,
        theme.text,
        line,
        lighten(line, 218),
        theme.radius_md,
        8,
        4,
        4,
        2,
        true,
    };
}

inline void graph_history_init(GraphHistory* history, uint64_t* samples, int capacity) {
    if (!history) return;
    history->samples = samples;
    history->capacity = capacity;
    history->count = 0;
    history->head = 0;
}

inline void graph_history_clear(GraphHistory* history) {
    if (!history) return;
    history->count = 0;
    history->head = 0;
}

inline void graph_history_push(GraphHistory* history, uint64_t value) {
    if (!history || !history->samples || history->capacity <= 0) return;
    history->samples[history->head] = value;
    history->head = (history->head + 1) % history->capacity;
    if (history->count < history->capacity)
        history->count++;
}

inline uint64_t graph_history_at(const GraphHistory& history, int index) {
    if (!history.samples || history.capacity <= 0 ||
        index < 0 || index >= history.count) {
        return 0;
    }

    int start = history.head - history.count;
    while (start < 0) start += history.capacity;
    return history.samples[(start + index) % history.capacity];
}

inline uint64_t graph_history_max(const GraphHistory& history) {
    uint64_t max_value = 0;
    for (int i = 0; i < history.count; i++) {
        uint64_t value = graph_history_at(history, i);
        if (value > max_value) max_value = value;
    }
    return max_value;
}

inline int graph_sample_x(const Rect& plot, int index, int count) {
    if (count <= 1) return plot.x + plot.w - 1;
    return plot.x + (int)(((int64_t)index * (plot.w - 1)) / (count - 1));
}

inline int graph_sample_y(const Rect& plot, uint64_t sample, uint64_t max_value) {
    if (plot.h <= 1) return plot.y;
    if (max_value == 0) max_value = 1;
    if (sample > max_value) sample = max_value;
    uint64_t scaled = (sample * (uint64_t)(plot.h - 1)) / max_value;
    return plot.y + plot.h - 1 - (int)scaled;
}

inline void graph_draw_point(Canvas& c,
                             const Rect& clip,
                             int x,
                             int y,
                             Color color,
                             int thickness) {
    thickness = gui_max(thickness, 1);
    int radius = thickness / 2;
    for (int py = y - radius; py <= y - radius + thickness - 1; py++) {
        for (int px = x - radius; px <= x - radius + thickness - 1; px++) {
            if (clip.contains(px, py))
                c.put_pixel(px, py, color);
        }
    }
}

inline void graph_draw_line(Canvas& c,
                            const Rect& clip,
                            int x0,
                            int y0,
                            int x1,
                            int y1,
                            Color color,
                            int thickness) {
    int dx = gui_abs(x1 - x0);
    int sx = x0 < x1 ? 1 : -1;
    int dy = -gui_abs(y1 - y0);
    int sy = y0 < y1 ? 1 : -1;
    int err = dx + dy;

    for (;;) {
        graph_draw_point(c, clip, x0, y0, color, thickness);
        if (x0 == x1 && y0 == y1) break;
        int e2 = 2 * err;
        if (e2 >= dy) {
            err += dy;
            x0 += sx;
        }
        if (e2 <= dx) {
            err += dx;
            y0 += sy;
        }
    }
}

inline void graph_fill_segment(Canvas& c,
                               const Rect& clip,
                               int x0,
                               int y0,
                               int x1,
                               int y1,
                               Color color) {
    int bottom = clip.y + clip.h - 1;
    if (x0 == x1) {
        int y = gui_min(y0, y1);
        if (clip.contains(x0, y))
            c.vline(x0, y, bottom - y + 1, color);
        return;
    }

    if (x1 < x0) {
        int tx = x0; x0 = x1; x1 = tx;
        int ty = y0; y0 = y1; y1 = ty;
    }

    for (int x = x0; x <= x1; x++) {
        if (x < clip.x || x >= clip.x + clip.w) continue;
        int y = y0 + (int)(((int64_t)(y1 - y0) * (x - x0)) / (x1 - x0));
        if (y < clip.y) y = clip.y;
        if (y > bottom) y = bottom;
        c.vline(x, y, bottom - y + 1, color);
    }
}

inline void draw_line_graph(Canvas& c,
                            const Rect& bounds,
                            const GraphHistory& history,
                            uint64_t max_value,
                            const char* title,
                            const char* value_label,
                            const Theme& theme,
                            const GraphStyle& style) {
    if (bounds.empty()) return;

    draw_rounded_frame(c, bounds, style.radius, style.background, style.border);

    int fh = system_font_height();
    int pad = gui_max(style.padding, 4);
    int title_y = bounds.y + pad;
    int title_x = bounds.x + pad;

    if (title && title[0])
        c.text(title_x, title_y, title, style.label);

    if (value_label && value_label[0]) {
        int value_w = text_width(value_label);
        int value_x = bounds.x + bounds.w - pad - value_w;
        if (value_x > title_x)
            c.text(value_x, title_y, value_label, style.value);
    }

    Rect plot = {
        bounds.x + pad,
        bounds.y + pad + fh + 7,
        bounds.w - pad * 2,
        bounds.h - pad * 2 - fh - 7
    };
    if (plot.empty()) return;

    c.fill_rect(plot.x, plot.y, plot.w, plot.h, style.plot_bg);

    int sample_count = history.count;
    if (sample_count > 0) {
        if (max_value == 0)
            max_value = graph_history_max(history);
        if (max_value == 0)
            max_value = 1;

        if (style.fill_area && sample_count > 1) {
            int prev_x = graph_sample_x(plot, 0, sample_count);
            int prev_y = graph_sample_y(plot, graph_history_at(history, 0), max_value);
            for (int i = 1; i < sample_count; i++) {
                int x = graph_sample_x(plot, i, sample_count);
                int y = graph_sample_y(plot, graph_history_at(history, i), max_value);
                graph_fill_segment(c, plot, prev_x, prev_y, x, y, style.fill);
                prev_x = x;
                prev_y = y;
            }
        }
    }

    if (style.grid_y > 0) {
        for (int i = 1; i < style.grid_y; i++) {
            int y = plot.y + (plot.h * i) / style.grid_y;
            c.hline(plot.x, y, plot.w, style.grid);
        }
    }

    if (style.grid_x > 0) {
        for (int i = 1; i < style.grid_x; i++) {
            int x = plot.x + (plot.w * i) / style.grid_x;
            c.vline(x, plot.y, plot.h, style.grid);
        }
    }

    c.rect(plot.x, plot.y, plot.w, plot.h, style.border);

    if (sample_count == 1) {
        int x = graph_sample_x(plot, 0, sample_count);
        int y = graph_sample_y(plot, graph_history_at(history, 0), max_value);
        graph_draw_point(c, plot, x, y, style.line, style.line_thickness + 1);
    } else if (sample_count > 1) {
        int prev_x = graph_sample_x(plot, 0, sample_count);
        int prev_y = graph_sample_y(plot, graph_history_at(history, 0), max_value);
        for (int i = 1; i < sample_count; i++) {
            int x = graph_sample_x(plot, i, sample_count);
            int y = graph_sample_y(plot, graph_history_at(history, i), max_value);
            graph_draw_line(c, plot, prev_x, prev_y, x, y,
                            style.line, style.line_thickness);
            prev_x = x;
            prev_y = y;
        }
    }

    (void)theme;
}

inline void draw_line_graph(Canvas& c,
                            const Rect& bounds,
                            const GraphHistory& history,
                            uint64_t max_value,
                            const char* title,
                            const char* value_label,
                            const Theme& theme,
                            Color line) {
    GraphStyle style = make_graph_style(theme, line);
    draw_line_graph(c, bounds, history, max_value, title, value_label, theme, style);
}

} // namespace gui::mtk
