module st;

import core.stdc.stdint;
import core.stdc.time : time_t;
import core.sys.posix.sys.types;
import core.sys.posix.time : timespec;
import core.sys.posix.termios;
import core.sys.posix.unistd;
import core.sys.posix.fcntl;
import core.sys.posix.sys.ioctl;
import core.sys.posix.sys.select;
import core.sys.posix.sys.wait;
import core.sys.posix.signal;
import core.stdc.errno;
import core.stdc.stdio;
import core.stdc.stdlib;
import core.stdc.stdlib : strtol, atoi;
import core.stdc.string;
import core.stdc.string : strchr;
import core.stdc.wchar_;
import core.stdc.ctype;
import core.stdc.limits;

import patches;
import win : WinMode, MOUSE, xbell, xsetmode, xsetpointermotion, xsetcursor;
import config;
import xft_types;

import std.conv : octal, to;
import std.process : environment;
import std.logger;

// Additional POSIX imports
public import core.sys.posix.sys.select : fd_set, FD_ZERO, FD_SET, FD_CLR, FD_ISSET, timeval;

// External C functions
extern(C) {
    pid_t setsid();
    int system(const(char)*);
    void perror(const(char)*);
    int execvp(const(char)*, char**);
    void _exit(int) nothrow @nogc;
    int dup2(int, int);
    int wcwidth(wchar_t);
    
    // Platform-specific openpty
    version(linux) {
        int openpty(int* master, int* slave, char* name, const(termios)* termp, const(winsize)* winp);
    } else version(OSX) {
        int openpty(int* master, int* slave, char* name, const(termios)* termp, const(winsize)* winp);
    } else version(FreeBSD) {
        int openpty(int* master, int* slave, char* name, const(termios)* termp, const(winsize)* winp);
    } else version(OpenBSD) {
        int openpty(int* master, int* slave, char* name, const(termios)* termp, const(winsize)* winp);
    } else {
        static assert(0, "openpty not available on this platform");
    }
}

// Terminal constants
enum {
    UTF_INVALID = 0xFFFD,
    UTF_SIZ = 4,
    ESC_BUF_SIZ = 128 * UTF_SIZ,
    ESC_ARG_SIZ = 16,
    STR_BUF_SIZ = ESC_BUF_SIZ,
    STR_ARG_SIZ = ESC_ARG_SIZ,
    BUFSIZ = 8192,  // Standard buffer size
}

static if (isPatchEnabled!"UNDERCURL_PATCH") {
    enum CAR_PER_ARG = 4;
}

immutable string STR_TERM_ST = "\033\\";
immutable string STR_TERM_BEL = "\007";

// Helper macros converted to functions
bool IS_SET(uint flag) { return (term.mode & flag) != 0; }
bool ISCONTROLC0(uint c) { return between(c, 0, 0x1f) || c == 0x7f; }
bool ISCONTROLC1(uint c) { return between(c, 0x80, 0x9f); }
bool ISCONTROL(uint c) { return ISCONTROLC0(c) || ISCONTROLC1(c); }
bool ISDELIM(wchar_t u) { 
    if (u == 0) return false;
    for (wchar* p = worddelimiters; *p; p++) {
        if (*p == u) return true;
    }
    return false;
}

// Terminal modes
enum TermMode : uint {
    WRAP         = 1 << 0,
    INSERT       = 1 << 1,
    ALTSCREEN    = 1 << 2,
    CRLF         = 1 << 3,
    ECHO         = 1 << 4,
    PRINT        = 1 << 5,
    UTF8         = 1 << 6,
}

// Escape state flags
enum : uint {
    ESC_START      = 1,
    ESC_CSI        = 2,
    ESC_STR        = 4,
    ESC_ALTCHARSET = 8,
    ESC_STR_END    = 16,
    ESC_TEST       = 32,
    ESC_UTF8       = 64,
}

static if (isPatchEnabled!"SIXEL_PATCH") {
    enum TermMode SIXEL = cast(TermMode)(1 << 7);
    enum TermMode SIXEL_CUR_RT = cast(TermMode)(1 << 8);
    enum TermMode SIXEL_SDM = cast(TermMode)(1 << 9);
}

static if (isPatchEnabled!"REFLOW_PATCH") {
    enum ScrollMode {
        RESIZE = -1,
        NOSAVEHIST = 0,
        SAVEHIST = 1,
    }
}

enum CursorMovement {
    SAVE,
    LOAD,
}

enum CursorState {
    DEFAULT = 0,
    WRAPNEXT = 1,
    ORIGIN = 2,
}

enum SelMode {
    IDLE = 0,
    EMPTY = 1,
    READY = 2
}

enum SelType {
    REGULAR = 1,
    RECTANGULAR = 2
}

enum SnapMode {
    WORD = 1,
    LINE = 2
}

enum Charset {
    GRAPHIC0,
    GRAPHIC1,
    UK,
    USA,
    MULTI,
    GER,
    FIN,
}

enum EscapeState {
    START      = 1,
    CSI        = 2,
    STR        = 4,  /* DCS, OSC, PM, APC */
    ALTCHARSET = 8,
    STR_END    = 16, /* a final string was encountered */
    TEST       = 32, /* Enter in test mode */
    UTF8       = 64,
}

static if (isPatchEnabled!"SIXEL_PATCH") {
    enum EscapeState DCS = cast(EscapeState)128;
}

// Selection structure
struct Selection {
    int mode;
    int type;
    int snap;
    struct Point { int x, y; }
    Point nb, ne, ob, oe;
    int alt;
}

// CSI Escape sequence struct
struct CSIEscape {
    char[ESC_BUF_SIZ] buf;
    size_t len;
    char priv;
    int[ESC_ARG_SIZ] arg;
    int narg;
    char[2] mode;
    static if (isPatchEnabled!"UNDERCURL_PATCH") {
        int[ESC_ARG_SIZ][CAR_PER_ARG] carg;
    }
}

// STR Escape sequence struct
struct STREscape {
    char type;
    char* buf;
    size_t siz;
    size_t len;
    char*[STR_ARG_SIZ] args;
    int narg;
    char* term;
}

// Global state
__gshared {
    Selection sel;
    CSIEscape csiescseq;
    STREscape strescseq;
    int cmdfd;
    int csdfd;  // slave fd of the pty
    pid_t pid;
    int iofd = 1;
    char* base64_digits = cast(char*)"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
}

// Terminal I/O definitions
enum TIOCSCTTY = 0x540E;  // Linux value, may need adjustment for other platforms
enum TIOCSWINSZ = 0x5414; // Linux value

// Wait status macros
bool WIFEXITED(int status) nothrow @nogc { return (status & 0x7f) == 0; }
int WEXITSTATUS(int status) nothrow @nogc { return (status >> 8) & 0xff; }
bool WIFSIGNALED(int status) nothrow @nogc { return ((status & 0x7f) + 1) >> 1 > 0; }
int WTERMSIG(int status) nothrow @nogc { return status & 0x7f; }

// Import X11 types from deimos
import deimos.X11.X;
import deimos.X11.Xlib;

// Additional X11 types
struct FcPattern;

// FcFontSet structure
struct FcFontSet {
    int nfont;
    int sfont;
    FcPattern** fonts;
}

// Helper functions/macros
T min(T)(T a, T b) { return a < b ? a : b; }
T max(T)(T a, T b) { return a < b ? b : a; }
size_t len(T)(T[] a) { return a.length; }
bool between(T)(T x, T a, T b) { return a <= x && x <= b; }
T divceil(T)(T n, T d) { return (n + d - 1) / d; }
void defaultTo(T)(ref T a, T b) { if (!a) a = b; }
void DEFAULT(T)(ref T a, T b) { if (a == 0) a = b; }
void limit(T)(ref T x, T a, T b) { 
    if (x < a) x = a;
    else if (x > b) x = b;
}
T clamp(T)(T x, T a, T b) { 
    if (x < a) return a;
    else if (x > b) return b;
    return x;
}

// Define ATTR_WRAP and ATTR_LIGA for easier use
enum uint ATTR_WRAP = GlyphAttribute.WRAP;
static if (isPatchEnabled!"LIGATURES_PATCH") {
    enum uint ATTR_LIGA = cast(uint)(1 << 15);
}

static if (isPatchEnabled!"LIGATURES_PATCH") {
    bool attrCmp(Glyph a, Glyph b) {
        return ((a.mode & ~ATTR_WRAP & ~ATTR_LIGA) != (b.mode & ~ATTR_WRAP & ~ATTR_LIGA)) ||
               a.fg != b.fg || a.bg != b.bg;
    }
} else {
    bool attrCmp(Glyph a, Glyph b) {
        return a.mode != b.mode || a.fg != b.fg || a.bg != b.bg;
    }
}

long timeDiff(timespec t1, timespec t2) {
    return (t1.tv_sec - t2.tv_sec) * 1000 + (t1.tv_nsec - t2.tv_nsec) / 1_000_000;
}

void modBit(T)(ref T x, bool set, T bit) {
    if (set) x |= bit;
    else x &= ~bit;
}

uint trueColor(ubyte r, ubyte g, ubyte b) {
    return (1 << 24) | (r << 16) | (g << 8) | b;
}

alias TRUECOLOR = trueColor;

bool isTrueCol(uint x) {
    return ((1 << 24) & x) != 0;
}

enum GlyphAttribute : uint {
    NULL           = 0,
    SET            = 1 << 0,
    BOLD           = 1 << 1,
    FAINT          = 1 << 2,
    ITALIC         = 1 << 3,
    UNDERLINE      = 1 << 4,
    BLINK          = 1 << 5,
    REVERSE        = 1 << 6,
    INVISIBLE      = 1 << 7,
    STRUCK         = 1 << 8,
    WRAP           = 1 << 9,
    WIDE           = 1 << 10,
    WDUMMY         = 1 << 11,
}

static if (isPatchEnabled!"SELECTION_COLORS_PATCH") {
    enum GlyphAttribute ATTR_SELECTED = cast(GlyphAttribute)(1 << 12);
}

static if (isPatchEnabled!"BOXDRAW_PATCH") {
    enum GlyphAttribute ATTR_BOXDRAW = cast(GlyphAttribute)(1 << 13);
}

static if (isPatchEnabled!"UNDERCURL_PATCH") {
    enum GlyphAttribute ATTR_DIRTYUNDERLINE = cast(GlyphAttribute)(1 << 14);
}

static if (isPatchEnabled!"LIGATURES_PATCH") {
    enum GlyphAttribute ATTR_LIGA = cast(GlyphAttribute)(1 << 15);
}

static if (isPatchEnabled!"SIXEL_PATCH") {
    enum GlyphAttribute ATTR_SIXEL = cast(GlyphAttribute)(1 << 16);
}

static if (isPatchEnabled!"KEYBOARDSELECT_PATCH" && isPatchEnabled!"REFLOW_PATCH") {
    enum GlyphAttribute ATTR_HIGHLIGHT = cast(GlyphAttribute)(1 << 17);
}

enum GlyphAttribute ATTR_BOLD_FAINT = GlyphAttribute.BOLD | GlyphAttribute.FAINT;

static if (isPatchEnabled!"SIXEL_PATCH") {
    struct ImageList {
        ImageList* next, prev;
        ubyte* pixels;
        void* pixmap;
        void* clipmask;
        int width;
        int height;
        int x;
        int y;
        static if (isPatchEnabled!"REFLOW_PATCH") {
            int reflow_y;
        }
        int cols;
        int cw;
        int ch;
        int transparent;
    }
}

static if (isPatchEnabled!"WIDE_GLYPHS_PATCH") {
    enum DrawingMode {
        NONE = 0,
        BG   = 1 << 0,
        FG   = 1 << 1,
    }
}

enum Screen {
    PRI = -1,
    ALL = 0,
    ALT = 1
}

enum SelectionMode {
    IDLE = 0,
    EMPTY = 1,
    READY = 2
}

enum SelectionType {
    REGULAR = 1,
    RECTANGULAR = 2
}

enum SelectionSnap {
    WORD = 1,
    LINE = 2
}

alias Rune = uint_least32_t;

struct Glyph {
    Rune u;
    uint mode;
    uint fg;
    uint bg;
    static if (isPatchEnabled!"UNDERCURL_PATCH") {
        int ustyle;
        int[3] ucolor;
    }
    
    this(Rune u, uint mode, uint fg, uint bg) {
        this.u = u;
        this.mode = mode;
        this.fg = fg;
        this.bg = bg;
    }
}

alias Line = Glyph*;

static if (isPatchEnabled!"LIGATURES_PATCH") {
    struct GlyphFontSeq {
        int ox;
        int charlen;
        int numspecs;
        Glyph base;
    }
}

struct TCursor {
    Glyph attr;
    int x;
    int y;
    char state = CursorState.DEFAULT;
}

struct Term {
    int row;
    int col;
    static if (isPatchEnabled!"COLUMNS_PATCH") {
        int maxcol;
    }
    Line* line;
    Line* alt;
    static if (isPatchEnabled!"REFLOW_PATCH" || isPatchEnabled!"SCROLLBACK_PATCH") {
        Line[HISTSIZE] hist;
        int histi;
        static if (isPatchEnabled!"REFLOW_PATCH") {
            int histf;
            int[2] wrapcwidth;
        } else {
            int histn;
        }
        int scr;
    }
    int* dirty;
    TCursor c;
    int ocx;
    int ocy;
    int top;
    int bot;
    int mode;
    int esc;
    char[4] trantbl;
    int charset;
    int icharset;
    int* tabs;
    static if (isPatchEnabled!"SIXEL_PATCH") {
        ImageList* images;
        ImageList* images_alt;
    }
    Rune lastc;
}

union Arg {
    int i;
    uint ui;
    float f;
    const(void)* v;
    const(char)* s;
}

// Scrollback support
Line TLINE(int y) {
    static if (isPatchEnabled!"SCROLLBACK_PATCH") {
        if (y < term.scr) {
            int idx = (y + term.histi - term.scr + HISTSIZE + 1) % HISTSIZE;
            return term.hist[idx];
        } else {
            int idx = y - term.scr;
            if (idx < 0 || idx >= term.row) {
                import std.logger : error;
                error("TLINE: invalid line index ", idx, " (y=", y, ", term.scr=", term.scr, ", term.row=", term.row, ")");
                return term.line[0]; // Return first line as fallback
            }
            return term.line[idx];
        }
    } else {
        return term.line[y];
    }
}

static if (isPatchEnabled!"SCROLLBACK_PATCH" || 
           isPatchEnabled!"SCROLLBACK_MOUSE_PATCH" || 
           isPatchEnabled!"SCROLLBACK_MOUSE_ALTSCREEN_PATCH") {
    extern(C) void kscrolldown(const(Arg)* a) {
        int n = a.i;
        
        if (n < 0)
            n = term.row + n;
            
        if (n > term.scr)
            n = term.scr;
            
        if (term.scr > 0) {
            term.scr -= n;
            selscroll(0, -n);
            tfulldirt();
        }
        
        static if (isPatchEnabled!"OPENURLONCLICK_PATCH") {
            if (n > 0)
                restoremousecursor();
        }
    }
    
    extern(C) void kscrollup(const(Arg)* a) {
        int n = a.i;
        
        if (n < 0)
            n = term.row + n;
            
        static if (isPatchEnabled!"REFLOW_PATCH") {
            if (term.scr + n > term.histf)
                n = term.histf - term.scr;
        } else {
            if (term.scr + n > term.histn)
                n = term.histn - term.scr;
        }
            
        if (!n)
            return;
            
        if (term.scr <= HISTSIZE - n) {
            term.scr += n;
            selscroll(0, n);
            tfulldirt();
        }
        
        static if (isPatchEnabled!"OPENURLONCLICK_PATCH") {
            if (n > 0)
                restoremousecursor();
        }
    }
}

// Stub for openurlonclick patch
static if (isPatchEnabled!"OPENURLONCLICK_PATCH") {
    void restoremousecursor() {
        // TODO: Implement when needed
    }
}

struct TermWindow {
    int tw, th;
    int w, h;
    static if (isPatchEnabled!"BACKGROUND_IMAGE_PATCH") {
        int x, y;
    }
    static if (isPatchEnabled!"ANYSIZE_PATCH") {
        int hborderpx, vborderpx;
    }
    int ch;
    int cw;
    static if (isPatchEnabled!"VERTCENTER_PATCH") {
        int cyo;
    }
    int mode;
    int cursor;
}

struct XWindow {
    Display* dpy;
    Colormap cmap;
    Window win;
    Drawable buf;
    GlyphFontSpec* specbuf;
    static if (isPatchEnabled!"LIGATURES_PATCH") {
        GlyphFontSeq* specseq;
    }
    Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid;
    static if (isPatchEnabled!"FULLSCREEN_PATCH") {
        Atom netwmstate, netwmfullscreen;
    }
    static if (isPatchEnabled!"NETWMICON_PATCH" || isPatchEnabled!"NETWMICON_LEGACY_PATCH" || 
               isPatchEnabled!"NETWMICON_FF_PATCH") {
        Atom netwmicon;
    }
    struct Ime {
        XIM xim;
        XIC xic;
        XPoint spot;
        XVaNestedList spotlist;
    }
    Ime ime;
    Draw draw;
    static if (isPatchEnabled!"BACKGROUND_IMAGE_PATCH") {
        GC bggc;
    }
    Visual* vis;
    XSetWindowAttributes attrs;
    static if (isPatchEnabled!"HIDECURSOR_PATCH" || isPatchEnabled!"OPENURLONCLICK_PATCH") {
        Cursor vpointer, bpointer;
        int pointerisvisible;
    }
    static if (isPatchEnabled!"OPENURLONCLICK_PATCH") {
        Cursor upointer;
    }
    int scr;
    int isfixed;
    static if (isPatchEnabled!"ALPHA_PATCH") {
        int depth;
    }
    int l, t;
    int gm;
}

struct XSelection {
    Atom xtarget;
    char* primary, clipboard;
    timespec tclick1;
    timespec tclick2;
}

struct Shortcut {
    uint mod;
    uint keysym;
    extern(C) void function(const(Arg)*) func;
    Arg arg;
    int screen;
}

struct MouseShortcut {
    uint mod;
    uint button;
    extern(C) void function(const(Arg)*) func;
    Arg arg;
    uint release;
    int screen;
}

struct Key {
    uint k;
    uint mask;
    char* s;
    byte appkey;
    byte appcursor;
}

struct Font {
    int height;
    int width;
    int ascent;
    int descent;
    int badslant;
    int badweight;
    short lbearing;
    short rbearing;
    XftFont* match;
    FcFontSet* set;
    FcPattern* pattern;
}

struct DC {
    Color* col;
    size_t collen;
    Font font, bfont, ifont, ibfont;
    GC gc;
}

// Function implementations
extern(C) void die(const(char)* errstr, ...) {
    import core.stdc.stdio : vfprintf, stderr;
    import core.stdc.stdlib : exit;
    import core.stdc.stdarg;
    
    va_list ap;
    va_start(ap, errstr);
    vfprintf(stderr, errstr, ap);
    va_end(ap);
    exit(1);
}

// Terminal functions implementation
extern(C) void tnew(int cols, int rows) {
    term = Term.init;
    tresize(cols, rows);
    treset();
    
    // Initialize history buffer
    static if (isPatchEnabled!"SCROLLBACK_PATCH" || isPatchEnabled!"REFLOW_PATCH") {
        for (int i = 0; i < HISTSIZE; i++) {
            term.hist[i] = cast(Line)xmalloc(cols * Glyph.sizeof);
            // Initialize with empty glyphs
            for (int j = 0; j < cols; j++) {
                term.hist[i][j] = Glyph(' ', 0, defaultfg, defaultbg);
            }
        }
    }
    
    // Force full redraw
    tfulldirt();
}

void tresize(int col, int row) {
    int i;
    int minrow = min(row, term.row);
    int mincol = min(col, term.col);
    int* bp;
    TCursor c;

    if (col < 1 || row < 1) {
        return;
    }

    // Backup cursor
    c = term.c;
    
    // Ensure cursor is within bounds
    if (term.c.x >= col)
        term.c.x = col - 1;
    if (term.c.y >= row)
        term.c.y = row - 1;
    
    // Resize to new width
    static if (isPatchEnabled!"COLUMNS_PATCH") {
        term.maxcol = max(col, term.maxcol);
        col = term.maxcol;
    }
    
    // Handle shrinking height - need to save lines to scrollback
    if (row < term.row) {
        tcursor(CursorMovement.SAVE);
        tsetscroll(0, term.row - 1);
        
        // Handle both screens - swap between them
        for (int screen = 0; screen < 2; screen++) {
            if (term.c.y >= row) {
                static if (isPatchEnabled!"SCROLLBACK_PATCH") {
                    tscrollup(0, term.c.y - row + 1, !IS_SET(TermMode.ALTSCREEN));
                } else {
                    tscrollup(0, term.c.y - row + 1);
                }
            }
            
            // Free the lines that are now outside the new size
            for (i = row; i < term.row; i++) {
                free(term.line[i]);
            }
            
            // Swap screens and restore cursor
            tswapscreen();
            tcursor(CursorMovement.LOAD);
        }
    }
    
    // Resize buffers - do this AFTER freeing lines when shrinking
    term.line = cast(Line*)xrealloc(term.line, row * (Line*).sizeof);
    term.alt = cast(Line*)xrealloc(term.alt, row * (Line*).sizeof);
    term.dirty = cast(int*)xrealloc(term.dirty, row * int.sizeof);
    term.tabs = cast(int*)xrealloc(term.tabs, col * int.sizeof);
    
    // Resize each row to new width, zero-pad if needed
    for (i = 0; i < minrow; i++) {
        term.line[i] = cast(Line)xrealloc(term.line[i], col * Glyph.sizeof);
        term.alt[i] = cast(Line)xrealloc(term.alt[i], col * Glyph.sizeof);
        
        // Clear newly allocated cells if width increased
        if (col > term.col) {
            for (int j = term.col; j < col; j++) {
                term.line[i][j] = Glyph(' ', 0, defaultfg, defaultbg);
                term.alt[i][j] = Glyph(' ', 0, defaultfg, defaultbg);
            }
        }
    }
    
    // Allocate new rows
    for (/* i = minrow */; i < row; i++) {
        term.line[i] = cast(Line)xmalloc(col * Glyph.sizeof);
        term.alt[i] = cast(Line)xmalloc(col * Glyph.sizeof);
        
        // Initialize new rows with empty cells
        for (int j = 0; j < col; j++) {
            term.line[i][j] = Glyph(' ', 0, defaultfg, defaultbg);
            term.alt[i][j] = Glyph(' ', 0, defaultfg, defaultbg);
        }
    }
    
    if (col > term.col) {
        bp = term.tabs + term.col;
        memset(bp, 0, (col - term.col) * int.sizeof);
        while (--bp > term.tabs && !*bp) {}
        for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
            *bp = 1;
    }
    
    // Initialize dirty flags for new rows
    if (row > term.row) {
        memset(term.dirty + term.row, 0, (row - term.row) * int.sizeof);
    }
    
    // Resize history buffer if needed
    static if (isPatchEnabled!"SCROLLBACK_PATCH" || isPatchEnabled!"REFLOW_PATCH") {
        if (col != term.col) {
            for (i = 0; i < HISTSIZE; i++) {
                if (term.hist[i] !is null) {
                    term.hist[i] = cast(Line)xrealloc(term.hist[i], col * Glyph.sizeof);
                    // Initialize new columns if expanding
                    if (col > term.col) {
                        for (int j = term.col; j < col; j++) {
                            term.hist[i][j] = Glyph(' ', 0, defaultfg, defaultbg);
                        }
                    }
                } else {
                    // Allocate if not already allocated
                    term.hist[i] = cast(Line)xmalloc(col * Glyph.sizeof);
                    for (int j = 0; j < col; j++) {
                        term.hist[i][j] = Glyph(' ', 0, defaultfg, defaultbg);
                    }
                }
            }
        }
    }
    
    // Check if we're expanding horizontally and have scrollback to pull from
    static if (isPatchEnabled!"SCROLLBACK_PATCH" && isPatchEnabled!"REFLOW_PATCH") {
        if (col > term.col && term.scr > 0) {
            
            // When expanding horizontally, lines that were wrapped might now fit on fewer lines
            // This means we can pull more content from scrollback
            int lines_to_pull = 0;
            
            // Count wrapped lines that might unwrap with more width
            // Check current visible lines for ATTR_WRAP
            int wrapped_lines = 0;
            for (i = 0; i < term.row - 1; i++) {
                // Check if last character has ATTR_WRAP set
                if (term.line[i] && term.col > 0 && 
                    (term.line[i][term.col - 1].mode & ATTR_WRAP)) {
                    wrapped_lines++;
                }
            }
            
            // Estimate how many lines we can pull based on wrapped lines
            if (wrapped_lines > 0) {
                // Rough estimate: if doubling width, half the wrapped lines might unwrap
                if (col >= term.col * 2) {
                    lines_to_pull = min(term.scr, wrapped_lines / 2);
                } else {
                    // For smaller increases, proportionally fewer lines
                    float ratio = cast(float)(col - term.col) / term.col;
                    lines_to_pull = min(term.scr, cast(int)(wrapped_lines * ratio * 0.5));
                }
            }
            
            // Also add some lines just for the width increase
            lines_to_pull += min(term.scr - lines_to_pull, (col - term.col) / 10);
            
            if (lines_to_pull > 0) {
                
                // Scroll down to make room for lines from history
                for (i = 0; i < lines_to_pull && term.scr > 0; i++) {
                    // Decrease scroll position
                    term.scr--;
                    
                    // Shift existing lines down
                    Line temp = term.line[term.row - 1];
                    for (int j = term.row - 1; j > 0; j--) {
                        term.line[j] = term.line[j - 1];
                    }
                    
                    // Pull line from history
                    int hist_idx = (term.histi - term.scr + HISTSIZE) % HISTSIZE;
                    
                    // The old bottom line goes to history (it was already saved earlier)
                    // Just free it since we're not maintaining that
                    free(temp);
                    
                    // Allocate new line for first position
                    term.line[0] = cast(Line)xmalloc(col * Glyph.sizeof);
                    
                    // Copy from history and expand to new width
                    Line hist_line = term.hist[hist_idx];
                    if (hist_line) {
                        for (int k = 0; k < min(term.col, col); k++) {
                            term.line[0][k] = hist_line[k];
                        }
                    }
                    // Clear any new columns
                    for (int k = term.col; k < col; k++) {
                        term.line[0][k] = Glyph(' ', 0, defaultfg, defaultbg);
                    }
                }
                
                // Update selection if needed
                if (lines_to_pull > 0) {
                    selscroll(0, lines_to_pull);
                    // Mark all lines as dirty to force redraw
                    tfulldirt();
                }
            }
        }
    }
    
    // Update terminal size
    term.col = col;
    term.row = row;
    
    // Fix scroll region
    tsetscroll(0, row - 1);
    
    // Restore cursor if applicable
    if (c.x >= col) {
        c.state &= ~CursorState.WRAPNEXT;
        c.x = col - 1;
    }
    term.c = c;
}

void treset() {
    uint i;
    
    term.c = TCursor();
    term.c.attr = Glyph(' ', 0, defaultfg, defaultbg);
    term.c.x = 0;
    term.c.y = 0;
    term.c.state = CursorState.DEFAULT;
    
    memset(term.tabs, 0, term.col * int.sizeof);
    for (i = tabspaces; i < cast(uint)term.col; i += tabspaces)
        term.tabs[i] = 1;
    
    term.top = 0;
    term.bot = term.row - 1;
    term.mode = TermMode.WRAP | TermMode.UTF8;
    memset(term.trantbl.ptr, Charset.USA, term.trantbl.sizeof);
    term.charset = 0;
    
    // Clear both primary and alternate screens
    for (i = 0; i < 2; i++) {
        tmoveto(0, 0);
        tcursor(CursorMovement.SAVE);
        static if (isPatchEnabled!"COLUMNS_PATCH") {
            tclearregion(0, 0, term.maxcol-1, term.row-1);
        } else {
            tclearregion(0, 0, term.col-1, term.row-1);
        }
        tswapscreen();
    }
    
    static if (isPatchEnabled!"REFLOW_PATCH") {
        treflow(-1, -1);
    }
}


uint tcellattr() {
    return term.c.attr.mode & (
        GlyphAttribute.BOLD | 
        GlyphAttribute.FAINT | 
        GlyphAttribute.ITALIC | 
        GlyphAttribute.UNDERLINE | 
        GlyphAttribute.BLINK | 
        GlyphAttribute.REVERSE | 
        GlyphAttribute.INVISIBLE | 
        GlyphAttribute.STRUCK
    );
}


static if (isPatchEnabled!"REFLOW_PATCH") {
    void treflow(int col, int row) {
        // Stub for reflow patch
    }
}

extern(C) void selinit() {
    // Initialize selection
    xsel.primary = null;
    xsel.clipboard = null;
    // Note: xsel.xtarget is initialized in xinit(), don't reset it here
}

extern(C) void* xmalloc(size_t size) {
    import core.stdc.stdlib : malloc;
    void* p = malloc(size);
    if (!p)
        die("malloc: %s\n".ptr, "out of memory".ptr);
    return p;
}

extern(C) void* xrealloc(void* p, size_t size) {
    import core.stdc.stdlib : realloc;
    p = realloc(p, size);
    if (!p)
        die("realloc: %s\n".ptr, "out of memory".ptr);
    return p;
}

extern(C) char* xstrdup(const(char)* s) {
    import core.stdc.string : strlen, strcpy;
    size_t len = strlen(s) + 1;
    char* p = cast(char*)xmalloc(len);
    strcpy(p, s);
    return p;
}

// TTY functions
extern(C) int ttynew(const(char)* line, char* cmd, const(char)* outfile, char** args) {
    int m, s;
    
    if (outfile) {
        term.mode |= TermMode.PRINT;
        iofd = (strcmp(outfile, "-") == 0) ? 1 : open(outfile, O_WRONLY | O_CREAT | O_TRUNC, octal!666);
        if (iofd < 0) {
            fprintf(stderr, "Error opening %s:%s\n", outfile, strerror(errno));
        }
    }
    
    if (line) {
        cmdfd = open(line, O_RDWR);
        if (cmdfd < 0)
            die("open line '%s' failed: %s\n", line, strerror(errno));
        dup2(cmdfd, 0);
        stty(args);
        return cmdfd;
    }
    
    if (openpty(&m, &s, null, null, null) < 0)
        die("openpty failed: %s\n", strerror(errno));
    
    switch (pid = fork()) {
    case -1:
        die("fork failed: %s\n", strerror(errno));
        break;
    case 0:
        close(iofd);
        close(m);
        setsid();
        dup2(s, 0);
        dup2(s, 1);
        dup2(s, 2);
        if (ioctl(s, TIOCSCTTY, null) < 0)
            die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
        if (s > 2)
            close(s);
        
        version(Posix) {
            import core.sys.posix.signal : signal, SIG_DFL;
            signal(SIGCHLD, SIG_DFL);
            signal(SIGHUP, SIG_DFL);
            signal(SIGINT, SIG_DFL);
            signal(SIGQUIT, SIG_DFL);
            signal(SIGTERM, SIG_DFL);
            signal(SIGALRM, SIG_DFL);
        }
        
        execsh(cmd, args);
        break;
    default:
        static if (isPatchEnabled!"EXTERNALPIPEIN_PATCH" && isPatchEnabled!"EXTERNALPIPE_PATCH") {
            csdfd = s;
        } else {
            close(s);
        }
        cmdfd = m;
        version(Posix) {
            import core.sys.posix.signal : signal;
            signal(SIGCHLD, &sigchld);
        }
    }
    
    return cmdfd;
}

void stty(char** args) {
    char[4096] cmd; // Use larger buffer like _POSIX_ARG_MAX
    char* p = cmd.ptr;
    char* s;
    size_t n, siz;
    
    n = strlen(stty_args);
    if (n > cmd.sizeof - 1)
        die("incorrect stty parameters\n");
    memcpy(cmd.ptr, stty_args, n);
    p = cmd.ptr + n;
    siz = cmd.sizeof - n;
    
    for (char** ap = args; ap && *ap; ap++) {
        s = *ap;
        n = strlen(s);
        if (n > siz - 1)
            die("stty parameter length too long\n");
        *p++ = ' ';
        memcpy(p, s, n);
        p += n;
        siz -= n + 1;
    }
    *p = '\0';
    
    if (system(cmd.ptr) != 0)
        perror("Couldn't call stty");
}

void execsh(char* cmd, char** args) {
    import core.sys.posix.pwd : passwd, getpwuid;
    import core.sys.posix.unistd : getuid;
    
    char* sh;
    char* prog;
    char* arg;
    const(passwd)* pw;
    
    errno = 0;
    pw = getpwuid(getuid());
    if (pw is null) {
        if (errno)
            die("getpwuid: %s\n", strerror(errno));
        else
            die("who are you?\n");
    }
    
    string shellEnv = environment.get("SHELL", null);
    if (shellEnv !is null) {
        sh = cast(char*)shellEnv.ptr;
    } else {
        if (pw.pw_shell[0])
            sh = cast(char*)pw.pw_shell;
        else
            sh = cast(char*)"/bin/sh";  // fallback to /bin/sh
    }
    
    if (args) {
        prog = args[0];
        arg = null;
    } else if (scroll) {
        prog = scroll;
        arg = utmp ? utmp : sh;
    } else if (utmp) {
        prog = utmp;
        arg = null;
    } else {
        prog = sh;
        arg = null;
    }
    
    char*[3] argv;
    argv[0] = prog;
    argv[1] = arg;
    argv[2] = null;
    
    // DEFAULT(args, argv) - if args is null, use our constructed argv
    if (!args)
        args = argv.ptr;
    
    environment.remove("COLUMNS");
    environment.remove("LINES");
    environment.remove("TERMCAP");
    
    import std.string : fromStringz;
    environment["LOGNAME"] = fromStringz(pw.pw_name).idup;
    environment["USER"] = fromStringz(pw.pw_name).idup;
    environment["SHELL"] = fromStringz(sh).idup;
    environment["HOME"] = fromStringz(pw.pw_dir).idup;
    environment["TERM"] = fromStringz(termname).idup;
    environment["COLORTERM"] = "truecolor";
    
    execvp(prog, args);
    _exit(1);
}

extern(C) void sigchld(int a) nothrow @nogc {
    int stat;
    pid_t p;
    
    p = wait(&stat);
    if (pid != p)
        return;
    
    if (WIFEXITED(stat) && WEXITSTATUS(stat))
        _exit(1);
    else if (WIFSIGNALED(stat))
        _exit(1);
    _exit(0);
}

extern(C) size_t ttyread() {
    static char[BUFSIZ] buf;
    static int buflen = 0;
    ssize_t ret;
    
    // Read from tty
    ret = read(cmdfd, buf.ptr + buflen, buf.length - buflen);
    
    switch (ret) {
    case 0:
        return 0;
    case -1:
        die("couldn't read from shell: %s\n", strerror(errno));
        break;
    default:
        buflen += cast(int)ret;
        break;
    }
    
    twrite(buf.ptr, buflen, 0);
    
    
    buflen = 0;
    
    return cast(size_t)ret;
}

void twrite(const(char)* buf, int n, int show_ctrl) {
    int charsize;
    Rune u;
    int len;
    
    for (int i = 0; i < n; i += charsize) {
        if (IS_SET(TermMode.UTF8)) {
            // UTF-8 decoding
            charsize = utf8decode(buf + i, &u, n - i);
            if (charsize == 0) {
                charsize = 1;
                u = buf[i] & 0x7f;
            }
        } else {
            charsize = 1;
            u = buf[i] & 0xff;
        }
        
        if (show_ctrl && ISCONTROL(u)) {
            if (u & 0x80) {
                u &= 0x7f;
                tputc('^');
                tputc('[');
            } else if (u != '\n' && u != '\r' && u != '\t') {
                u ^= 0x40;
                tputc('^');
            }
        }
        tputc(u);
    }
}

void tputc(Rune u) {
    char[UTF_SIZ] c;
    int width;
    size_t len;
    bool control;
    
    control = ISCONTROL(u);
    if (u < 127 || !IS_SET(TermMode.UTF8)) {
        c[0] = cast(char)u;
        width = len = 1;
    } else {
        len = utf8encode(u, c.ptr);
        if (!control && (width = wcwidth(u)) == -1)
            width = 1;
    }
    
    /*
     * STR sequence must be checked before anything else
     * because it uses all following characters until it
     * receives a ESC, a SUB, a ST or any other C1 control
     * character.
     */
    if (term.esc & ESC_STR) {
        if (u == '\a' || u == octal!"30" || u == octal!"32" || u == octal!"33" ||
           ISCONTROLC1(u)) {
            term.esc &= ~(ESC_START|ESC_STR);
            term.esc |= ESC_STR_END;
            goto check_control_code;
        }
        
        if (strescseq.len+len >= strescseq.siz) {
            /*
             * Here is a bug in terminals. If the user never sends
             * some code to stop the str or esc command, then st
             * will stop responding. But this is better than
             * silently failing with unknown characters. At least
             * then users will report back.
             *
             * In the case users ever get fixed, here is the code:
             */
            /*
             * term.esc = 0;
             * strhandle();
             */
            if (strescseq.siz > (size_t.max - UTF_SIZ) / 2)
                return;
            strescseq.siz *= 2;
            strescseq.buf = cast(char*)xrealloc(strescseq.buf, strescseq.siz);
        }
        
        memmove(&strescseq.buf[strescseq.len], c.ptr, len);
        strescseq.len += len;
        return;
    }
    
check_control_code:
    /*
     * Actions of control codes must be performed as soon they arrive
     * because they can be embedded inside a control sequence, and
     * they must not cause conflicts with sequences.
     */
    if (control) {
        /* in UTF-8 mode ignore handling C1 control characters */
        if (IS_SET(TermMode.UTF8) && ISCONTROLC1(u))
            return;
        tcontrolcode(u);
        /*
         * control codes are not shown ever
         */
        if (!term.esc)
            term.lastc = 0;
        return;
    } else if (term.esc & ESC_START) {
        if (term.esc & ESC_CSI) {
            csiescseq.buf[csiescseq.len++] = cast(char)u;
            if (between(u, 0x40, 0x7E) ||
                    csiescseq.len >= ESC_BUF_SIZ-1) {
                // Removed verbose CSI sequence trace
                term.esc = 0;
                csiparse();
                csihandle();
            }
            return;
        } else if (term.esc & ESC_ALTCHARSET) {
            tdeftran(cast(char)u);
        } else if (term.esc & ESC_TEST) {
            tdectest(cast(char)u);
        } else {
            if (!eschandle(u))
                return;
            /* sequence already finished */
        }
        term.esc = 0;
        /*
         * All characters which form part of a sequence are not
         * printed
         */
        return;
    }
    
    
    // Handle normal character
    if (IS_SET(TermMode.WRAP) && term.c.state & CursorState.WRAPNEXT) {
        term.line[term.c.y][term.c.x].mode |= GlyphAttribute.WRAP;
        tnewline(1);
    }
    
    if (IS_SET(TermMode.INSERT) && term.c.x + width < term.col) {
        memmove(&term.line[term.c.y][term.c.x + width],
                &term.line[term.c.y][term.c.x],
                (term.col - term.c.x - width) * Glyph.sizeof);
    }
    
    if (term.c.x + width > term.col) {
        if (IS_SET(TermMode.WRAP))
            tnewline(1);
        else
            tmoveto(term.col - width, term.c.y);
    }
    
    tsetchar(u, &term.c.attr, term.c.x, term.c.y);
    term.dirty[term.c.y] = 1;  // Mark line as dirty
    
    if (width == 2) {
        term.line[term.c.y][term.c.x].mode |= GlyphAttribute.WIDE;
        if (term.c.x + 1 < term.col) {
            term.line[term.c.y][term.c.x + 1].u = 0;
            term.line[term.c.y][term.c.x + 1].mode |= GlyphAttribute.WDUMMY;
        }
    }
    
    if (term.c.x + width < term.col) {
        tmoveto(term.c.x + width, term.c.y);
    } else {
        term.c.state |= CursorState.WRAPNEXT;
    }
}

void tcontrolcode(uint ascii) {
    
    switch (ascii) {
    case '\t':   // HT
        tputtab(1);
        return;
    case '\b':   // BS
        tmoveto(term.c.x - 1, term.c.y);
        return;
    case '\r':   // CR
        tmoveto(0, term.c.y);
        return;
    case '\f':   // LF
    case '\v':   // VT
    case '\n':   // LF
        tnewline(IS_SET(TermMode.CRLF));
        return;
    case '\a':   // BEL
        if (term.esc & ESC_STR_END) {
            term.esc = 0;
        } else {
            xbell();
        }
        return;
    case '\033': // ESC
        csireset();
        term.esc &= ~(ESC_CSI | ESC_ALTCHARSET | ESC_TEST);
        term.esc |= ESC_START;
        return;
    case '\016': // SO (LS1 -- Locking shift 1)
    case '\017': // SI (LS0 -- Locking shift 0)
        // charset handling
        return;
    case '\032': // SUB
        tsetchar('?', &term.c.attr, term.c.x, term.c.y);
        goto case;
    case '\030': // CAN
        csireset();
        break;
    case '\005': // ENQ (IGNORED)
    case '\000': // NUL (IGNORED)
    case '\021': // XON (IGNORED)
    case '\023': // XOFF (IGNORED)
    case octal!"177":   // DEL (IGNORED)
        return;
    case 0x80:   // TODO: PAD
    case 0x81:   // TODO: HOP
    case 0x82:   // TODO: BPH
    case 0x83:   // TODO: NBH
    case 0x84:   // TODO: IND
        break;
    case 0x85:   // NEL -- Next line
        tnewline(1);
        break;
    case 0x86:   // TODO: SSA
    case 0x87:   // TODO: ESA
        break;
    case 0x88:   // HTS -- Horizontal tab stop
        term.tabs[term.c.x] = 1;
        break;
    case 0x89:   // TODO: HTJ
    case 0x8a:   // TODO: VTS
    case 0x8b:   // TODO: PLD
    case 0x8c:   // TODO: PLU
    case 0x8d:   // TODO: RI
    case 0x8e:   // TODO: SS2
    case 0x8f:   // TODO: SS3
    case 0x91:   // TODO: PU1
    case 0x92:   // TODO: PU2
    case 0x93:   // TODO: STS
    case 0x94:   // TODO: CCH
    case 0x95:   // TODO: MW
    case 0x96:   // TODO: SPA
    case 0x97:   // TODO: EPA
    case 0x98:   // TODO: SOS
    case 0x99:   // TODO: SGCI
        break;
    case 0x9a:   // DECID -- Identify Terminal
        ttywrite(vtiden, cast(int)strlen(vtiden), 0);
        break;
    case 0x9b:   // TODO: CSI
    case 0x9c:   // TODO: ST
        break;
    case 0x90:   // DCS -- Device Control String
    case 0x9d:   // OSC -- Operating System Command
    case 0x9e:   // PM -- Privacy Message
    case 0x9f:   // APC -- Application Program Command
        tstrsequence(ascii);
        return;
    default:
        break;
    }
    term.esc &= ~(ESC_STR_END | ESC_STR);
}

int utf8decode(const(char)* c, Rune* u, size_t clen) {
    ubyte[4] seq;
    size_t len;
    
    if (clen == 0)
        return 0;
    
    seq[0] = cast(ubyte)c[0];
    
    // Determine UTF-8 sequence length
    if ((seq[0] & 0x80) == 0) {
        *u = seq[0];
        return 1;
    } else if ((seq[0] & 0xE0) == 0xC0) {
        len = 2;
    } else if ((seq[0] & 0xF0) == 0xE0) {
        len = 3;
    } else if ((seq[0] & 0xF8) == 0xF0) {
        len = 4;
    } else {
        *u = UTF_INVALID;
        return 1;
    }
    
    if (clen < len) {
        *u = UTF_INVALID;
        return 1;
    }
    
    for (size_t i = 1; i < len; i++) {
        seq[i] = cast(ubyte)c[i];
        if ((seq[i] & 0xC0) != 0x80) {
            *u = UTF_INVALID;
            return 1;
        }
    }
    
    // Decode UTF-8 sequence
    switch (len) {
    case 2:
        *u = ((seq[0] & 0x1F) << 6) | (seq[1] & 0x3F);
        break;
    case 3:
        *u = ((seq[0] & 0x0F) << 12) | ((seq[1] & 0x3F) << 6) | (seq[2] & 0x3F);
        break;
    case 4:
        *u = ((seq[0] & 0x07) << 18) | ((seq[1] & 0x3F) << 12) | 
             ((seq[2] & 0x3F) << 6) | (seq[3] & 0x3F);
        break;
    default:
        return 1;
    }
    
    // Validate the decoded codepoint
    if (*u > 0x10FFFF || (*u >= 0xD800 && *u <= 0xDFFF)) {
        *u = UTF_INVALID;
    }
    
    return cast(int)len;
}

extern(C) void ttywrite(const(char)* s, size_t n, int may_echo) {
    fd_set wfd, rfd;
    ssize_t r;
    size_t lim = 256;
    
    if (IS_SET(TermMode.ECHO) && may_echo) {
        twrite(s, cast(int)n, 1);
    }
    
    if (!IS_SET(TermMode.CRLF)) {
        ttywriteraw(s, n);
        return;
    }
    
    // Handle CRLF conversion
    // Stub implementation
    ttywriteraw(s, n);
}

void ttywriteraw(const(char)* s, size_t n) {
    fd_set wfd;
    fd_set* rfd;
    ssize_t r;
    size_t lim = 256;
    
    if (n > lim) {
        // Handle flow control
        n = lim;
    }
    
    // Write to tty
    for (;;) {
        r = write(cmdfd, s, n);
        //printf("ttywriteraw: write returned %zd\n", r);
        //fflush(stdout);
        if (r < 0) {
            if (errno == EAGAIN || errno == EINTR) {
                continue;
            }
            die("write error on tty: %s\n", strerror(errno));
        }
        if (r < cast(ssize_t)n) {
            s += r;
            n -= r;
        } else {
            break;
        }
    }
}

extern(C) void ttyresize(int tw, int th) {
    winsize w;
    
    w.ws_row = cast(ushort)term.row;
    w.ws_col = cast(ushort)term.col;
    w.ws_xpixel = cast(ushort)tw;
    w.ws_ypixel = cast(ushort)th;
    
    if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
        fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
}

// Function declarations
extern(C):
void redraw();
void draw();
void drawregion(int, int, int, int);
void tfulldirt() {
    tsetdirt(0, term.row-1);
}

void printscreen(const(Arg)*);
void printsel(const(Arg)*);
void sendbreak(const(Arg)*);
void toggleprinter(const(Arg)*);

int tattrset(int);
int tisaltscr();
void tsetdirtattr(int);
void ttyhangup();
extern(C) void resettitle();

extern(C) int tisaltscr() {
    return IS_SET(TermMode.ALTSCREEN) ? 1 : 0;
}

void selclear();
void selremove();
void selstart(int, int, int);
void selextend(int, int, int, int);
int selected(int x, int y) {
    if (sel.mode == SelMode.EMPTY || sel.ob.x == -1 ||
            sel.alt != IS_SET(TermMode.ALTSCREEN)) {
        return 0;
    }

    int result;
    if (sel.type == SelType.RECTANGULAR) {
        result = between(y, sel.nb.y, sel.ne.y) && between(x, sel.nb.x, sel.ne.x);
    } else {
        result = between(y, sel.nb.y, sel.ne.y)
            && (y != sel.nb.y || x >= sel.nb.x)
            && (y != sel.ne.y || x <= sel.ne.x);
    }
    
    return result;
}
char* getsel() {
    char* str;
    char* ptr;
    int y, bufsize, lastx, linelen;
    const(Glyph)* gp;
    const(Glyph)* last;

    if (sel.ob.x == -1)
        return null;

    bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
    ptr = str = cast(char*)xmalloc(bufsize);

    /* append every set & selected glyph to the selection */
    for (y = sel.nb.y; y <= sel.ne.y; y++) {
        if ((linelen = tlinelen(y)) == 0) {
            *ptr++ = '\n';
            continue;
        }

        if (sel.type == SelType.RECTANGULAR) {
            gp = &TLINE(y)[sel.nb.x];
            lastx = sel.ne.x;
        } else {
            gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0];
            lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
        }
        last = &TLINE(y)[min(lastx, linelen-1)];
        while (last >= gp && last.u == ' ')
            --last;

        for ( ; gp <= last; ++gp) {
            if (gp.mode & GlyphAttribute.WDUMMY)
                continue;

            ptr += utf8encode(gp.u, ptr);
        }

        /*
         * Copy and pasting of line endings is inconsistent
         * in the inconsistent terminal and GUI world.
         * The best solution seems like to produce '\n' when
         * something is copied from st and convert '\n' to
         * '\r', when something to be pasted is received by
         * st.
         * FIXME: Fix the computer world.
         */
        if ((y < sel.ne.y || lastx >= linelen) &&
            (!(last.mode & GlyphAttribute.WRAP) || sel.type == SelType.RECTANGULAR))
            *ptr++ = '\n';
    }
    *ptr = 0;
    return str;
}

size_t utf8encode(Rune, char*);

int xgetcolor(int x, ubyte* r, ubyte* g, ubyte* b);

// UTF-8 constants
__gshared immutable ubyte[UTF_SIZ + 1] utfbyte = [0x80, 0, 0xC0, 0xE0, 0xF0];
__gshared immutable ubyte[UTF_SIZ + 1] utfmask = [0xC0, 0x80, 0xE0, 0xF0, 0xF8];
__gshared immutable Rune[UTF_SIZ + 1] utfmin = [0, 0, 0x80, 0x800, 0x10000];
__gshared immutable Rune[UTF_SIZ + 1] utfmax = [0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF];

// Helper macro for scrollback

static if (isPatchEnabled!"BOXDRAW_PATCH") {
    int isboxdraw(Rune);
    ushort boxdrawindex(const(Glyph)*);
    void boxdraw_xinit(Display*, Colormap, XftDraw*, Visual*);
    void drawboxes(int, int, int, int, XftColor*, XftColor*, const(XftGlyphFontSpec)*, int);
}

// Global variables - defined in config module
extern(C) __gshared {
    extern char* utmp;
    extern char* scroll;
    extern char* stty_args;
    extern char* vtiden;
    extern wchar* worddelimiters;
    static if (isPatchEnabled!"KEYBOARDSELECT_PATCH" && isPatchEnabled!"REFLOW_PATCH") {
        extern wchar* kbds_sdelim;
        extern wchar* kbds_ldelim;
    }
    extern int allowaltscreen;
    extern int allowwindowops;
    extern char* termname;
    extern uint tabspaces;
    extern uint defaultfg;
    extern uint defaultbg;
    extern uint defaultcs;
    static if (isPatchEnabled!"EXTERNALPIPE_PATCH") {
        int extpipeactive;
    }
    
    static if (isPatchEnabled!"BOXDRAW_PATCH") {
        extern const int boxdraw, boxdraw_bold, boxdraw_braille;
    }
    static if (isPatchEnabled!"ALPHA_PATCH") {
        extern float alpha;
        static if (isPatchEnabled!"ALPHA_FOCUS_HIGHLIGHT_PATCH") {
            extern float alphaUnfocused;
        }
    }
    
    DC dc;
    XWindow xw;
    XSelection xsel;
    TermWindow win;
    Term term;
}

// Additional functions needed by x.d
extern(C) int tattrset(int attr) {
    // Check if any terminal cells have the given attribute
    for (int i = 0; i < term.row; i++) {
        for (int j = 0; j < term.col; j++) {
            if (term.line[i][j].mode & attr)
                return 1;
        }
    }
    return 0;
}

extern(C) void tsetdirtattr(int attr) {
    // Mark lines dirty that contain the given attribute
    for (int i = 0; i < term.row; i++) {
        for (int j = 0; j < term.col; j++) {
            if (term.line[i][j].mode & attr) {
                term.dirty[i] = 1;
                break;
            }
        }
    }
}

extern(C) void ttyhangup() {
    // Send SIGHUP to shell
    import core.sys.posix.signal : kill, SIGHUP;
    
    if (pid != 0) {
        kill(pid, SIGHUP);
    }
}

// Helper functions
void tmoveto(int x, int y) {
    int miny, maxy;

    if (term.c.state & CursorState.ORIGIN) {
        miny = term.top;
        maxy = term.bot;
    } else {
        miny = 0;
        maxy = term.row - 1;
    }
    term.c.state &= ~CursorState.WRAPNEXT;
    term.c.x = clamp(x, 0, term.col-1);
    term.c.y = clamp(y, miny, maxy);
}

void tsetchar(Rune u, Glyph* attr, int x, int y) {
    if (term.line[y][x].mode & GlyphAttribute.WIDE) {
        if (x+1 < term.col) {
            term.line[y][x+1].u = ' ';
            term.line[y][x+1].mode &= ~GlyphAttribute.WDUMMY;
        }
    } else if (term.line[y][x].mode & GlyphAttribute.WDUMMY) {
        term.line[y][x-1].u = ' ';
        term.line[y][x-1].mode &= ~GlyphAttribute.WIDE;
    }

    term.dirty[y] = 1;
    term.line[y][x] = *attr;
    term.line[y][x].u = u;
    
    static if (isPatchEnabled!"BOXDRAW_PATCH") {
        import patch.boxdraw : isboxdraw;
        if (isboxdraw(u))
            term.line[y][x].mode |= ATTR_BOXDRAW;
    }
}

void tnewline(int first_col) {
    int y = term.c.y;

    if (y == term.bot) {
        static if (isPatchEnabled!"SCROLLBACK_PATCH") {
            tscrollup(term.top, 1, 1);  // Save to history
        } else {
            tscrollup(term.top, 1);
        }
    } else {
        y++;
    }
    tmoveto(first_col ? 0 : term.c.x, y);
}

void tputtab(int n) {
    uint x = term.c.x;

    if (n > 0) {
        while (x < cast(uint)term.col && n--)
            for (++x; x < cast(uint)term.col && !term.tabs[x]; ++x) {}
    } else if (n < 0) {
        while (x > 0 && n++)
            for (--x; x > 0 && !term.tabs[x]; --x) {}
    }
    term.c.x = min(x, cast(uint)term.col - 1);
}

static if (isPatchEnabled!"SCROLLBACK_PATCH") {
    void tscrollup(int orig, int n, int copyhist = 0) {
        int i;
        Line temp;
        
        trace("tscrollup (SCROLLBACK): orig=", orig, " n=", n, " copyhist=", copyhist, 
              " term.bot=", term.bot, " term.top=", term.top);
        
        static if (isPatchEnabled!"OPENURLONCLICK_PATCH") {
            restoremousecursor();
        }
        
        n = clamp(n, 0, term.bot-orig+1);
        
        if (copyhist && !IS_SET(TermMode.ALTSCREEN)) {
            for (i = 0; i < n; i++) {
                term.histi = (term.histi + 1) % HISTSIZE;
                temp = term.hist[term.histi];
                term.hist[term.histi] = term.line[orig+i];
                term.line[orig+i] = temp;
            }
            static if (isPatchEnabled!"REFLOW_PATCH") {
                term.histf = min(term.histf + n, HISTSIZE);
            } else {
                term.histn = min(term.histn + n, HISTSIZE);
            }
            
            if (term.scr > 0 && term.scr < HISTSIZE)
                term.scr = min(term.scr + n, HISTSIZE-1);
        }
        
        tclearregion(0, orig, term.col-1, orig+n-1);
        tsetdirt(orig+n, term.bot);
        
        for (i = orig; i <= term.bot-n; i++) {
            temp = term.line[i];
            term.line[i] = term.line[i+n];
            term.line[i+n] = temp;
        }
        
        selscroll(orig, -n);
    }
} else {
    void tscrollup(int orig, int n) {
        int i;
        Line temp;
        
        n = clamp(n, 0, term.bot-orig+1);
        
        tclearregion(0, orig, term.col-1, orig+n-1);
        tsetdirt(orig+n, term.bot);
        
        for (i = orig; i <= term.bot-n; i++) {
            temp = term.line[i];
            term.line[i] = term.line[i+n];
            term.line[i+n] = temp;
        }
        
        selscroll(orig, -n);
    }
}

void tscrolldown(int orig, int n) {
    int i;
    Line temp;

    n = clamp(n, 0, term.bot-orig+1);

    tsetdirt(orig, term.bot-n);
    tclearregion(0, term.bot-n+1, term.col-1, term.bot);

    for (i = term.bot; i >= orig+n; i--) {
        temp = term.line[i];
        term.line[i] = term.line[i-n];
        term.line[i-n] = temp;
    }

    selscroll(orig, n);
}

void csireset() {
    memset(&csiescseq, 0, CSIEscape.sizeof);
}

void strreset() {
    strescseq.buf = cast(char*)xrealloc(strescseq.buf, STR_BUF_SIZ);
    strescseq.siz = STR_BUF_SIZ;
    strescseq.len = 0;
    strescseq.type = ' ';
    strescseq.narg = 0;
}

void tstrsequence(uint c) {
    switch (c) {
    case 0x90:   // DCS -- Device Control String
        c = 'P';
        break;
    case 0x9f:   // APC -- Application Program Command
        c = '_';
        break;
    case 0x9e:   // PM -- Privacy Message
        c = '^';
        break;
    case 0x9d:   // OSC -- Operating System Command
        c = ']';
        break;
    default:
        return;
    }
    strreset();
    strescseq.type = cast(char)c;
    term.esc |= ESC_STR;
}

void selscroll(int orig, int n) {
    if (sel.ob.x == -1 || sel.alt != IS_SET(TermMode.ALTSCREEN))
        return;

    if (between(sel.nb.y, orig, term.bot) != between(sel.ne.y, orig, term.bot)) {
        selclear();
    } else if (between(sel.nb.y, orig, term.bot)) {
        sel.ob.y += n;
        sel.oe.y += n;
        if (sel.ob.y < term.top || sel.ob.y > term.bot ||
            sel.oe.y < term.top || sel.oe.y > term.bot) {
            selclear();
        } else {
            selnormalize();
        }
    }
}

void selstart(int col, int row, int snap) {
    
    selclear();
    sel.mode = SelMode.EMPTY;
    sel.type = SelType.REGULAR;
    sel.alt = IS_SET(TermMode.ALTSCREEN);
    sel.snap = snap;
    sel.oe.x = sel.ob.x = col;
    sel.oe.y = sel.ob.y = row;
    selnormalize();

    if (sel.snap != 0)
        sel.mode = SelMode.READY;
    
    tsetdirt(sel.nb.y, sel.ne.y);
}

void selextend(int col, int row, int type, int done) {
    int oldey, oldex, oldsby, oldsey, oldtype;

    if (sel.mode == SelMode.IDLE) {
        return;
    }
    if (done && sel.mode == SelMode.EMPTY) {
        selclear();
        return;
    }

    oldey = sel.oe.y;
    oldex = sel.oe.x;
    oldsby = sel.nb.y;
    oldsey = sel.ne.y;
    oldtype = sel.type;

    sel.oe.x = col;
    sel.oe.y = row;
    sel.type = type;
    selnormalize();

    if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SelMode.EMPTY)
        tsetdirt(min(sel.nb.y, oldsby), max(sel.ne.y, oldsey));

    sel.mode = done ? SelMode.IDLE : SelMode.READY;
}

void selclear() {
    if (sel.ob.x == -1)
        return;
    sel.mode = SelMode.IDLE;
    sel.ob.x = -1;
    tsetdirt(sel.nb.y, sel.ne.y);
}

void selnormalize() {
    int i;

    if (sel.type == SelType.REGULAR && sel.ob.y != sel.oe.y) {
        sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
        sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
    } else {
        sel.nb.x = min(sel.ob.x, sel.oe.x);
        sel.ne.x = max(sel.ob.x, sel.oe.x);
    }
    sel.nb.y = min(sel.ob.y, sel.oe.y);
    sel.ne.y = max(sel.ob.y, sel.oe.y);

    selsnap(&sel.nb.x, &sel.nb.y, -1);
    selsnap(&sel.ne.x, &sel.ne.y, +1);

    /* expand selection over line breaks */
    if (sel.type == SelType.RECTANGULAR)
        return;
    i = tlinelen(sel.nb.y);
    if (i < sel.nb.x)
        sel.nb.x = i;
    if (tlinelen(sel.ne.y) <= sel.ne.x)
        sel.ne.x = term.col - 1;
}

void selsnap(int* x, int* y, int direction) {
    // Simple implementation - could be enhanced for word/line selection
    switch (sel.snap) {
    case SnapMode.WORD:
        // Implement word snapping
        wchar_t prevdelim = direction > 0 ? ' ' : '\0';
        wchar_t curdelim;
        
        for (;;) {
            if (direction < 0 && *x <= 0) {
                if (*y > 0 && term.line[*y - 1][term.col - 1].mode & ATTR_WRAP) {
                    *y -= 1;
                    *x = term.col - 1;
                } else {
                    break;
                }
            }
            if (direction > 0 && *x >= term.col - 1) {
                if (*y < term.row - 1 && term.line[*y][term.col - 1].mode & ATTR_WRAP) {
                    *y += 1;
                    *x = 0;
                } else {
                    break;
                }
            }

            if (term.line[*y][*x].mode & GlyphAttribute.WDUMMY) {
                *x += direction;
                continue;
            }

            curdelim = term.line[*y][*x].u;
            if (ISDELIM(curdelim) != ISDELIM(prevdelim))
                break;

            *x += direction;
            prevdelim = curdelim;
        }
        break;
    case SnapMode.LINE:
        *x = (direction < 0) ? 0 : term.col - 1;
        break;
    default:
        break;
    }
}

void tsetdirt(int top, int bot) {
    int i;
    top = clamp(top, 0, term.row-1);
    bot = clamp(bot, 0, term.row-1);
    for (i = top; i <= bot; i++)
        term.dirty[i] = 1;
}

// Implementation of missing functions

extern(C) ptrdiff_t xwrite(int fd, const(char)* s, size_t len) {
    size_t aux = len;
    ptrdiff_t r;
    
    while (len > 0) {
        r = write(fd, s, len);
        if (r < 0)
            return r;
        len -= r;
        s += r;
    }
    
    return aux;
}

extern(C) int tlinelen(int y) {
    int i = term.col;
    
    static if (isPatchEnabled!"SCROLLBACK_PATCH") {
        if (TLINE(y)[i - 1].mode & GlyphAttribute.WRAP)
            return i;
        
        while (i > 0 && TLINE(y)[i - 1].u == ' ')
            --i;
    } else {
        if (term.line[y][i - 1].mode & GlyphAttribute.WRAP)
            return i;
        
        while (i > 0 && term.line[y][i - 1].u == ' ')
            --i;
    }
    
    return i;
}

char utf8encodebyte(Rune u, size_t i) {
    return cast(char)(utfbyte[i] | (u & ~utfmask[i]));
}

size_t utf8validate(Rune* u, size_t i) {
    if (!between(*u, utfmin[i], utfmax[i]) || between(*u, 0xD800, 0xDFFF))
        *u = UTF_INVALID;
    for (i = 1; *u > utfmax[i]; ++i) {}
    
    return i;
}

extern(C) size_t utf8encode(Rune u, char* c) {
    size_t len, i;
    
    len = utf8validate(&u, 0);
    if (len > UTF_SIZ)
        return 0;
    
    for (i = len - 1; i != 0; --i) {
        c[i] = utf8encodebyte(u, 0);
        u >>= 6;
    }
    c[0] = utf8encodebyte(u, len);
    
    return len;
}

// Terminal manipulation functions

void tmoveato(int x, int y) {
    int actual_y = y + ((term.c.state & CursorState.ORIGIN) ? term.top : 0);
    trace("tmoveato: x=", x, " y=", y, " actual_y=", actual_y, " term.row=", term.row, " term.bot=", term.bot);
    tmoveto(x, actual_y);
}

void tclearregion(int x1, int y1, int x2, int y2) {
    int x, y, temp;


    if (x1 > x2) {
        temp = x1;
        x1 = x2;
        x2 = temp;
    }
    if (y1 > y2) {
        temp = y1;
        y1 = y2;
        y2 = temp;
    }

    x1 = clamp(x1, 0, term.col-1);
    x2 = clamp(x2, 0, term.col-1);
    y1 = clamp(y1, 0, term.row-1);
    y2 = clamp(y2, 0, term.row-1);

    for (y = y1; y <= y2; y++) {
        term.dirty[y] = 1;
        for (x = x1; x <= x2; x++) {
            Glyph* gp = &term.line[y][x];
            if (selected(x, y))
                selclear();
            gp.fg = term.c.attr.fg;
            gp.bg = term.c.attr.bg;
            gp.mode = 0;
            gp.u = ' ';
        }
    }
}

void tdeletechar(int n) {
    int dst, src, size;
    Glyph* line;

    n = clamp(n, 0, term.col - term.c.x);

    dst = term.c.x;
    src = term.c.x + n;
    size = term.col - src;
    line = term.line[term.c.y];

    memmove(&line[dst], &line[src], size * Glyph.sizeof);
    tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
}

void tinsertblank(int n) {
    int dst, src, size;
    Glyph* line;

    n = clamp(n, 0, term.col - term.c.x);

    dst = term.c.x + n;
    src = term.c.x;
    size = term.col - dst;
    line = term.line[term.c.y];

    memmove(&line[dst], &line[src], size * Glyph.sizeof);
    tclearregion(src, term.c.y, dst - 1, term.c.y);
}

void tinsertblankline(int n) {
    if (between(term.c.y, term.top, term.bot))
        tscrolldown(term.c.y, n);
}

void tdeleteline(int n) {
    if (between(term.c.y, term.top, term.bot))
        tscrollup(term.c.y, n);
}

void tsetattr(const(int)* attr, int l) {
    int i;
    uint idx;
    
    // Handle CSI m with no parameters (reset)
    if (l == 0) {
        term.c.attr.mode &= ~(GlyphAttribute.BOLD | GlyphAttribute.FAINT |
                              GlyphAttribute.ITALIC | GlyphAttribute.UNDERLINE | GlyphAttribute.BLINK |
                              GlyphAttribute.REVERSE | GlyphAttribute.INVISIBLE | GlyphAttribute.STRUCK);
        term.c.attr.fg = defaultfg;
        term.c.attr.bg = defaultbg;
        return;
    }

    for (i = 0; i < l; i++) {
        switch (attr[i]) {
        case 0:
            term.c.attr.mode &= ~(GlyphAttribute.BOLD | GlyphAttribute.FAINT |
                                  GlyphAttribute.ITALIC | GlyphAttribute.UNDERLINE | GlyphAttribute.BLINK |
                                  GlyphAttribute.REVERSE | GlyphAttribute.INVISIBLE | GlyphAttribute.STRUCK);
            term.c.attr.fg = defaultfg;
            term.c.attr.bg = defaultbg;
            break;
        case 1:
            term.c.attr.mode |= GlyphAttribute.BOLD;
            break;
        case 2:
            term.c.attr.mode |= GlyphAttribute.FAINT;
            break;
        case 3:
            term.c.attr.mode |= GlyphAttribute.ITALIC;
            break;
        case 4:
            term.c.attr.mode |= GlyphAttribute.UNDERLINE;
            break;
        case 5: /* slow blink */
        case 6: /* rapid blink */
            term.c.attr.mode |= GlyphAttribute.BLINK;
            break;
        case 7:
            term.c.attr.mode |= GlyphAttribute.REVERSE;
            break;
        case 8:
            term.c.attr.mode |= GlyphAttribute.INVISIBLE;
            break;
        case 9:
            term.c.attr.mode |= GlyphAttribute.STRUCK;
            break;
        case 22:
            term.c.attr.mode &= ~(GlyphAttribute.BOLD | GlyphAttribute.FAINT);
            break;
        case 23:
            term.c.attr.mode &= ~GlyphAttribute.ITALIC;
            break;
        case 24:
            term.c.attr.mode &= ~GlyphAttribute.UNDERLINE;
            break;
        case 25:
            term.c.attr.mode &= ~GlyphAttribute.BLINK;
            break;
        case 27:
            term.c.attr.mode &= ~GlyphAttribute.REVERSE;
            break;
        case 28:
            term.c.attr.mode &= ~GlyphAttribute.INVISIBLE;
            break;
        case 29:
            term.c.attr.mode &= ~GlyphAttribute.STRUCK;
            break;
        case 38:
            if (i + 2 < l && attr[i + 1] == 5) {
                term.c.attr.fg = attr[i + 2];
                i += 2;
            } else if (i + 4 < l && attr[i + 1] == 2) {
                idx = attr[i + 2] << 16 | attr[i + 3] << 8 | attr[i + 4];
                term.c.attr.fg = TRUECOLOR(cast(ubyte)attr[i + 2], cast(ubyte)attr[i + 3], cast(ubyte)attr[i + 4]);
                i += 4;
            }
            break;
        case 39:
            term.c.attr.fg = defaultfg;
            break;
        case 48:
            if (i + 2 < l && attr[i + 1] == 5) {
                term.c.attr.bg = attr[i + 2];
                i += 2;
            } else if (i + 4 < l && attr[i + 1] == 2) {
                idx = attr[i + 2] << 16 | attr[i + 3] << 8 | attr[i + 4];
                term.c.attr.bg = TRUECOLOR(cast(ubyte)attr[i + 2], cast(ubyte)attr[i + 3], cast(ubyte)attr[i + 4]);
                i += 4;
            }
            break;
        case 49:
            term.c.attr.bg = defaultbg;
            break;
        default:
            if (between(attr[i], 30, 37)) {
                term.c.attr.fg = attr[i] - 30;
            } else if (between(attr[i], 40, 47)) {
                term.c.attr.bg = attr[i] - 40;
            } else if (between(attr[i], 90, 97)) {
                term.c.attr.fg = attr[i] - 90 + 8;
            } else if (between(attr[i], 100, 107)) {
                term.c.attr.bg = attr[i] - 100 + 8;
            } else {
                fprintf(stderr, "erresc: unhandled CSI SGR %d\n", attr[i]);
            }
            break;
        }
    }
}

void tsetscroll(int t, int b) {
    int temp;

    t = clamp(t, 0, term.row-1);
    b = clamp(b, 0, term.row-1);
    if (t > b) {
        temp = t;
        t = b;
        b = temp;
    }
    term.top = t;
    term.bot = b;
}

void tdump() {
    int i;

    for (i = 0; i < term.row; ++i)
        tdumpline(i);
}

void tdumpline(int n) {
    int i;
    Glyph* line;
    char[UTF_SIZ] buf;
    int len;

    line = term.line[n];
    for (i = 0; i < term.col; ++i) {
        if (line[i].u == ' ')
            continue;

        len = cast(int)utf8encode(line[i].u, buf.ptr);
        write(1, buf.ptr, len);
    }
    printf("\n");
}

void tdumpsel() {
    char* ptr;

    if ((ptr = getsel()) !is null) {
        printf("%s", ptr);
        free(ptr);
    }
}

void csidump() {
    size_t i;
    uint c;

    fprintf(stderr, "ESC[");
    for (i = 0; i < csiescseq.len; i++) {
        c = csiescseq.buf[i] & 0xff;
        if (isprint(c)) {
            putc(c, stderr);
        } else if (c == '\n') {
            fprintf(stderr, "(\\n)");
        } else if (c == '\r') {
            fprintf(stderr, "(\\r)");
        } else if (c == 0x1b) {
            fprintf(stderr, "(\\e)");
        } else {
            fprintf(stderr, "(%02x)", c);
        }
    }
    putc('\n', stderr);
}

void tswapscreen() {
    Line* tmp = term.line;

    term.line = term.alt;
    term.alt = tmp;
    term.mode ^= TermMode.ALTSCREEN;
    tfulldirt();
}

void tcursor(int mode) {
    static TCursor[2] c;
    int alt = IS_SET(TermMode.ALTSCREEN) ? 1 : 0;

    if (mode == CursorMovement.SAVE) {
        c[alt] = term.c;
    } else if (mode == CursorMovement.LOAD) {
        term.c = c[alt];
        tmoveto(c[alt].x, c[alt].y);
    }
}

void tsetmode(int priv, int set, const(int)* args, int narg) {
    int* lim;
    int mode;
    int alt;

    for (lim = cast(int*)args + narg; args < lim; ++args) {
        if (priv) {
            switch (*args) {
            case 1: /* DECCKM -- Cursor key */
                xsetmode(set, WinMode.APPCURSOR);
                break;
            case 5: /* DECSCNM -- Reverse video */
                mode = IS_SET(WinMode.REVERSE) ? 1 : 0;
                if (mode != set) {
                    xsetmode(set, WinMode.REVERSE);
                    redraw();
                }
                break;
            case 6: /* DECOM -- Origin */
                term.c.state = set ? (term.c.state | CursorState.ORIGIN) : (term.c.state & ~CursorState.ORIGIN);
                tmoveato(0, 0);
                break;
            case 7: /* DECAWM -- Auto wrap */
                term.mode = set ? (term.mode | TermMode.WRAP) : (term.mode & ~TermMode.WRAP);
                break;
            case 0:  /* Error (IGNORED) */
            case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
            case 3:  /* DECCOLM -- Column  (IGNORED) */
            case 4:  /* DECSCLM -- Scroll (IGNORED) */
            case 8:  /* DECARM -- Auto repeat (IGNORED) */
            case 18: /* DECPFF -- Printer feed (IGNORED) */
            case 19: /* DECPEX -- Printer extent (IGNORED) */
            case 42: /* DECNRCM -- National characters (IGNORED) */
            case 12: /* att610 -- Start blinking cursor (IGNORED) */
                break;
            case 25: /* DECTCEM -- Text Cursor Enable Mode */
                xsetmode(!set, WinMode.HIDE);
                break;
            case 9:    /* X10 mouse compatibility mode */
                xsetpointermotion(0);
                xsetmode(0, MOUSE);
                xsetmode(set, WinMode.MOUSEX10);
                break;
            case 1000: /* 1000: report button press */
                xsetpointermotion(0);
                xsetmode(0, MOUSE);
                xsetmode(set, WinMode.MOUSEBTN);
                break;
            case 1002: /* 1002: report motion on button press */
                xsetpointermotion(0);
                xsetmode(0, MOUSE);
                xsetmode(set, WinMode.MOUSEMOTION);
                break;
            case 1003: /* 1003: enable all mouse motions */
                xsetpointermotion(set);
                xsetmode(0, MOUSE);
                xsetmode(set, WinMode.MOUSEMANY);
                break;
            case 1004: /* 1004: send focus events to tty */
                xsetmode(set, WinMode.FOCUS);
                break;
            case 1006: /* 1006: extended reporting mode */
                xsetmode(set, WinMode.MOUSESGR);
                break;
            case 1034:
                xsetmode(set, WinMode.MODE_8BIT);
                break;
            case 1049: /* swap screen & set/restore cursor as xterm */
                if (!allowaltscreen)
                    break;
                tcursor((set) ? CursorMovement.SAVE : CursorMovement.LOAD);
                goto case 47;
            case 47: /* swap screen */
            case 1047:
                if (!allowaltscreen)
                    break;
                alt = IS_SET(TermMode.ALTSCREEN) ? 1 : 0;
                if (alt) {
                    tclearregion(0, 0, term.col-1, term.row-1);
                }
                if (set ^ alt) /* set is always 1 or 0 */
                    tswapscreen();
                if (*args != 1049)
                    break;
                goto case 1048;
            case 1048:
                tcursor((set) ? CursorMovement.SAVE : CursorMovement.LOAD);
                break;
            case 2004: /* 2004: bracketed paste mode */
                xsetmode(set, WinMode.BRCKTPASTE);
                break;
            /* Not implemented mouse modes. See comments there. */
            case 1001: /* mouse highlight mode; can hang the
                         terminal by design when implemented. */
            case 1005: /* UTF-8 mouse mode; will confuse
                         applications not supporting UTF-8
                         and luit. */
            case 1015: /* urxvt mangled mouse mode; incompatible
                         and can be mistaken for other control
                         codes. */
            case 7727: /* Application escape key mode (rxvt-unicode specific) */
                break;
            default:
                fprintf(stderr, "erresc: unknown private set/reset mode %d\n", *args);
                break;
            }
        } else {
            switch (*args) {
            case 0:  /* Error (IGNORED) */
                break;
            case 2:
                term.mode = set ? (term.mode | TermMode.INSERT) : (term.mode & ~TermMode.INSERT);
                break;
            case 4:  /* IRM -- Insertion-replacement */
                term.mode = set ? (term.mode | TermMode.INSERT) : (term.mode & ~TermMode.INSERT);
                break;
            case 12: /* SRM -- Send/Receive */
                term.mode = set ? (term.mode | TermMode.ECHO) : (term.mode & ~TermMode.ECHO);
                break;
            case 20: /* LNM -- Linefeed/new line */
                term.mode = set ? (term.mode | TermMode.CRLF) : (term.mode & ~TermMode.CRLF);
                break;
            default:
                fprintf(stderr, "erresc: unknown set/reset mode %d\n", *args);
                break;
            }
        }
    }
}

void csihandle() {
    char[40] buffer;
    int n = 0;

    // Debug log CSI sequence
    trace("csihandle: mode='", cast(char)csiescseq.mode[0], "' (0x", to!string(cast(uint)csiescseq.mode[0], 16), 
          ") priv=", csiescseq.priv, " narg=", csiescseq.narg);
    for (int i = 0; i < csiescseq.narg; i++) {
        trace("  arg[", i, "]=", csiescseq.arg[i]);
    }

    switch (csiescseq.mode[0]) {
    default:
    unknown:
        fprintf(stderr, "erresc: unknown csi ");
        csidump();
        break;
    case '@': /* ICH -- Insert <n> blank char */
        DEFAULT(csiescseq.arg[0], 1);
        tinsertblank(csiescseq.arg[0]);
        break;
    case 'A': /* CUU -- Cursor <n> Up */
        DEFAULT(csiescseq.arg[0], 1);
        tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
        break;
    case 'B': /* CUD -- Cursor <n> Down */
    case 'e': /* VPR --Cursor <n> Down */
        DEFAULT(csiescseq.arg[0], 1);
        tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
        break;
    case 'i': /* MC -- Media Copy */
        switch (csiescseq.arg[0]) {
        case 0:
            tdump();
            break;
        case 1:
            tdumpline(term.c.y);
            break;
        case 2:
            tdumpsel();
            break;
        case 4:
            term.mode &= ~TermMode.PRINT;
            break;
        case 5:
            term.mode |= TermMode.PRINT;
            break;
        default:
            break;
        }
        break;
    case 'c': /* DA -- Device Attributes */
        if (csiescseq.arg[0] == 0) {
            if (csiescseq.priv) {
                /* Secondary DA -- ignore for now */
                break;
            }
            ttywrite(vtiden, strlen(vtiden), 0);
        }
        break;
    case 'q': /* DECSCUSR and other 'q' sequences */
        if (csiescseq.priv) {
            /* Tertiary DA or other private 'q' sequences -- ignore */
            break;
        }
        /* Other 'q' sequences */
        goto unknown;
    case 'b': /* REP -- if last char is printable print it <n> more times */
        DEFAULT(csiescseq.arg[0], 1);
        if (csiescseq.arg[0] > 0) csiescseq.arg[0] = min(csiescseq.arg[0], 65535);
        if (term.lastc)
            while (csiescseq.arg[0]-- > 0)
                tputc(term.lastc);
        break;
    case 'C': /* CUF -- Cursor <n> Forward */
    case 'a': /* HPR -- Cursor <n> Forward */
        DEFAULT(csiescseq.arg[0], 1);
        tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
        break;
    case 'D': /* CUB -- Cursor <n> Backward */
        DEFAULT(csiescseq.arg[0], 1);
        tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
        break;
    case 'E': /* CNL -- Cursor <n> Down and first col */
        DEFAULT(csiescseq.arg[0], 1);
        tmoveto(0, term.c.y+csiescseq.arg[0]);
        break;
    case 'F': /* CPL -- Cursor <n> Up and first col */
        DEFAULT(csiescseq.arg[0], 1);
        tmoveto(0, term.c.y-csiescseq.arg[0]);
        break;
    case 'g': /* TBC -- Tabulation clear */
        switch (csiescseq.arg[0]) {
        case 0: /* clear current tab stop */
            term.tabs[term.c.x] = 0;
            break;
        case 3: /* clear all the tabs */
            memset(term.tabs, 0, term.col * int.sizeof);
            break;
        default:
            goto unknown;
        }
        break;
    case 'G': /* CHA -- Move to <col> */
    case '`': /* HPA */
        DEFAULT(csiescseq.arg[0], 1);
        tmoveto(csiescseq.arg[0]-1, term.c.y);
        break;
    case 'H': /* CUP -- Move to <row> <col> */
    case 'f': /* HVP */
        DEFAULT(csiescseq.arg[0], 1);
        DEFAULT(csiescseq.arg[1], 1);
        tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
        break;
    case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
        DEFAULT(csiescseq.arg[0], 1);
        tputtab(csiescseq.arg[0]);
        break;
    case 'J': /* ED -- Clear screen */
        switch (csiescseq.arg[0]) {
        case 0: /* below */
            tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
            if (term.c.y < term.row-1)
                tclearregion(0, term.c.y+1, term.col-1, term.row-1);
            break;
        case 1: /* above */
            if (term.c.y > 0)
                tclearregion(0, 0, term.col-1, term.c.y-1);
            tclearregion(0, term.c.y, term.c.x, term.c.y);
            break;
        case 2: /* all */
            tclearregion(0, 0, term.col-1, term.row-1);
            break;
        default:
            goto unknown;
        }
        break;
    case 'K': /* EL -- Clear line */
        switch (csiescseq.arg[0]) {
        case 0: /* right */
            tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
            break;
        case 1: /* left */
            tclearregion(0, term.c.y, term.c.x, term.c.y);
            break;
        case 2: /* all */
            tclearregion(0, term.c.y, term.col-1, term.c.y);
            break;
        default:
            break;
        }
        break;
    case 'S': /* SU -- Scroll <n> line up */
        DEFAULT(csiescseq.arg[0], 1);
        tscrollup(term.top, csiescseq.arg[0]);
        break;
    case 'T': /* SD -- Scroll <n> line down */
        DEFAULT(csiescseq.arg[0], 1);
        tscrolldown(term.top, csiescseq.arg[0]);
        break;
    case 'L': /* IL -- Insert <n> blank lines */
        DEFAULT(csiescseq.arg[0], 1);
        tinsertblankline(csiescseq.arg[0]);
        break;
    case 'l': /* RM -- Reset Mode */
        tsetmode(csiescseq.priv, 0, csiescseq.arg.ptr, csiescseq.narg);
        break;
    case 'M': /* DL -- Delete <n> lines */
        DEFAULT(csiescseq.arg[0], 1);
        tdeleteline(csiescseq.arg[0]);
        break;
    case 'X': /* ECH -- Erase <n> char */
        DEFAULT(csiescseq.arg[0], 1);
        tclearregion(term.c.x, term.c.y,
                term.c.x + csiescseq.arg[0] - 1, term.c.y);
        break;
    case 'P': /* DCH -- Delete <n> char */
        DEFAULT(csiescseq.arg[0], 1);
        tdeletechar(csiescseq.arg[0]);
        break;
    case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
        DEFAULT(csiescseq.arg[0], 1);
        tputtab(-csiescseq.arg[0]);
        break;
    case 'd': /* VPA -- Move to <row> */
        DEFAULT(csiescseq.arg[0], 1);
        tmoveato(term.c.x, csiescseq.arg[0]-1);
        break;
    case 'h': /* SM -- Set terminal mode */
        tsetmode(csiescseq.priv, 1, csiescseq.arg.ptr, csiescseq.narg);
        break;
    case 'm': /* SGR -- Terminal attribute (color) */
        tsetattr(csiescseq.arg.ptr, csiescseq.narg);
        break;
    case 'n': /* DSR – Device Status Report (cursor position) */
        if (csiescseq.arg[0] == 6) {
            n = snprintf(buffer.ptr, buffer.sizeof, "\033[%d;%dR",
                    term.c.y+1, term.c.x+1);
            ttywrite(buffer.ptr, n, 0);
        }
        break;
    case 'p': /* DECRQM -- Request Mode */
        if (csiescseq.priv) {
            /* Mode status request - respond with mode not recognized */
            /* Format: CSI ? mode ; value $ y 
               where value is:
               0 - not recognized
               1 - set
               2 - reset
               3 - permanently set
               4 - permanently reset */
            DEFAULT(csiescseq.arg[0], 0);
            n = snprintf(buffer.ptr, buffer.sizeof, "\033[?%d;0$y", csiescseq.arg[0]);
            ttywrite(buffer.ptr, n, 0);
        }
        break;
    case 'r': /* DECSTBM -- Set Scrolling Region */
        if (csiescseq.priv) {
            goto unknown;
        } else {
            DEFAULT(csiescseq.arg[0], 1);
            DEFAULT(csiescseq.arg[1], term.row);
            tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
            tmoveto(0, 0);
        }
        break;
    case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
        tcursor(CursorMovement.SAVE);
        break;
    case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
        tcursor(CursorMovement.LOAD);
        break;
    case ' ':
        switch (csiescseq.mode[1]) {
        case 'q': /* DECSCUSR -- Set Cursor Style */
            if (xsetcursor(csiescseq.arg[0]))
                goto unknown;
            break;
        default:
            goto unknown;
        }
        break;
    }
}

void csiparse() {
    char* p = csiescseq.buf.ptr;
    char* np;
    long v;
    
    csiescseq.narg = 0;
    if (*p == '?') {
        csiescseq.priv = 1;
        p++;
    } else if (*p == '>') {
        csiescseq.priv = 1;
        p++;
    }
    
    csiescseq.buf[csiescseq.len] = '\0';
    while (p < csiescseq.buf.ptr + csiescseq.len) {
        np = null;
        v = strtol(p, &np, 10);
        if (np == p)
            v = 0;
        if (v == long.max || v < 0)
            v = 0;
        csiescseq.arg[csiescseq.narg++] = cast(int)v;
        p = np;
        if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) {
            // Check for '$' separator used in mode status requests
            if (*p == '$' && p + 1 < csiescseq.buf.ptr + csiescseq.len) {
                p++; // Skip the '$'
            }
            break;
        }
        p++;
    }
    csiescseq.mode[0] = *p++;
    csiescseq.mode[1] = (p < csiescseq.buf.ptr + csiescseq.len) ? *p : '\0';
    
    // Removed verbose CSI parsed trace
}

void strparse() {
    int c;
    char* p = strescseq.buf;
    
    strescseq.narg = 0;
    strescseq.buf[strescseq.len] = '\0';
    
    if (*p == '\0')
        return;
    
    while (strescseq.narg < STR_ARG_SIZ) {
        strescseq.args[strescseq.narg++] = p;
        while ((c = *p) != ';' && c != '\0')
            ++p;
        if (c == '\0')
            return;
        *p++ = '\0';
    }
}

void strhandle() {
    char* p = null;
    int j, narg, par;
    
    term.esc &= ~(ESC_STR_END | ESC_STR);
    strparse();
    narg = strescseq.narg;
    par = narg ? atoi(strescseq.args[0]) : 0;
    
    switch (strescseq.type) {
    case ']': /* OSC -- Operating System Command */
        switch (par) {
        case 0:
        case 1:
        case 2:
            if (narg > 1) {
                import x : xsettitle;
                xsettitle(strescseq.args[1]);
            }
            return;
        case 4: /* color set */
            if (narg < 3)
                break;
            p = strescseq.args[2];
            /* FALLTHROUGH */
            goto case;
        case 104: /* color reset */
            j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
            if (xsetcolorname(j, p)) {
                import x : xloadcols;
                xloadcols();
                redraw();
            }
            return;
        default:
            break;
        }
        break;
    case 'k': /* old title set compatibility */
        import x : xsettitle;
        xsettitle(strescseq.args[0]);
        return;
    case 'P': /* DCS -- Device Control String */
    case '_': /* APC -- Application Program Command */
    case '^': /* PM -- Privacy Message */
        return;
    default:
        break;
    }
    
    fprintf(stderr, "erresc: unknown str ");
    strdump();
}

void strdump() {
    int i;
    uint c;
    
    fprintf(stderr, "ESC%c", strescseq.type);
    for (i = 0; i < strescseq.len; i++) {
        c = strescseq.buf[i] & 0xff;
        if (c == '\0') {
            putc('\n', stderr);
            return;
        } else if (isprint(c)) {
            putc(c, stderr);
        } else if (c == '\n') {
            fprintf(stderr, "(\\n)");
        } else if (c == '\r') {
            fprintf(stderr, "(\\r)");
        } else if (c == 0x1b) {
            fprintf(stderr, "(\\e)");
        } else {
            fprintf(stderr, "(%02x)", c);
        }
    }
    fprintf(stderr, "ESC\\\n");
}

int xsetcolorname(int x, char* name) {
    // Stub implementation - would set color palette
    return 0;
}

void tdeftran(char ascii) {
    static immutable char[] CS_GRAPHIC0 = "0";
    static immutable char[] CS_UK = "A";
    static immutable char[] CS_USA = "B";
    static immutable char[] CS_MULTI = "5";
    static immutable char[] CS_GER = "7";
    static immutable char[] CS_FIN = "C";
    
    const(char)* p = strchr(CS_GRAPHIC0.ptr, ascii);
    
    if (p)
        term.trantbl[term.icharset] = cast(char)(Charset.GRAPHIC0 + (p - CS_GRAPHIC0.ptr));
    else if ((p = strchr(CS_UK.ptr, ascii)) != null)
        term.trantbl[term.icharset] = cast(char)Charset.UK;
    else if ((p = strchr(CS_USA.ptr, ascii)) != null)
        term.trantbl[term.icharset] = cast(char)Charset.USA;
    else if ((p = strchr(CS_MULTI.ptr, ascii)) != null)
        term.trantbl[term.icharset] = cast(char)Charset.MULTI;
    else if ((p = strchr(CS_GER.ptr, ascii)) != null)
        term.trantbl[term.icharset] = cast(char)Charset.GER;
    else if ((p = strchr(CS_FIN.ptr, ascii)) != null)
        term.trantbl[term.icharset] = cast(char)Charset.FIN;
    else
        fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
}

void tdectest(char c) {
    int x, y;
    
    if (c == '8') { /* DEC screen alignment test. */
        for (x = 0; x < term.col; ++x) {
            for (y = 0; y < term.row; ++y)
                tsetchar('E', &term.c.attr, x, y);
        }
    }
}

int eschandle(uint ascii) {
    switch (ascii) {
    case '[': // CSI -- Control Sequence Introducer
        term.esc |= ESC_CSI;
        return 0;
    case '#': // DEC test
        term.esc |= ESC_TEST;
        return 0;
    case '%': // charset
        term.esc |= ESC_UTF8;
        return 0;
    case 'P': // DCS -- Device Control String
    case '_': // APC -- Application Program Command
    case '^': // PM -- Privacy Message
    case ']': // OSC -- Operating System Command
    case 'k': // old title set compatibility
        tstrsequence(ascii);
        return 0;
    case 'n': // LS2 -- Locking shift 2
    case 'o': // LS3 -- Locking shift 3
        term.charset = 2 + (ascii - 'n');
        break;
    case '(': // GZD4 -- set primary charset G0
    case ')': // G1D4 -- set secondary charset G1
    case '*': // G2D4 -- set tertiary charset G2
    case '+': // G3D4 -- set quaternary charset G3
        term.icharset = ascii - '(';
        term.esc |= ESC_ALTCHARSET;
        return 0;
    case 'D': // IND -- Linefeed
        if (term.c.y == term.bot) {
            static if (isPatchEnabled!"SCROLLBACK_PATCH") {
                tscrollup(term.top, 1, 1);  // Save to history
            } else {
                tscrollup(term.top, 1);
            }
        } else {
            tmoveto(term.c.x, term.c.y+1);
        }
        break;
    case 'E': // NEL -- Next line
        tnewline(1);
        break;
    case 'H': // HTS -- Horizontal tab stop
        term.tabs[term.c.x] = 1;
        break;
    case 'M': // RI -- Reverse index
        if (term.c.y == term.top) {
            tscrolldown(term.top, 1);
        } else {
            tmoveto(term.c.x, term.c.y-1);
        }
        break;
    case 'Z': // DECID -- Identify Terminal
        ttywrite(vtiden, strlen(vtiden), 0);
        break;
    case 'c': // RIS -- Reset to initial state
        treset();
        resettitle();
        import x : xloadcols;
        xloadcols();
        break;
    case '=': // DECPAM -- Application keypad
        xsetmode(1, WinMode.APPKEYPAD);
        break;
    case '>': // DECPNM -- Normal keypad
        xsetmode(0, WinMode.APPKEYPAD);
        break;
    case '7': // DECSC -- Save Cursor
        tcursor(CursorMovement.SAVE);
        break;
    case '8': // DECRC -- Restore Cursor
        tcursor(CursorMovement.LOAD);
        break;
    case '\\': // ST -- String Terminator
        if (term.esc & ESC_STR_END) {
            strescseq.term = cast(char*)(STR_TERM_ST ~ "\0").ptr;
            strhandle();
        }
        break;
    case '1': // Possible application cursor keys or other mode
        // Some terminals use ESC 1 for various purposes
        // Just consume it silently to avoid display corruption
        break;
    default:
        fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
            cast(ubyte)ascii, isprint(ascii) ? cast(char)ascii : '.');
        break;
    }
    return 1;
}

