/*
 * main.cpp
 * MontaukOS Audio Control
 *
 * Built with the Montauk Toolkit (mtk):
 *   1. Master output card (large %, slider, mute button)
 *   2. Per-application mixer rows (name, slider, mute)
 *
 * Copyright (c) 2026 Daniel Hammer
 */

#include <montauk/syscall.h>
#include <montauk/string.h>
#include <gui/mtk.hpp>
#include <gui/mtk/settings.hpp>
#include <gui/standalone.hpp>

extern "C" {
#include <stdio.h>
}

using namespace gui;

// ============================================================================
// Layout constants (in MTK theme units)
// ============================================================================

static constexpr int WIN_W = 380;
static constexpr int WIN_H = 460;

static constexpr int PAD             = 22;
static constexpr int KNOB_R          = 8;
static constexpr int SLIDER_TRACK_H  = 6;
static constexpr int MUTE_BTN_W      = 32;
static constexpr int MASTER_BUTTON_W = 96;

// Vertical rhythm for the master section. Tuned to give the controls room to
// breathe without floating in a card frame.
static constexpr int MASTER_TOP            = 32;
static constexpr int MASTER_SUB_GAP        = 16;  // % → "Master output"
static constexpr int MASTER_SLIDER_GAP     = 32;  // subtitle → slider
static constexpr int MASTER_BUTTON_GAP     = 36;  // slider → mute button
static constexpr int MASTER_TAIL_GAP       = 36;  // mute button → separator
static constexpr int APPS_HEADER_GAP       = 20;  // separator → "APPLICATIONS"
static constexpr int APPS_FIRST_ROW_GAP    = 16;  // header → first row

// ============================================================================
// State
// ============================================================================

static WsWindow g_win;

static int  g_master_vol = 80;
static bool g_master_muted = false;

static montauk::abi::AudioStreamInfo g_streams[8];
static int g_stream_count = 0;

// Drag target: -2 = none, -1 = master, 0..N-1 = stream slot in g_streams.
static int  g_drag_target = -2;

// Mixer serial we last synced from. Bumped by the kernel on every state
// change; the loop refreshes only when it differs from g_mixer_serial.
static uint64_t g_mixer_serial = 0;
static int g_mouse_x = -1;
static int g_mouse_y = -1;

static Color g_accent = colors::ACCENT;

// ============================================================================
// Theme
// ============================================================================

static mtk::Theme app_theme() {
    return mtk::make_theme(g_accent);
}

static void load_accent() {
    g_accent = mtk::load_system_accent();
}

// ============================================================================
// Geometry helpers
// ============================================================================

static int big_font_line_height() {
    if (fonts::system_font && fonts::system_font->valid)
        return fonts::system_font->get_line_height(fonts::LARGE_SIZE);
    return system_font_height() * 2;
}

static int master_big_y() { return MASTER_TOP; }

static int master_sub_y() {
    return master_big_y() + big_font_line_height() + MASTER_SUB_GAP;
}

static int master_slider_y() {
    return master_sub_y() + system_font_height() + MASTER_SLIDER_GAP;
}

static Rect master_slider_rect() {
    return {PAD, master_slider_y(), g_win.width - PAD * 2, SLIDER_TRACK_H};
}

static int master_button_y(const mtk::Theme& theme) {
    return master_slider_y() + KNOB_R + MASTER_BUTTON_GAP - theme.control_h / 2;
}

static Rect master_button_rect(const mtk::Theme& theme) {
    int bx = (g_win.width - MASTER_BUTTON_W) / 2;
    return {bx, master_button_y(theme), MASTER_BUTTON_W, theme.control_h};
}

static int separator_y(const mtk::Theme& theme) {
    return master_button_y(theme) + theme.control_h + MASTER_TAIL_GAP;
}

static int apps_header_y(const mtk::Theme& theme) {
    return separator_y(theme) + APPS_HEADER_GAP;
}

static int stream_list_y(const mtk::Theme& theme) {
    return apps_header_y(theme) + system_font_height() + APPS_FIRST_ROW_GAP;
}

static int stream_row_h(const mtk::Theme& theme) {
    // Label row + slider row + breathing space below.
    return system_font_height() + theme.gap_md + theme.control_h + theme.gap_lg;
}

static Rect stream_row_rect(int idx, const mtk::Theme& theme) {
    int y = stream_list_y(theme) + idx * stream_row_h(theme);
    return {PAD, y, g_win.width - PAD * 2, stream_row_h(theme)};
}

static Rect stream_slider_rect(int idx, const mtk::Theme& theme) {
    Rect row = stream_row_rect(idx, theme);
    int y = row.y + system_font_height() + theme.gap_md
          + (theme.control_h - SLIDER_TRACK_H) / 2;
    int x = row.x;
    int w = row.w - MUTE_BTN_W - theme.gap_md;
    return {x, y, w, SLIDER_TRACK_H};
}

static Rect stream_mute_rect(int idx, const mtk::Theme& theme) {
    Rect row = stream_row_rect(idx, theme);
    int y = row.y + system_font_height() + theme.gap_md;
    int x = row.x + row.w - MUTE_BTN_W;
    return {x, y, MUTE_BTN_W, theme.control_h};
}

static int vol_from_x(const Rect& slider, int mx) {
    if (slider.w <= 0) return 0;
    int v = ((mx - slider.x) * 100) / slider.w;
    if (v < 0) v = 0;
    if (v > 100) v = 100;
    return v;
}

static bool slider_hit(const Rect& slider, int mx, int my) {
    return my >= slider.y - KNOB_R && my <= slider.y + slider.h + KNOB_R &&
           mx >= slider.x - KNOB_R && mx <= slider.x + slider.w + KNOB_R;
}

static bool mouse_in_rect(const Rect& r) {
    return r.contains(g_mouse_x, g_mouse_y);
}

// ============================================================================
// Backend helpers
// ============================================================================

static void refresh_master() {
    int v = montauk::audio_get_master_volume();
    if (v >= 0) g_master_vol = v;
    g_master_muted = montauk::audio_get_master_mute() == 1;
}

static void apply_master_volume(int v) {
    if (v < 0) v = 0;
    if (v > 100) v = 100;
    if (v == g_master_vol) return;   // No change → skip syscall + HW codec hit.
    g_master_vol = v;
    montauk::audio_set_master_volume(v);
}

static void toggle_master_mute() {
    g_master_muted = !g_master_muted;
    montauk::audio_set_master_mute(g_master_muted);
}

static void refresh_streams() {
    int n = montauk::audio_list(g_streams, 8);
    g_stream_count = (n < 0) ? 0 : n;
}

static void apply_stream_volume(int idx, int v) {
    if (idx < 0 || idx >= g_stream_count) return;
    if (v < 0) v = 0;
    if (v > 100) v = 100;
    if ((uint8_t)v == g_streams[idx].volume) return;
    g_streams[idx].volume = (uint8_t)v;
    montauk::audio_set_volume(g_streams[idx].handle, v);
}

static void toggle_stream_mute(int idx) {
    if (idx < 0 || idx >= g_stream_count) return;
    bool m = !g_streams[idx].muted;
    g_streams[idx].muted = (uint8_t)(m ? 1 : 0);
    montauk::audio_set_mute(g_streams[idx].handle, m);
}

// ============================================================================
// Drawing
// ============================================================================

static void draw_slider(Canvas& c, const Rect& slider, int volume, bool muted,
                        const mtk::Theme& theme) {
    Color track = mtk::mix(theme.surface, theme.border, 80);
    Color fill = muted ? theme.danger : theme.accent;

    c.fill_rounded_rect(slider.x, slider.y, slider.w, slider.h,
                        slider.h / 2, track);

    int show = muted ? 0 : volume;
    int fw = (show * slider.w) / 100;
    if (fw > 0)
        c.fill_rounded_rect(slider.x, slider.y, fw, slider.h,
                            slider.h / 2, fill);

    int kx = slider.x + fw;
    int ky = slider.y + slider.h / 2;
    fill_circle(c, kx, ky, KNOB_R, fill);
    fill_circle(c, kx, ky, KNOB_R - 3, colors::WHITE);
}

static void draw_master(Canvas& c, const mtk::Theme& theme) {
    // Big percentage (no enclosing card).
    char buf[8];
    snprintf(buf, sizeof(buf), "%d%%", g_master_muted ? 0 : g_master_vol);
    Color vc = g_master_muted ? theme.danger : theme.accent;
    if (fonts::system_font && fonts::system_font->valid) {
        int large_w = fonts::system_font->measure_text(buf, fonts::LARGE_SIZE);
        c.text_2x((g_win.width - large_w) / 2, master_big_y(), buf, vc);
    } else {
        int bw = text_width(buf);
        c.text((g_win.width - bw) / 2, master_big_y(), buf, vc);
    }

    // Subtitle.
    const char* sub = g_master_muted ? "Master output  (muted)" : "Master output";
    int sw = text_width(sub);
    c.text((g_win.width - sw) / 2, master_sub_y(),
           sub, g_master_muted ? theme.danger : theme.text_subtle);

    // Slider.
    draw_slider(c, master_slider_rect(), g_master_vol, g_master_muted, theme);

    // Mute button. PRIMARY when muted so the recovery action stands out.
    Rect btn = master_button_rect(theme);
    mtk::WidgetState st = mtk::widget_state(false, mouse_in_rect(btn), true);
    mtk::draw_button(c, btn, g_master_muted ? "Unmute" : "Mute",
                     g_master_muted ? mtk::BUTTON_PRIMARY : mtk::BUTTON_SECONDARY,
                     st, theme);
}

static void draw_applications(Canvas& c, const mtk::Theme& theme) {
    int hy = apps_header_y(theme);
    c.text(PAD, hy, "APPLICATIONS", theme.text_muted);

    if (g_stream_count == 0) {
        int empty_y = stream_list_y(theme) + theme.gap_md;
        const char* msg = "No applications are playing audio.";
        int mw = text_width(msg);
        c.text((g_win.width - mw) / 2, empty_y, msg, theme.text_subtle);
        return;
    }

    int visible = g_stream_count;
    int rows_room = (g_win.height - stream_list_y(theme) - PAD) / stream_row_h(theme);
    if (rows_room < 1) rows_room = 1;
    if (visible > rows_room) visible = rows_room;

    for (int i = 0; i < visible; i++) {
        Rect row = stream_row_rect(i, theme);

        char label[96];
        const char* name = g_streams[i].name[0] ? g_streams[i].name : "?";
        snprintf(label, sizeof(label), "%s  (pid %d)", name,
                 g_streams[i].ownerPid);
        c.text(row.x, row.y, label, theme.text);

        char pct[8];
        snprintf(pct, sizeof(pct), "%d%%",
                 g_streams[i].muted ? 0 : g_streams[i].volume);
        int pw = text_width(pct);
        c.text(row.x + row.w - pw, row.y, pct, theme.text_muted);

        Rect slider = stream_slider_rect(i, theme);
        draw_slider(c, slider, g_streams[i].volume,
                    g_streams[i].muted != 0, theme);

        Rect mute = stream_mute_rect(i, theme);
        bool m = g_streams[i].muted != 0;
        mtk::WidgetState st = mtk::widget_state(m, mouse_in_rect(mute), true);
        mtk::draw_button(c, mute, m ? "M" : "m",
                         m ? mtk::BUTTON_DANGER : mtk::BUTTON_SECONDARY,
                         st, theme);
    }
}

static void render() {
    mtk::StandaloneHost host(&g_win);
    Canvas c = host.canvas();
    mtk::Theme theme = app_theme();

    c.fill(theme.window_bg);

    draw_master(c, theme);
    mtk::draw_separator(c, PAD, separator_y(theme),
                        g_win.width - PAD * 2, theme);
    draw_applications(c, theme);

    host.present();
}

// ============================================================================
// Input
// ============================================================================

static bool handle_mouse(int mx, int my, uint8_t buttons, uint8_t prev) {
    mtk::Theme theme = app_theme();
    bool clicked = (buttons & 1) && !(prev & 1);
    bool released = !(buttons & 1) && (prev & 1);
    bool down = (buttons & 1) != 0;

    if (clicked) {
        // Master slider.
        Rect slider = master_slider_rect();
        if (slider_hit(slider, mx, my)) {
            if (g_master_muted) toggle_master_mute();
            apply_master_volume(vol_from_x(slider, mx));
            g_drag_target = -1;
            return true;
        }
        // Master mute button.
        Rect btn = master_button_rect(theme);
        if (btn.contains(mx, my)) {
            toggle_master_mute();
            return true;
        }
        // Per-stream rows.
        for (int i = 0; i < g_stream_count; i++) {
            Rect ssl = stream_slider_rect(i, theme);
            if (slider_hit(ssl, mx, my)) {
                if (g_streams[i].muted) toggle_stream_mute(i);
                apply_stream_volume(i, vol_from_x(ssl, mx));
                g_drag_target = i;
                return true;
            }
            Rect mute = stream_mute_rect(i, theme);
            if (mute.contains(mx, my)) {
                toggle_stream_mute(i);
                return true;
            }
        }
    }

    if (g_drag_target != -2 && down) {
        if (g_drag_target == -1) {
            Rect slider = master_slider_rect();
            if (g_master_muted) toggle_master_mute();
            apply_master_volume(vol_from_x(slider, mx));
            return true;
        } else if (g_drag_target >= 0 && g_drag_target < g_stream_count) {
            Rect ssl = stream_slider_rect(g_drag_target, theme);
            if (g_streams[g_drag_target].muted) toggle_stream_mute(g_drag_target);
            apply_stream_volume(g_drag_target, vol_from_x(ssl, mx));
            return true;
        }
    }

    if (released) g_drag_target = -2;
    return false;
}

static bool handle_key(const montauk::abi::KeyEvent& key) {
    if (!key.pressed) return false;
    if (key.scancode == 0x01) {
        g_win.closed = true;
        return false;
    }
    if (key.scancode == 0x4D || key.ascii == '+' || key.ascii == '=') {
        if (g_master_muted) toggle_master_mute();
        apply_master_volume(g_master_vol + 5);
        return true;
    }
    if (key.scancode == 0x4B || key.ascii == '-') {
        if (g_master_muted) toggle_master_mute();
        apply_master_volume(g_master_vol - 5);
        return true;
    }
    if (key.ascii == 'm' || key.ascii == 'M') {
        toggle_master_mute();
        return true;
    }
    return false;
}

// ============================================================================
// Entry point
// ============================================================================

extern "C" void _start() {
    if (!fonts::init()) {
        montauk::exit(1);
    }

    load_accent();
    refresh_master();
    refresh_streams();
    g_mixer_serial = montauk::audio_wait(0, 0);

    if (!g_win.create("Audio", WIN_W, WIN_H))
        montauk::exit(1);

    render();

    while (g_win.id >= 0 && !g_win.closed) {
        montauk::abi::WinEvent ev;
        int r = g_win.poll(&ev);

        bool redraw = false;

        // Event-driven sync: the kernel bumps a mixer serial on every state
        // change. Reading it is one cheap syscall; we only re-snapshot the
        // full mixer state when the serial has actually moved. Only redraw
        // when the snapshot differs from what we already show, otherwise
        // self-driven bumps (e.g. the user dragging our own slider) would
        // cause a redraw on every drag tick.
        uint64_t now_serial = montauk::audio_wait(g_mixer_serial, 0);
        if (now_serial != g_mixer_serial) {
            g_mixer_serial = now_serial;

            // Master: only refresh when we're not dragging master ourselves,
            // otherwise a wake mid-drag could snap our knob to the kernel.
            if (g_drag_target != -1) {
                int prev_vol = g_master_vol;
                bool prev_muted = g_master_muted;
                refresh_master();
                if (g_master_vol != prev_vol || g_master_muted != prev_muted)
                    redraw = true;
            }

            // Per-stream: diff before redrawing so dragging the master slider
            // (which bumps the serial) doesn't trigger redundant repaints.
            int prev_count = g_stream_count;
            uint8_t prev_vols[8] = {0};
            uint8_t prev_mutes[8] = {0};
            int prev_handles[8] = {0};
            for (int i = 0; i < prev_count && i < 8; i++) {
                prev_vols[i]    = g_streams[i].volume;
                prev_mutes[i]   = g_streams[i].muted;
                prev_handles[i] = g_streams[i].handle;
            }
            refresh_streams();
            if (g_stream_count != prev_count) {
                redraw = true;
            } else {
                for (int i = 0; i < g_stream_count; i++) {
                    if (g_streams[i].handle != prev_handles[i] ||
                        g_streams[i].volume != prev_vols[i] ||
                        g_streams[i].muted != prev_mutes[i]) {
                        redraw = true; break;
                    }
                }
            }
        }

        if (r < 0) break;
        if (r == 0) {
            if (redraw) render();
            // Idle: block for up to 16 ms, waking early if the mixer state
            // changes. This is the only place we sleep — the next loop will
            // pick up both the change and any queued window event.
            montauk::audio_wait(g_mixer_serial, 16);
            continue;
        }

        if (ev.type == 3) break;            // close
        if (ev.type == 2 || ev.type == 4) { // resize / scale change
            redraw = true;
        } else if (ev.type == 1) {
            // Always redraw on motion so hover state tracks the cursor — the
            // window server stops forwarding events once the cursor leaves
            // our content area, so the last position before exit is near a
            // window edge (not on a button), which naturally clears hover.
            g_mouse_x = ev.mouse.x;
            g_mouse_y = ev.mouse.y;
            handle_mouse(ev.mouse.x, ev.mouse.y,
                         ev.mouse.buttons, ev.mouse.prev_buttons);
            redraw = true;
        } else if (ev.type == 0) {
            if (handle_key(ev.key)) redraw = true;
        }

        if (redraw) render();
    }

    g_win.destroy();
    montauk::exit(0);
}
