/*
    * terminal.hpp
    * MontaukOS terminal emulator with ANSI escape sequence support
    * Copyright (c) 2026 Daniel Hammer
*/

#pragma once
#include "gui/gui.hpp"
#include "gui/font.hpp"
#include <montauk/syscall.h>
#include <montauk/string.h>
#include <Api/Syscall.hpp>

namespace gui {

struct TermCell {
    char ch;
    Color fg;
    Color bg;
};

static constexpr int TERM_MAX_SCROLLBACK = 500;
static constexpr int TERM_READ_BUF_SIZE = 8192;
static constexpr int TERM_MAX_DRAIN_BYTES_PER_POLL = 256 * 1024;
static constexpr int TERM_DRAIN_GRACE_YIELDS = 2;

struct TerminalState {
    TermCell* cells;
    TermCell* alt_cells;         // alternate screen buffer
    TermCell* render_cells;      // last visible cells painted into the window buffer
    int cols, rows;
    int cursor_x, cursor_y;
    int saved_cursor_x, saved_cursor_y;  // saved cursor for alternate screen
    int scrollback_lines;        // lines of history above visible screen (0..max_scrollback)
    int max_scrollback;          // 0 for viewers (klog), TERM_MAX_SCROLLBACK for terminal
    int view_offset;             // how many rows scrolled back (0 = live view)
    int child_pid;
    void* desktop;               // DesktopState* for closing window on child exit
    Color current_fg;
    Color current_bg;
    bool cursor_visible;
    bool alt_screen_active;
    bool reverse_video;
    bool dirty;                  // true when content changed since last render
    bool render_valid;
    bool render_cursor_visible;
    int render_cols, render_rows;
    int render_term_cols, render_term_rows;
    int render_base_row;
    int render_pw, render_ph;
    int render_cell_w, render_cell_h;
    int render_cursor_x, render_cursor_y;
    uint32_t* render_pixels;

    enum { STATE_NORMAL, STATE_ESC, STATE_CSI } parse_state;
    bool csi_private;            // true if '?' was seen after CSI
    int csi_params[8];
    int csi_param_count;
    int csi_current_param;
};

// Standard ANSI color palette as ARGB pixels
static inline Color term_ansi_color(int idx) {
    switch (idx) {
    case 0:  return Color::from_hex(0x000000);
    case 1:  return Color::from_hex(0xCC0000);
    case 2:  return Color::from_hex(0x4E9A06);
    case 3:  return Color::from_hex(0xC4A000);
    case 4:  return Color::from_hex(0x3465A4);
    case 5:  return Color::from_hex(0x75507B);
    case 6:  return Color::from_hex(0x06989A);
    case 7:  return Color::from_hex(0xD3D7CF);
    case 8:  return Color::from_hex(0x555753);
    case 9:  return Color::from_hex(0xEF2929);
    case 10: return Color::from_hex(0x8AE234);
    case 11: return Color::from_hex(0xFCE94F);
    case 12: return Color::from_hex(0x729FCF);
    case 13: return Color::from_hex(0xAD7FA8);
    case 14: return Color::from_hex(0x34E2E2);
    case 15: return Color::from_hex(0xEEEEEC);
    default: return colors::TERM_FG;
    }
}

// ANSI 256-color palette (0-15 = standard, 16-231 = RGB cube, 232-255 = grayscale)
static inline Color term_ansi_256_color(int idx) {
    if (idx < 0) return colors::TERM_FG;
    if (idx <= 15) return term_ansi_color(idx);
    if (idx <= 231) {
        // 6x6x6 RGB cube: idx 16 = (0,0,0), idx 231 = (5,5,5)
        int r = ((idx - 16) / 36) * 51;
        int g = (((idx - 16) % 36) / 6) * 51;
        int b = ((idx - 16) % 6) * 51;
        return Color::from_rgb(r, g, b);
    }
    // Grayscale: idx 232 = #080808, idx 255 = #eeeeee
    int gray = 8 + (idx - 232) * 10;
    if (gray > 238) gray = 238;
    return Color::from_rgb(gray, gray, gray);
}

// Helper: pointer to start of screen-relative row r in the cells buffer
static inline TermCell* term_screen_row(TerminalState* t, int r) {
    return &t->cells[(t->scrollback_lines + r) * t->cols];
}

static inline bool term_color_equal(Color a, Color b) {
    return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a;
}

static inline bool term_cell_equal(const TermCell& a, const TermCell& b) {
    return a.ch == b.ch && term_color_equal(a.fg, b.fg) && term_color_equal(a.bg, b.bg);
}

static inline void terminal_invalidate_render_cache(TerminalState* t) {
    if (!t) return;
    t->render_valid = false;
    t->render_cursor_visible = false;
}

static inline Color terminal_erase_bg(TerminalState* t) {
    return t->current_bg;
}

static inline void terminal_scroll_up(TerminalState* t) {
    if (!t->alt_screen_active && t->scrollback_lines < t->max_scrollback) {
        // Room for scrollback: top visible row becomes scrollback, no data movement
        t->scrollback_lines++;
    } else if (!t->alt_screen_active && t->max_scrollback > 0) {
        // Scrollback full: discard oldest line, shift entire buffer up by one row
        int total = (t->max_scrollback + t->rows - 1) * t->cols * sizeof(TermCell);
        montauk::memmove(t->cells, t->cells + t->cols, total);
    } else {
        // Alt screen or no scrollback (klog): shift visible area up
        TermCell* screen = term_screen_row(t, 0);
        montauk::memmove(screen, screen + t->cols, (t->rows - 1) * t->cols * sizeof(TermCell));
    }

    // Clear the new bottom visible row
    TermCell* bottom = term_screen_row(t, t->rows - 1);
    for (int c = 0; c < t->cols; c++) {
        bottom[c] = {' ', t->current_fg, terminal_erase_bg(t)};
    }

    // Keep user's scrolled-back viewport stable
    if (t->view_offset > 0) {
        t->view_offset++;
        if (t->view_offset > t->scrollback_lines)
            t->view_offset = t->scrollback_lines;
    }
}

// Initialize only the cell grid (no child process). Used by viewers like klog.
// max_sb = 0 for viewers, TERM_MAX_SCROLLBACK for the real terminal.
static inline void terminal_init_cells(TerminalState* t, int cols, int rows, int max_sb = 0) {
    t->cols = cols;
    t->rows = rows;
    t->cursor_x = 0;
    t->cursor_y = 0;
    t->saved_cursor_x = 0;
    t->saved_cursor_y = 0;
    t->scrollback_lines = 0;
    t->max_scrollback = max_sb;
    t->view_offset = 0;
    t->current_fg = colors::TERM_FG;
    t->current_bg = colors::TERM_BG;
    t->cursor_visible = false;
    t->alt_screen_active = false;
    t->reverse_video = false;
    t->dirty = true;
    t->render_cells = nullptr;
    t->render_valid = false;
    t->render_cursor_visible = false;
    t->render_cols = 0;
    t->render_rows = 0;
    t->render_term_cols = 0;
    t->render_term_rows = 0;
    t->render_base_row = 0;
    t->render_pw = 0;
    t->render_ph = 0;
    t->render_cell_w = 0;
    t->render_cell_h = 0;
    t->render_cursor_x = 0;
    t->render_cursor_y = 0;
    t->render_pixels = nullptr;
    t->parse_state = TerminalState::STATE_NORMAL;
    t->csi_private = false;
    t->csi_param_count = 0;
    t->csi_current_param = 0;
    t->child_pid = 0;

    int total_cells = (rows + max_sb) * cols;
    int screen_cells = rows * cols;
    t->cells = (TermCell*)montauk::alloc(total_cells * sizeof(TermCell));
    t->alt_cells = (TermCell*)montauk::alloc(screen_cells * sizeof(TermCell));
    if (!t->cells || !t->alt_cells) {
        // Allocation failed — leave terminal in a safe but unusable state
        if (t->cells) { montauk::free(t->cells); t->cells = nullptr; }
        if (t->alt_cells) { montauk::free(t->alt_cells); t->alt_cells = nullptr; }
        t->cols = 0; t->rows = 0;
        return;
    }
    for (int i = 0; i < total_cells; i++) {
        t->cells[i] = {' ', colors::TERM_FG, colors::TERM_BG};
    }
    for (int i = 0; i < screen_cells; i++) {
        t->alt_cells[i] = {' ', colors::TERM_FG, colors::TERM_BG};
    }
}

static inline void terminal_init(TerminalState* t, int cols, int rows) {
    terminal_init_cells(t, cols, rows, TERM_MAX_SCROLLBACK);
    t->cursor_visible = true;

    t->child_pid = montauk::spawn_redir("0:/os/shell.elf");
    if (t->child_pid > 0)
        montauk::childio_settermsz(t->child_pid, cols, rows);
}

static inline void terminal_put_char(TerminalState* t, char ch) {
    if (t->cursor_x >= t->cols) {
        t->cursor_x = 0;
        t->cursor_y++;
    }
    if (t->cursor_y >= t->rows) {
        terminal_scroll_up(t);
        t->cursor_y = t->rows - 1;
    }
    TermCell* row = term_screen_row(t, t->cursor_y);
    row[t->cursor_x].ch = ch;
    row[t->cursor_x].fg = t->current_fg;
    row[t->cursor_x].bg = t->current_bg;
    t->cursor_x++;
}

static inline void terminal_enter_alt_screen(TerminalState* t) {
    if (t->alt_screen_active) return;
    t->alt_screen_active = true;
    t->dirty = true;
    // Save cursor
    t->saved_cursor_x = t->cursor_x;
    t->saved_cursor_y = t->cursor_y;
    // Save visible screen to alt_cells, clear visible screen
    int total = t->cols * t->rows;
    TermCell* screen = term_screen_row(t, 0);
    for (int i = 0; i < total; i++) {
        t->alt_cells[i] = screen[i];
        screen[i] = {' ', colors::TERM_FG, colors::TERM_BG};
    }
    t->view_offset = 0;
    t->cursor_x = 0;
    t->cursor_y = 0;
}

static inline void terminal_exit_alt_screen(TerminalState* t) {
    if (!t->alt_screen_active) return;
    t->alt_screen_active = false;
    t->dirty = true;
    // Restore visible screen from alt_cells
    int total = t->cols * t->rows;
    TermCell* screen = term_screen_row(t, 0);
    for (int i = 0; i < total; i++) {
        screen[i] = t->alt_cells[i];
    }
    // Restore cursor
    t->cursor_x = t->saved_cursor_x;
    t->cursor_y = t->saved_cursor_y;
}

static inline void terminal_process_private_mode(TerminalState* t, char cmd) {
    int p0 = t->csi_param_count > 0 ? t->csi_params[0] : 0;

    if (cmd == 'h') {
        // Set private mode
        if (p0 == 25) {
            t->cursor_visible = true;
        } else if (p0 == 1049) {
            terminal_enter_alt_screen(t);
        }
    } else if (cmd == 'l') {
        // Reset private mode
        if (p0 == 25) {
            t->cursor_visible = false;
        } else if (p0 == 1049) {
            terminal_exit_alt_screen(t);
        }
    }
}

static inline void terminal_process_csi(TerminalState* t, char cmd) {
    // Finalize current param
    if (t->csi_param_count < 8) {
        t->csi_params[t->csi_param_count] = t->csi_current_param;
        t->csi_param_count++;
    }

    // Handle private mode sequences (ESC[?...)
    if (t->csi_private) {
        terminal_process_private_mode(t, cmd);
        return;
    }

    int p0 = t->csi_param_count > 0 ? t->csi_params[0] : 0;
    int p1 = t->csi_param_count > 1 ? t->csi_params[1] : 0;

    switch (cmd) {
    case 'H': case 'f': {
        // Cursor position: ESC[row;colH (1-based)
        int row = (p0 > 0 ? p0 : 1) - 1;
        int col = (p1 > 0 ? p1 : 1) - 1;
        if (row < 0) row = 0;
        if (row >= t->rows) row = t->rows - 1;
        if (col < 0) col = 0;
        if (col >= t->cols) col = t->cols - 1;
        t->cursor_y = row;
        t->cursor_x = col;
        break;
    }
    case 'A': {
        // Cursor up
        int n = p0 > 0 ? p0 : 1;
        t->cursor_y -= n;
        if (t->cursor_y < 0) t->cursor_y = 0;
        break;
    }
    case 'B': {
        // Cursor down
        int n = p0 > 0 ? p0 : 1;
        t->cursor_y += n;
        if (t->cursor_y >= t->rows) t->cursor_y = t->rows - 1;
        break;
    }
    case 'C': {
        // Cursor forward
        int n = p0 > 0 ? p0 : 1;
        t->cursor_x += n;
        if (t->cursor_x >= t->cols) t->cursor_x = t->cols - 1;
        break;
    }
    case 'D': {
        // Cursor backward
        int n = p0 > 0 ? p0 : 1;
        t->cursor_x -= n;
        if (t->cursor_x < 0) t->cursor_x = 0;
        break;
    }
    case 'J': {
        // Erase in display
        Color erase_bg = terminal_erase_bg(t);
        if (p0 == 0) {
            // Clear from cursor to end
            TermCell* row = term_screen_row(t, t->cursor_y);
            for (int x = t->cursor_x; x < t->cols; x++)
                row[x] = {' ', t->current_fg, erase_bg};
            for (int r = t->cursor_y + 1; r < t->rows; r++) {
                TermCell* rp = term_screen_row(t, r);
                for (int c = 0; c < t->cols; c++)
                    rp[c] = {' ', t->current_fg, erase_bg};
            }
        } else if (p0 == 1) {
            // Clear from start to cursor
            for (int r = 0; r < t->cursor_y; r++) {
                TermCell* rp = term_screen_row(t, r);
                for (int c = 0; c < t->cols; c++)
                    rp[c] = {' ', t->current_fg, erase_bg};
            }
            TermCell* row = term_screen_row(t, t->cursor_y);
            for (int x = 0; x <= t->cursor_x; x++)
                row[x] = {' ', t->current_fg, erase_bg};
        } else if (p0 == 2) {
            // Clear entire screen
            for (int r = 0; r < t->rows; r++) {
                TermCell* rp = term_screen_row(t, r);
                for (int c = 0; c < t->cols; c++)
                    rp[c] = {' ', t->current_fg, erase_bg};
            }
            t->cursor_x = 0;
            t->cursor_y = 0;
        }
        break;
    }
    case 'K': {
        // Erase in line
        int start = 0, end = t->cols;
        if (p0 == 0) { start = t->cursor_x; end = t->cols; }
        else if (p0 == 1) { start = 0; end = t->cursor_x + 1; }
        else if (p0 == 2) { start = 0; end = t->cols; }
        Color erase_bg = terminal_erase_bg(t);
        TermCell* row = term_screen_row(t, t->cursor_y);
        for (int x = start; x < end; x++)
            row[x] = {' ', t->current_fg, erase_bg};
        break;
    }
    case 'm': {
        // SGR - Set Graphics Rendition
        for (int i = 0; i < t->csi_param_count; i++) {
            int code = t->csi_params[i];

            // Handle extended color sequences: 38;5;N or 48;5;N or 38;2;R;G;B or 48;2;R;G;B
            if ((code == 38 || code == 48) && i + 2 < t->csi_param_count) {
                if (t->csi_params[i + 1] == 5) {
                    // 256-color: 38;5;N or 48;5;N
                    int color_idx = t->csi_params[i + 2];
                    if (code == 38) {
                        t->current_fg = term_ansi_256_color(color_idx);
                    } else {
                        t->current_bg = term_ansi_256_color(color_idx);
                    }
                    i += 2;
                    continue;
                } else if (t->csi_params[i + 1] == 2 && i + 4 < t->csi_param_count) {
                    // Truecolor: 38;2;R;G;B or 48;2;R;G;B
                    int r = t->csi_params[i + 2];
                    int g = t->csi_params[i + 3];
                    int b = t->csi_params[i + 4];
                    if (code == 38) {
                        t->current_fg = Color::from_rgb(r, g, b);
                    } else {
                        t->current_bg = Color::from_rgb(r, g, b);
                    }
                    i += 4;
                    continue;
                }
            }

            if (code == 0) {
                t->current_fg = colors::TERM_FG;
                t->current_bg = colors::TERM_BG;
                t->reverse_video = false;
            } else if (code == 1) {
                // Bold: map to bright version of current color
                uint8_t r = t->current_fg.r;
                uint8_t g = t->current_fg.g;
                uint8_t b = t->current_fg.b;
                int add = 50;
                r = (r + add > 255) ? 255 : r + add;
                g = (g + add > 255) ? 255 : g + add;
                b = (b + add > 255) ? 255 : b + add;
                t->current_fg = Color::from_rgb(r, g, b);
            } else if (code == 2) {
                // Dim: darken current fg color
                t->current_fg.r = t->current_fg.r / 2;
                t->current_fg.g = t->current_fg.g / 2;
                t->current_fg.b = t->current_fg.b / 2;
            } else if (code == 7) {
                // Reverse video
                if (!t->reverse_video) {
                    t->reverse_video = true;
                    Color tmp = t->current_fg;
                    t->current_fg = t->current_bg;
                    t->current_bg = tmp;
                }
            } else if (code == 27) {
                // Reverse off
                if (t->reverse_video) {
                    t->reverse_video = false;
                    Color tmp = t->current_fg;
                    t->current_fg = t->current_bg;
                    t->current_bg = tmp;
                }
            } else if (code >= 30 && code <= 37) {
                t->current_fg = term_ansi_color(code - 30);
                if (t->reverse_video) {
                    // In reverse mode, fg is displayed as bg
                    Color tmp = t->current_fg;
                    t->current_fg = t->current_bg;
                    t->current_bg = tmp;
                }
            } else if (code >= 40 && code <= 47) {
                t->current_bg = term_ansi_color(code - 40);
            } else if (code >= 90 && code <= 97) {
                t->current_fg = term_ansi_color(code - 90 + 8);
            } else if (code >= 100 && code <= 107) {
                t->current_bg = term_ansi_color(code - 100 + 8);
            } else if (code == 39) {
                t->current_fg = colors::TERM_FG;
            } else if (code == 49) {
                t->current_bg = colors::TERM_BG;
            }
        }
        if (t->csi_param_count == 0) {
            // ESC[m with no params = reset
            t->current_fg = colors::TERM_FG;
            t->current_bg = colors::TERM_BG;
            t->reverse_video = false;
        }
        break;
    }
    default:
        break;
    }
}

static inline void terminal_feed(TerminalState* t, const char* data, int len) {
    if (len > 0) t->dirty = true;
    for (int i = 0; i < len; i++) {
        char ch = data[i];

        switch (t->parse_state) {
        case TerminalState::STATE_NORMAL:
            if (ch == '\033') {
                t->parse_state = TerminalState::STATE_ESC;
            } else if (ch == '\n') {
                t->cursor_x = 0;  // CR+LF: shell sends \n without \r
                t->cursor_y++;
                if (t->cursor_y >= t->rows) {
                    terminal_scroll_up(t);
                    t->cursor_y = t->rows - 1;
                }
            } else if (ch == '\r') {
                t->cursor_x = 0;
            } else if (ch == '\b') {
                if (t->cursor_x > 0) t->cursor_x--;
            } else if (ch == '\t') {
                int next = (t->cursor_x + 8) & ~7;
                if (next > t->cols) next = t->cols;
                while (t->cursor_x < next) {
                    terminal_put_char(t, ' ');
                }
            } else if (ch >= 32 || ch < 0) {
                // Printable character (also treat high-bit chars as printable)
                terminal_put_char(t, ch);
            }
            break;

        case TerminalState::STATE_ESC:
            if (ch == '[') {
                t->parse_state = TerminalState::STATE_CSI;
                t->csi_private = false;
                t->csi_param_count = 0;
                t->csi_current_param = 0;
                for (int j = 0; j < 8; j++) t->csi_params[j] = 0;
            } else if (ch == 'c') {
                // Reset terminal
                t->current_fg = colors::TERM_FG;
                t->current_bg = colors::TERM_BG;
                t->cursor_x = 0;
                t->cursor_y = 0;
                t->parse_state = TerminalState::STATE_NORMAL;
            } else {
                // Unknown ESC sequence, ignore
                t->parse_state = TerminalState::STATE_NORMAL;
            }
            break;

        case TerminalState::STATE_CSI:
            if (ch >= '0' && ch <= '9') {
                t->csi_current_param = t->csi_current_param * 10 + (ch - '0');
            } else if (ch == ';') {
                if (t->csi_param_count < 8) {
                    t->csi_params[t->csi_param_count] = t->csi_current_param;
                    t->csi_param_count++;
                }
                t->csi_current_param = 0;
            } else if (ch == '?') {
                t->csi_private = true;
            } else if (ch >= 0x40 && ch <= 0x7E) {
                // Final byte - execute command
                terminal_process_csi(t, ch);
                t->parse_state = TerminalState::STATE_NORMAL;
            } else {
                // Unknown, abort CSI
                t->parse_state = TerminalState::STATE_NORMAL;
            }
            break;
        }
    }
}

static inline void terminal_fill_pixel_rect(uint32_t* pixels, int pw, int ph,
                                            int x, int y, int w, int h,
                                            uint32_t color) {
    if (!pixels || pw <= 0 || ph <= 0 || w <= 0 || h <= 0) return;
    int x0 = x < 0 ? 0 : x;
    int y0 = y < 0 ? 0 : y;
    int x1 = x + w > pw ? pw : x + w;
    int y1 = y + h > ph ? ph : y + h;
    for (int row = y0; row < y1; row++) {
        uint32_t* dst = &pixels[row * pw + x0];
        for (int col = x0; col < x1; col++)
            *dst++ = color;
    }
}

static inline void terminal_draw_bitmap_char(uint32_t* pixels, int pw, int ph,
                                             int x, int y, unsigned char ch,
                                             uint32_t color) {
    const uint8_t* glyph = &font_data[ch * FONT_HEIGHT];
    for (int fy = 0; fy < FONT_HEIGHT; fy++) {
        int dy = y + fy;
        if (dy >= ph) break;
        if (dy < 0) continue;
        uint8_t bits = glyph[fy];
        for (int fx = 0; fx < FONT_WIDTH; fx++) {
            if (bits & (0x80 >> fx)) {
                int dx = x + fx;
                if (dx >= pw) break;
                if (dx >= 0) pixels[dy * pw + dx] = color;
            }
        }
    }
}

static inline void terminal_draw_cell_pixels(const TermCell& cell,
                                             uint32_t* pixels, int pw, int ph,
                                             int px, int py, int cell_w, int cell_h,
                                             bool fill_bg, bool use_ttf,
                                             GlyphCache* gc) {
    if (fill_bg) {
        terminal_fill_pixel_rect(pixels, pw, ph, px, py, cell_w, cell_h,
                                 cell.bg.to_pixel());
    }

    if (cell.ch <= 32 && cell.ch >= 0) return;

    if (use_ttf) {
        int baseline = py + gc->ascent;
        fonts::mono->draw_char_to_buffer(pixels, pw, ph,
            px, baseline, (unsigned char)cell.ch, cell.fg, gc);
    } else {
        terminal_draw_bitmap_char(pixels, pw, ph, px, py, (unsigned char)cell.ch,
                                  cell.fg.to_pixel());
    }
}

static inline void terminal_draw_cursor_pixels(const TermCell& cell,
                                               uint32_t* pixels, int pw, int ph,
                                               int px, int py, int cell_w, int cell_h,
                                               bool use_ttf, GlyphCache* gc) {
    terminal_fill_pixel_rect(pixels, pw, ph, px, py, cell_w, cell_h,
                             colors::WHITE.to_pixel());

    if (cell.ch <= 32 && cell.ch >= 0) return;

    if (use_ttf) {
        int baseline = py + gc->ascent;
        fonts::mono->draw_char_to_buffer(pixels, pw, ph,
            px, baseline, (unsigned char)cell.ch, colors::BLACK, gc);
    } else {
        terminal_draw_bitmap_char(pixels, pw, ph, px, py, (unsigned char)cell.ch,
                                  colors::BLACK.to_pixel());
    }
}

static inline bool terminal_prepare_render_cache(TerminalState* t,
                                                 int visible_cols,
                                                 int visible_rows) {
    if (visible_cols <= 0 || visible_rows <= 0) return false;
    if (t->render_cells && t->render_cols == visible_cols && t->render_rows == visible_rows)
        return true;

    if (t->render_cells) {
        montauk::free(t->render_cells);
        t->render_cells = nullptr;
    }

    int cells = visible_cols * visible_rows;
    t->render_cells = (TermCell*)montauk::alloc(cells * sizeof(TermCell));
    if (!t->render_cells) {
        terminal_invalidate_render_cache(t);
        t->render_cols = 0;
        t->render_rows = 0;
        return false;
    }

    t->render_cols = visible_cols;
    t->render_rows = visible_rows;
    terminal_invalidate_render_cache(t);
    return true;
}

static inline TermCell terminal_visible_cell(TerminalState* t, int base_row, int x, int y) {
    return t->cells[(base_row + y) * t->cols + x];
}

static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, int ph) {
    if (!t->dirty || !t->cells || !pixels || pw <= 0 || ph <= 0) return;

    int cell_w = mono_cell_width();
    int cell_h = mono_cell_height();
    if (cell_w <= 0 || cell_h <= 0) return;

    bool use_ttf = fonts::mono && fonts::mono->valid;
    GlyphCache* gc = use_ttf ? fonts::mono->get_cache(fonts::TERM_SIZE) : nullptr;

    int base_row = t->scrollback_lines - t->view_offset;
    if (base_row < 0) base_row = 0;

    int visible_rows = ph / cell_h;
    int visible_cols = pw / cell_w;
    if (visible_rows > t->rows) visible_rows = t->rows;
    if (visible_cols > t->cols) visible_cols = t->cols;
    if (visible_rows <= 0 || visible_cols <= 0) return;

    bool have_cache = terminal_prepare_render_cache(t, visible_cols, visible_rows);
    bool full_redraw = !have_cache || !t->render_valid ||
        t->render_pixels != pixels ||
        t->render_pw != pw || t->render_ph != ph ||
        t->render_cell_w != cell_w || t->render_cell_h != cell_h ||
        t->render_base_row != base_row ||
        t->render_term_cols != t->cols || t->render_term_rows != t->rows;

    uint32_t bg_px = colors::TERM_BG.to_pixel();
    if (full_redraw) {
        int row_bytes = pw * sizeof(uint32_t);
        for (int i = 0; i < pw; i++) pixels[i] = bg_px;
        for (int r = 1; r < ph; r++)
            montauk::memcpy(&pixels[r * pw], pixels, row_bytes);
    } else if (t->render_cursor_visible) {
        int x = t->render_cursor_x;
        int y = t->render_cursor_y;
        if (x >= 0 && x < visible_cols && y >= 0 && y < visible_rows) {
            TermCell cell = terminal_visible_cell(t, base_row, x, y);
            terminal_draw_cell_pixels(cell, pixels, pw, ph,
                                      x * cell_w, y * cell_h,
                                      cell_w, cell_h, true, use_ttf, gc);
            if (have_cache)
                t->render_cells[y * visible_cols + x] = cell;
        }
    }

    for (int r = 0; r < visible_rows; r++) {
        int py = r * cell_h;
        for (int c = 0; c < visible_cols; c++) {
            TermCell cell = terminal_visible_cell(t, base_row, c, r);
            int cache_idx = r * visible_cols + c;
            bool changed = full_redraw || !have_cache ||
                !term_cell_equal(t->render_cells[cache_idx], cell);
            if (!changed) continue;

            bool fill_bg = !full_redraw || cell.bg.to_pixel() != bg_px;
            terminal_draw_cell_pixels(cell, pixels, pw, ph,
                                      c * cell_w, py,
                                      cell_w, cell_h, fill_bg, use_ttf, gc);
            if (have_cache)
                t->render_cells[cache_idx] = cell;
        }
    }

    bool cursor_now_visible = t->view_offset == 0 && t->cursor_visible &&
        t->cursor_x >= 0 && t->cursor_y >= 0 &&
        t->cursor_x < visible_cols && t->cursor_y < visible_rows;
    if (cursor_now_visible) {
        TermCell cell = terminal_visible_cell(t, base_row, t->cursor_x, t->cursor_y);
        terminal_draw_cursor_pixels(cell, pixels, pw, ph,
                                    t->cursor_x * cell_w, t->cursor_y * cell_h,
                                    cell_w, cell_h, use_ttf, gc);
    }

    t->render_valid = have_cache;
    t->render_cursor_visible = cursor_now_visible;
    t->render_cursor_x = t->cursor_x;
    t->render_cursor_y = t->cursor_y;
    t->render_term_cols = t->cols;
    t->render_term_rows = t->rows;
    t->render_base_row = base_row;
    t->render_pw = pw;
    t->render_ph = ph;
    t->render_cell_w = cell_w;
    t->render_cell_h = cell_h;
    t->render_pixels = pixels;
    t->dirty = false;
}

static inline void terminal_resize(TerminalState* t, int new_cols, int new_rows) {
    if (new_cols == t->cols && new_rows == t->rows) return;
    if (new_cols < 1 || new_rows < 1) return;
    t->dirty = true;

    int new_capacity = new_rows + t->max_scrollback;
    int new_total = new_capacity * new_cols;
    TermCell* new_cells = (TermCell*)montauk::alloc(new_total * sizeof(TermCell));
    TermCell* new_alt = (TermCell*)montauk::alloc(new_rows * new_cols * sizeof(TermCell));
    if (!new_cells || !new_alt) {
        if (new_cells) montauk::free(new_cells);
        if (new_alt) montauk::free(new_alt);
        return;  // keep existing buffers
    }

    // Clear new buffers
    for (int i = 0; i < new_total; i++)
        new_cells[i] = {' ', colors::TERM_FG, colors::TERM_BG};
    for (int i = 0; i < new_rows * new_cols; i++)
        new_alt[i] = {' ', colors::TERM_FG, colors::TERM_BG};

    // Copy content: scrollback + visible screen
    int old_content = t->scrollback_lines + t->rows;
    int keep = old_content < new_capacity ? old_content : new_capacity;
    int discard = old_content - keep;
    int copy_cols = t->cols < new_cols ? t->cols : new_cols;

    for (int r = 0; r < keep; r++) {
        for (int c = 0; c < copy_cols; c++) {
            new_cells[r * new_cols + c] = t->cells[(discard + r) * t->cols + c];
        }
    }

    // Anchor the new visible region to the cursor's line. Treating the bottom
    // new_rows of kept content as the screen (the naive keep - new_rows) only
    // works when the screen is full: with a partly filled screen -- e.g. a
    // shell prompt a few lines down with blank rows below it -- it would push
    // the real text up into scrollback and drop the view onto the blank region,
    // so a zoom appears to scroll down and strands the cursor in empty space.
    // Instead pin the cursor to the bottom row when there is enough history
    // above it, otherwise keep the content anchored at the top.
    int abs_cursor_y = t->scrollback_lines + t->cursor_y - discard;
    if (abs_cursor_y < 0) abs_cursor_y = 0;
    int new_scrollback = abs_cursor_y - (new_rows - 1);
    if (new_scrollback < 0) new_scrollback = 0;
    if (new_scrollback > t->max_scrollback) new_scrollback = t->max_scrollback;

    // Adjust cursor
    int new_cursor_y = abs_cursor_y - new_scrollback;
    if (new_cursor_y < 0) new_cursor_y = 0;
    if (new_cursor_y >= new_rows) new_cursor_y = new_rows - 1;
    int new_cursor_x = t->cursor_x < new_cols ? t->cursor_x : new_cols - 1;

    if (t->cells) montauk::free(t->cells);
    if (t->alt_cells) montauk::free(t->alt_cells);
    if (t->render_cells) {
        montauk::free(t->render_cells);
        t->render_cells = nullptr;
    }

    t->cells = new_cells;
    t->alt_cells = new_alt;
    t->cols = new_cols;
    t->rows = new_rows;
    t->scrollback_lines = new_scrollback;
    t->cursor_x = new_cursor_x;
    t->cursor_y = new_cursor_y;
    terminal_invalidate_render_cache(t);

    // Clamp view offset
    if (t->view_offset > t->scrollback_lines)
        t->view_offset = t->scrollback_lines;

    // Notify child process of new terminal size
    if (t->child_pid > 0) {
        montauk::childio_settermsz(t->child_pid, new_cols, new_rows);
    }
}

static inline void terminal_handle_key(TerminalState* t, const montauk::abi::KeyEvent& key) {
    // Snap to live on any keyboard input
    if (t->view_offset > 0) {
        t->view_offset = 0;
        t->dirty = true;
    }
    if (t->child_pid > 0) {
        montauk::childio_writekey(t->child_pid, &key);
    }
}

// Returns false if the child process has exited
static inline bool terminal_poll(TerminalState* t) {
    if (t->child_pid <= 0) return false;
    char buf[TERM_READ_BUF_SIZE];
    int drained = 0;
    int grace_yields = 0;

    // Drain bursty TUI output as one frame. When a child was blocked on a full
    // stream, a short yield lets it finish the redraw before we present.
    for (;;) {
        int n = montauk::childio_read(t->child_pid, buf, sizeof(buf));
        if (n > 0) {
            terminal_feed(t, buf, n);
            drained += n;
            grace_yields = 0;
            if (drained >= TERM_MAX_DRAIN_BYTES_PER_POLL)
                break;
        } else {
            // n == -1 means child process is gone; n == 0 means no data yet
            if (n < 0) { t->child_pid = 0; return false; }
            if (drained > 0 && grace_yields < TERM_DRAIN_GRACE_YIELDS) {
                grace_yields++;
                montauk::yield();
                continue;
            }
            break;
        }
    }
    return true;
}

} // namespace gui
