/*
    * messagebox.cpp
    * Message box dialog implementation
    * Copyright (c) 2026 Daniel Hammer
*/

#include "messagebox.hpp"
#include "main.hpp"
#include <montauk/string.h>
#include <gui/canvas.hpp>
#include <gui/font.hpp>
#include <gui/mtk/widgets.hpp>

namespace {

using namespace gui;

struct MessageButton {
    const char* label;
    uint8_t choice;
    bool primary;
};

struct MessageBoxState {
    char text[512];
    uint8_t buttons;
    uint8_t default_choice;
    int mouse_x;
    int mouse_y;
    int wrapped_width;
    int line_count;
    bool overflow;
    char lines[messagebox::MAX_LINES][messagebox::MAX_LINE_LEN];
    MessageButton button_specs[messagebox::MAX_BUTTONS];
    int button_count;
    uint8_t choice;
};

MessageBoxState g_message;

uint8_t sanitize_buttons(uint8_t buttons) {
    switch (buttons) {
        case gui::dialogs::MESSAGE_BOX_OK:
        case gui::dialogs::MESSAGE_BOX_OK_CANCEL:
        case gui::dialogs::MESSAGE_BOX_YES_NO:
        case gui::dialogs::MESSAGE_BOX_YES_NO_CANCEL:
            return buttons;
        default:
            return gui::dialogs::MESSAGE_BOX_OK;
    }
}

void add_button(MessageBoxState* st, const char* label, uint8_t choice, bool primary) {
    if (!st || st->button_count >= messagebox::MAX_BUTTONS) return;
    st->button_specs[st->button_count++] = {label, choice, primary};
}

bool has_choice(const MessageBoxState* st, uint8_t choice) {
    if (!st) return false;
    for (int i = 0; i < st->button_count; i++) {
        if (st->button_specs[i].choice == choice) return true;
    }
    return false;
}

uint8_t default_choice_for(uint8_t buttons) {
    if (buttons == gui::dialogs::MESSAGE_BOX_YES_NO ||
        buttons == gui::dialogs::MESSAGE_BOX_YES_NO_CANCEL) {
        return gui::dialogs::MESSAGE_BOX_RESULT_YES;
    }
    return gui::dialogs::MESSAGE_BOX_RESULT_OK;
}

uint8_t dismiss_choice_for(uint8_t buttons) {
    switch (buttons) {
        case gui::dialogs::MESSAGE_BOX_OK:
            return gui::dialogs::MESSAGE_BOX_RESULT_OK;
        case gui::dialogs::MESSAGE_BOX_OK_CANCEL:
        case gui::dialogs::MESSAGE_BOX_YES_NO_CANCEL:
            return gui::dialogs::MESSAGE_BOX_RESULT_CANCEL;
        case gui::dialogs::MESSAGE_BOX_YES_NO:
            return gui::dialogs::MESSAGE_BOX_RESULT_NO;
        default:
            return gui::dialogs::MESSAGE_BOX_RESULT_NONE;
    }
}

void configure_buttons(MessageBoxState* st) {
    if (!st) return;
    st->button_count = 0;
    switch (st->buttons) {
        case gui::dialogs::MESSAGE_BOX_OK_CANCEL:
            add_button(st, "OK", gui::dialogs::MESSAGE_BOX_RESULT_OK, true);
            add_button(st, "Cancel", gui::dialogs::MESSAGE_BOX_RESULT_CANCEL, false);
            break;
        case gui::dialogs::MESSAGE_BOX_YES_NO:
            add_button(st, "Yes", gui::dialogs::MESSAGE_BOX_RESULT_YES, true);
            add_button(st, "No", gui::dialogs::MESSAGE_BOX_RESULT_NO, false);
            break;
        case gui::dialogs::MESSAGE_BOX_YES_NO_CANCEL:
            add_button(st, "Yes", gui::dialogs::MESSAGE_BOX_RESULT_YES, true);
            add_button(st, "No", gui::dialogs::MESSAGE_BOX_RESULT_NO, false);
            add_button(st, "Cancel", gui::dialogs::MESSAGE_BOX_RESULT_CANCEL, false);
            break;
        case gui::dialogs::MESSAGE_BOX_OK:
        default:
            add_button(st, "OK", gui::dialogs::MESSAGE_BOX_RESULT_OK, true);
            break;
    }
}

void safe_append(char* dst, int dst_len, const char* src) {
    if (!dst || dst_len <= 0 || !src) return;
    int len = montauk::slen(dst);
    int i = 0;
    while (src[i] && len < dst_len - 1)
        dst[len++] = src[i++];
    dst[len] = '\0';
}

void fit_text_end(const char* src, char* out, int out_len, int max_px) {
    if (!out || out_len <= 0) return;
    if (!src || !src[0]) {
        out[0] = '\0';
        return;
    }
    if (text_width(src) <= max_px) {
        gui::dialogs::safe_copy(out, out_len, src);
        return;
    }

    int slen = montauk::slen(src);
    int keep = slen;
    while (keep > 1) {
        char candidate[messagebox::MAX_LINE_LEN];
        candidate[0] = '.';
        candidate[1] = '.';
        candidate[2] = '.';
        candidate[3] = '\0';
        const char* tail = src + (slen - keep);
        safe_append(candidate, sizeof(candidate), tail);
        if (text_width(candidate) <= max_px) {
            gui::dialogs::safe_copy(out, out_len, candidate);
            return;
        }
        keep--;
    }

    gui::dialogs::safe_copy(out, out_len, "...");
}

void mark_overflow(MessageBoxState* st) {
    if (!st) return;
    st->overflow = true;
    if (messagebox::MAX_LINES <= 0) return;
    if (st->line_count <= 0) st->line_count = 1;
    if (st->line_count > messagebox::MAX_LINES) st->line_count = messagebox::MAX_LINES;
    gui::dialogs::safe_copy(st->lines[st->line_count - 1], messagebox::MAX_LINE_LEN, "...");
}

void push_line(MessageBoxState* st, const char* line) {
    if (!st || st->overflow) return;
    if (st->line_count >= messagebox::MAX_LINES) {
        mark_overflow(st);
        return;
    }
    gui::dialogs::safe_copy(st->lines[st->line_count], messagebox::MAX_LINE_LEN, line ? line : "");
    st->line_count++;
}

void commit_word(MessageBoxState* st, char* current, int* current_len, const char* word, int max_px) {
    if (!st || st->overflow || !current || !current_len || !word || !word[0]) return;

    char candidate[messagebox::MAX_LINE_LEN];
    gui::dialogs::safe_copy(candidate, sizeof(candidate), current);
    if (*current_len > 0) safe_append(candidate, sizeof(candidate), " ");
    safe_append(candidate, sizeof(candidate), word);

    if (*current_len == 0 && text_width(word) > max_px) {
        char fitted[messagebox::MAX_LINE_LEN];
        fit_text_end(word, fitted, sizeof(fitted), max_px);
        push_line(st, fitted);
        current[0] = '\0';
        *current_len = 0;
        return;
    }

    if (*current_len == 0 || text_width(candidate) <= max_px) {
        gui::dialogs::safe_copy(current, messagebox::MAX_LINE_LEN, candidate);
        *current_len = montauk::slen(current);
        return;
    }

    push_line(st, current);
    if (st->overflow) return;

    current[0] = '\0';
    *current_len = 0;
    if (text_width(word) > max_px) {
        char fitted[messagebox::MAX_LINE_LEN];
        fit_text_end(word, fitted, sizeof(fitted), max_px);
        push_line(st, fitted);
    } else {
        gui::dialogs::safe_copy(current, messagebox::MAX_LINE_LEN, word);
        *current_len = montauk::slen(current);
    }
}

bool is_space(char ch) {
    return ch == ' ' || ch == '\t' || ch == '\r';
}

int clamp_int(int value, int min_value, int max_value) {
    if (value < min_value) return min_value;
    if (value > max_value) return max_value;
    return value;
}

int button_row_width(const MessageBoxState* st) {
    if (!st || st->button_count <= 0) return 0;
    int total_w = st->button_count * messagebox::BUTTON_W;
    if (st->button_count > 1)
        total_w += (st->button_count - 1) * messagebox::BUTTON_GAP;
    return total_w;
}

int widest_explicit_message_line(const MessageBoxState* st, int max_px) {
    if (!st || !st->text[0]) return 0;

    char line[messagebox::MAX_LINE_LEN] = {};
    int line_len = 0;
    int widest = 0;

    for (const char* p = st->text; ; p++) {
        char ch = *p;
        bool at_end = ch == '\0';
        if (at_end || ch == '\n') {
            line[line_len] = '\0';
            int width = text_width(line);
            if (width > widest) widest = width;
            line_len = 0;
            line[0] = '\0';
            if (at_end) break;
            continue;
        }

        if (line_len >= messagebox::MAX_LINE_LEN - 1)
            return max_px;
        line[line_len++] = ch;
    }

    return widest > max_px ? max_px : widest;
}

void wrap_message(MessageBoxState* st, int max_px) {
    if (!st) return;
    if (max_px < 40) max_px = 40;

    st->line_count = 0;
    st->overflow = false;

    char current[messagebox::MAX_LINE_LEN] = {};
    int current_len = 0;
    char word[messagebox::MAX_LINE_LEN] = {};
    int word_len = 0;

    for (const char* p = st->text; ; p++) {
        char ch = *p;
        bool at_end = ch == '\0';
        bool separator = at_end || ch == '\n' || is_space(ch);

        if (separator) {
            if (word_len > 0) {
                word[word_len] = '\0';
                commit_word(st, current, &current_len, word, max_px);
                word_len = 0;
                word[0] = '\0';
            }

            if (ch == '\n') {
                push_line(st, current);
                current[0] = '\0';
                current_len = 0;
            }

            if (at_end) break;
            if (st->overflow) break;
            continue;
        }

        if (word_len >= messagebox::MAX_LINE_LEN - 1) {
            word[word_len] = '\0';
            commit_word(st, current, &current_len, word, max_px);
            word_len = 0;
            word[0] = '\0';
        }
        if (word_len < messagebox::MAX_LINE_LEN - 1)
            word[word_len++] = ch;
    }

    if (!st->overflow && (current_len > 0 || st->line_count == 0))
        push_line(st, current);

    st->wrapped_width = max_px;
}

MessageBoxLayout message_layout(const MessageBoxState* st) {
    MessageBoxLayout lo = {};
    lo.button_count = st ? st->button_count : 0;
    lo.footer_rect = {0, g_app.win.height - messagebox::FOOTER_H, g_app.win.width, messagebox::FOOTER_H};

    lo.text_rect = {messagebox::TEXT_X, messagebox::TEXT_Y,
                    g_app.win.width - messagebox::TEXT_X * 2,
                    lo.footer_rect.y - messagebox::TEXT_Y - messagebox::TEXT_BOTTOM_GAP};

    int total_w = button_row_width(st);
    int x = g_app.win.width - messagebox::BUTTON_RIGHT_PAD - total_w;
    int y = lo.footer_rect.y + (messagebox::FOOTER_H - messagebox::BUTTON_H) / 2;
    for (int i = 0; i < lo.button_count && i < messagebox::MAX_BUTTONS; i++) {
        lo.buttons[i] = {x, y, messagebox::BUTTON_W, messagebox::BUTTON_H};
        x += messagebox::BUTTON_W + messagebox::BUTTON_GAP;
    }

    return lo;
}

mtk::Theme message_theme() {
    mtk::Theme theme = mtk::make_theme();
    theme.surface = Color::from_rgb(0xF7, 0xF7, 0xF7);
    theme.surface_hover = mtk::mix(theme.surface, theme.accent, 18);
    return theme;
}

void finish(uint8_t choice) {
    g_message.choice = choice;
    g_app.running = false;
}

void draw_message_box() {
    MessageBoxState* st = &g_message;
    Canvas c = g_app.win.canvas();
    c.fill(colors::WINDOW_BG);

    MessageBoxLayout lo = message_layout(st);
    if (st->wrapped_width != lo.text_rect.w)
        wrap_message(st, lo.text_rect.w);

    int line_h = system_font_height() + 5;
    int y = lo.text_rect.y;
    for (int i = 0; i < st->line_count; i++) {
        if (y + line_h > lo.footer_rect.y - 8) break;
        c.text(lo.text_rect.x, y, st->lines[i], colors::TEXT_COLOR);
        y += line_h;
    }

    c.fill_rect(lo.footer_rect.x, lo.footer_rect.y, lo.footer_rect.w, lo.footer_rect.h,
                Color::from_rgb(0xF7, 0xF7, 0xF7));
    c.hline(0, lo.footer_rect.y, c.w, colors::BORDER);

    mtk::Theme theme = message_theme();
    for (int i = 0; i < st->button_count && i < messagebox::MAX_BUTTONS; i++) {
        bool hovered = lo.buttons[i].contains(st->mouse_x, st->mouse_y);
        mtk::WidgetState state = mtk::widget_state(false, hovered, true);
        mtk::draw_button(c, lo.buttons[i], st->button_specs[i].label,
                         st->button_specs[i].primary ? mtk::BUTTON_PRIMARY : mtk::BUTTON_SECONDARY,
                         state, theme);
    }
}

void handle_message_mouse(const Montauk::WinEvent& ev) {
    MessageBoxState* st = &g_message;
    st->mouse_x = ev.mouse.x;
    st->mouse_y = ev.mouse.y;

    bool left_pressed = (ev.mouse.buttons & 0x01) && !(ev.mouse.prev_buttons & 0x01);
    if (!left_pressed) return;

    MessageBoxLayout lo = message_layout(st);
    for (int i = 0; i < st->button_count && i < messagebox::MAX_BUTTONS; i++) {
        if (lo.buttons[i].contains(ev.mouse.x, ev.mouse.y)) {
            finish(st->button_specs[i].choice);
            return;
        }
    }
}

void handle_message_key(const Montauk::KeyEvent& key) {
    if (!key.pressed) return;

    MessageBoxState* st = &g_message;
    if (key.scancode == 0x01) {
        finish(dismiss_choice_for(st->buttons));
        return;
    }
    if (key.ascii == '\n' || key.ascii == '\r') {
        finish(st->default_choice);
        return;
    }
    if ((key.ascii == 'o' || key.ascii == 'O') &&
        has_choice(st, gui::dialogs::MESSAGE_BOX_RESULT_OK)) {
        finish(gui::dialogs::MESSAGE_BOX_RESULT_OK);
        return;
    }
    if ((key.ascii == 'c' || key.ascii == 'C') &&
        has_choice(st, gui::dialogs::MESSAGE_BOX_RESULT_CANCEL)) {
        finish(gui::dialogs::MESSAGE_BOX_RESULT_CANCEL);
        return;
    }
    if ((key.ascii == 'y' || key.ascii == 'Y') &&
        has_choice(st, gui::dialogs::MESSAGE_BOX_RESULT_YES)) {
        finish(gui::dialogs::MESSAGE_BOX_RESULT_YES);
        return;
    }
    if ((key.ascii == 'n' || key.ascii == 'N') &&
        has_choice(st, gui::dialogs::MESSAGE_BOX_RESULT_NO)) {
        finish(gui::dialogs::MESSAGE_BOX_RESULT_NO);
    }
}

} // namespace

namespace messagebox {

void init(const char* message, uint8_t buttons, uint8_t default_choice) {
    MessageBoxState* st = &g_message;
    montauk::memset(st, 0, sizeof(*st));
    gui::dialogs::safe_copy(st->text, sizeof(st->text), message ? message : "");
    st->buttons = sanitize_buttons(buttons);
    st->default_choice = default_choice ? default_choice : default_choice_for(st->buttons);
    configure_buttons(st);
    if (!has_choice(st, st->default_choice))
        st->default_choice = default_choice_for(st->buttons);
    wrap_message(st, messagebox::MESSAGE_MIN_W - messagebox::TEXT_X * 2);
}

void preferred_size(const char* title, int* out_w, int* out_h) {
    MessageBoxState* st = &g_message;

    int max_text_w = messagebox::MESSAGE_MAX_W - messagebox::TEXT_X * 2;
    int desired_text_w = widest_explicit_message_line(st, max_text_w);
    if (title && title[0]) {
        int title_w = text_width(title) + 120;
        if (title_w > desired_text_w) desired_text_w = title_w;
    }

    int desired_w = desired_text_w + messagebox::TEXT_X * 2;
    int buttons_w = button_row_width(st) + messagebox::BUTTON_RIGHT_PAD * 2;
    if (buttons_w > desired_w) desired_w = buttons_w;
    desired_w = clamp_int(desired_w, messagebox::MESSAGE_MIN_W, messagebox::MESSAGE_MAX_W);

    wrap_message(st, desired_w - messagebox::TEXT_X * 2);

    int line_h = system_font_height() + 5;
    int text_h = st->line_count > 0 ? st->line_count * line_h : line_h;
    int desired_h = messagebox::TEXT_Y + text_h + messagebox::TEXT_BOTTOM_GAP +
                    messagebox::FOOTER_H;
    desired_h = clamp_int(desired_h, messagebox::MESSAGE_MIN_H, messagebox::MESSAGE_MAX_H);

    if (out_w) *out_w = desired_w;
    if (out_h) *out_h = desired_h;
}

void draw() {
    draw_message_box();
}

void handle_mouse(const Montauk::WinEvent& ev) {
    handle_message_mouse(ev);
}

void handle_key(const Montauk::KeyEvent& key) {
    handle_message_key(key);
}

void dismiss() {
    finish(dismiss_choice_for(g_message.buttons));
}

uint8_t selected_choice() {
    return g_message.choice;
}

void cleanup() {
}

} // namespace messagebox
