#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/cervus.h>
#include <sys/syscall.h>
#include <cervus_util.h>

#ifndef VFS_MAX_PATH
#define VFS_MAX_PATH 1024
#endif

#define TIOCGWINSZ  0x5413
#define TIOCGCURSOR 0x5480

typedef struct { uint16_t ws_row, ws_col, ws_xpixel, ws_ypixel; } cervus_winsize_t;
typedef struct { uint32_t row, col; } cervus_cursor_pos_t;

static inline int sh_ioctl(int fd, unsigned long req, void *arg) {
    return (int)syscall3(SYS_IOCTL, fd, req, arg);
}

static int g_cols = 80;
static int g_rows = 25;

static void term_update_size(void) {
    cervus_winsize_t ws;
    if (sh_ioctl(1, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 8 && ws.ws_row >= 2) {
        g_cols = (int)ws.ws_col;
        g_rows = (int)ws.ws_row;
    }
}

static int term_get_cursor_row(void) {
    cervus_cursor_pos_t cp;
    if (sh_ioctl(1, TIOCGCURSOR, &cp) == 0) return (int)cp.row;
    return 0;
}

static void vt_goto(int row, int col) {
    char b[24];
    snprintf(b, sizeof(b), "\x1b[%d;%dH", row + 1, col + 1);
    fputs(b, stdout);
}

static void vt_eol(void) { fputs("\x1b[K", stdout); }

#define HIST_MAX 1024
#define LINE_MAX 1024

static char history[HIST_MAX][LINE_MAX];
static int  hist_count = 0, hist_head = 0;

static const char *g_hist_file = NULL;

static void hist_load(const char *path) {
    int fd = open(path, O_RDONLY, 0);
    if (fd < 0) return;
    char line[LINE_MAX];
    int li = 0;
    char ch;
    while (read(fd, &ch, 1) > 0) {
        if (ch == '\n' || li >= LINE_MAX - 1) {
            line[li] = '\0';
            if (li > 0) {
                int idx = (hist_head + hist_count) % HIST_MAX;
                strncpy(history[idx], line, LINE_MAX - 1);
                history[idx][LINE_MAX - 1] = '\0';
                if (hist_count < HIST_MAX) hist_count++;
                else hist_head = (hist_head + 1) % HIST_MAX;
            }
            li = 0;
        } else {
            line[li++] = ch;
        }
    }
    close(fd);
}

static void hist_save_entry(const char *path, const char *l) {
    int fd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0600);
    if (fd < 0) return;
    int n = 0;
    while (l[n]) n++;
    write(fd, l, n);
    write(fd, "\n", 1);
    close(fd);
}

static void hist_push(const char *l) {
    if (!l[0]) return;
    if (hist_count > 0) {
        int last = (hist_head + hist_count - 1) % HIST_MAX;
        if (strcmp(history[last], l) == 0) return;
    }
    int idx = (hist_head + hist_count) % HIST_MAX;
    strncpy(history[idx], l, LINE_MAX - 1);
    history[idx][LINE_MAX - 1] = '\0';
    if (hist_count < HIST_MAX) hist_count++;
    else hist_head = (hist_head + 1) % HIST_MAX;
    if (g_hist_file) hist_save_entry(g_hist_file, l);
}

static const char *hist_get(int n) {
    if (n < 1 || n > hist_count) return NULL;
    return history[(hist_head + hist_count - n) % HIST_MAX];
}

#define ENV_MAX_VARS 128
#define ENV_NAME_MAX  64
#define ENV_VAL_MAX  512

typedef struct { char name[ENV_NAME_MAX]; char value[ENV_VAL_MAX]; } env_var_t;

static env_var_t g_env[ENV_MAX_VARS];
static int       g_env_count = 0;

static int env_find(const char *name) {
    for (int i = 0; i < g_env_count; i++)
        if (strcmp(g_env[i].name, name) == 0) return i;
    return -1;
}
static const char *env_get(const char *name) {
    int i = env_find(name);
    return i >= 0 ? g_env[i].value : "";
}
static void env_set(const char *name, const char *value) {
    int i = env_find(name);
    if (i >= 0) { strncpy(g_env[i].value, value, ENV_VAL_MAX - 1); return; }
    if (g_env_count >= ENV_MAX_VARS) return;
    strncpy(g_env[g_env_count].name,  name,  ENV_NAME_MAX - 1);
    strncpy(g_env[g_env_count].value, value, ENV_VAL_MAX  - 1);
    g_env_count++;
}
static void env_unset(const char *name) {
    int i = env_find(name);
    if (i < 0) return;
    g_env[i] = g_env[--g_env_count];
}

static int g_last_rc = 0;

static void expand_vars(const char *src, char *dst, size_t dsz) {
    size_t di = 0;
    for (const char *p = src; *p && di + 1 < dsz; ) {
        if (*p != '$') { dst[di++] = *p++; continue; }
        p++;
        if (*p == '?') {
            char tmp[12];
            int len = snprintf(tmp, sizeof(tmp), "%d", g_last_rc);
            for (int x = 0; x < len && di + 1 < dsz; x++) dst[di++] = tmp[x];
            p++; continue;
        }
        int braced = (*p == '{');
        if (braced) p++;
        char name[ENV_NAME_MAX]; int ni = 0;
        while (*p && ni + 1 < (int)ENV_NAME_MAX) {
            char c = *p;
            if (braced) { if (c == '}') { p++; break; } }
            else if (!isalnum((unsigned char)c) && c != '_') break;
            name[ni++] = c; p++;
        }
        name[ni] = '\0';
        if (ni == 0) { dst[di++] = '$'; continue; }
        const char *val = env_get(name);
        for (; *val && di + 1 < dsz; val++) dst[di++] = *val;
    }
    dst[di] = '\0';
}

static char cwd[VFS_MAX_PATH];
static int  prompt_len = 0;

static const char *display_path(void) {
    static char dpbuf[VFS_MAX_PATH];
    const char *home = env_get("HOME");
    size_t hlen = home ? strlen(home) : 0;
    if (hlen > 1 && strncmp(cwd, home, hlen) == 0 &&
        (cwd[hlen] == '/' || cwd[hlen] == '\0')) {
        dpbuf[0] = '~';
        strncpy(dpbuf + 1, cwd + hlen, sizeof(dpbuf) - 2);
        dpbuf[sizeof(dpbuf) - 1] = '\0';
        if (dpbuf[1] == '\0') { dpbuf[0] = '~'; dpbuf[1] = '\0'; }
        return dpbuf;
    }
    return cwd;
}

static void print_prompt(void) {
    const char *dp = display_path();
    fputs(C_GREEN "cervus" C_RESET ":" C_BLUE, stdout);
    fputs(dp, stdout);
    fputs(C_RESET "$ ", stdout);
    prompt_len = 9 + (int)strlen(dp);
}

static int g_start_row = 0;

static void sync_start_row(int cur_logical_pos) {
    int real_row = term_get_cursor_row();
    int row_offset = (prompt_len + cur_logical_pos) / g_cols;
    g_start_row = real_row - row_offset;
    if (g_start_row < 0) g_start_row = 0;
}

static void input_pos_to_screen(int pos, int *row, int *col) {
    int abs = prompt_len + pos;
    *row = g_start_row + abs / g_cols;
    *col = abs % g_cols;
}

static void cursor_to(int pos) {
    int row, col;
    input_pos_to_screen(pos, &row, &col);
    if (row >= g_rows) row = g_rows - 1;
    if (row < 0) row = 0;
    vt_goto(row, col);
}

static int last_row_of(int len) {
    int abs = prompt_len + len;
    return g_start_row + (abs > 0 ? (abs - 1) : 0) / g_cols;
}

static void redraw(const char *buf, int from, int new_len, int old_len, int pos) {
    cursor_to(from);
    if (new_len > from) write(1, buf + from, new_len - from);
    sync_start_row(new_len);
    if (old_len > new_len) {
        int old_last = last_row_of(old_len);
        int new_last = last_row_of(new_len);
        cursor_to(new_len); vt_eol();
        for (int r = new_last + 1; r <= old_last; r++) {
            if (r >= g_rows) break;
            vt_goto(r, 0); vt_eol();
        }
    }
    cursor_to(pos);
}

static void replace_line(char *buf, int *len, int *pos, const char *newtext, int newlen) {
    int old_len = *len;
    for (int i = 0; i < newlen; i++) buf[i] = newtext[i];
    buf[newlen] = '\0';
    *len = newlen;
    *pos = newlen;
    redraw(buf, 0, newlen, old_len, newlen);
}

static void insert_str(char *buf, int *len, int *pos, int maxlen, const char *s, int slen) {
    if (*len + slen >= maxlen) return;
    int old_len = *len;
    for (int i = *len; i >= *pos; i--) buf[i + slen] = buf[i];
    for (int i = 0; i < slen; i++) buf[*pos + i] = s[i];
    *len += slen;
    buf[*len] = '\0';
    cursor_to(*pos);
    write(1, buf + *pos, *len - *pos);
    sync_start_row(*len);
    *pos += slen;
    cursor_to(*pos);
}

static int find_word_start(const char *buf, int pos) {
    int p = pos;
    while (p > 0 && buf[p - 1] != ' ') p--;
    return p;
}

static void list_dir_matches(const char *dir, const char *prefix, int plen,
                             char matches[][256], int *nmatch, int max) {
    DIR *d = opendir(dir);
    if (!d) return;
    struct dirent *de;
    while (*nmatch < max && (de = readdir(d)) != NULL) {
        if (strncmp(de->d_name, prefix, plen) == 0) {
            strncpy(matches[*nmatch], de->d_name, 255);
            matches[*nmatch][255] = '\0';
            (*nmatch)++;
        }
    }
    closedir(d);
}

static void do_tab_complete(char *buf, int *len, int *pos, int maxlen) {
    int ws_start = find_word_start(buf, *pos);
    int wlen = *pos - ws_start;
    if (wlen <= 0) {
        insert_str(buf, len, pos, maxlen, "    ", 4);
        return;
    }
    char word[256];
    if (wlen > 255) wlen = 255;
    memcpy(word, buf + ws_start, wlen);
    word[wlen] = '\0';

    int is_first_word = 1;
    for (int i = 0; i < ws_start; i++) {
        if (buf[i] != ' ') { is_first_word = 0; break; }
    }

    char matches[32][256];
    int nmatch = 0;

    if (is_first_word) {
        const char *pathvar = env_get("PATH");
        char ptmp[ENV_VAL_MAX];
        strncpy(ptmp, pathvar, sizeof(ptmp) - 1);
        char *p = ptmp;
        while (*p && nmatch < 32) {
            char *seg = p;
            while (*p && *p != ':') p++;
            if (*p == ':') *p++ = '\0';
            if (seg[0]) list_dir_matches(seg, word, wlen, matches, &nmatch, 32);
        }
        const char *builtins[] = {"help","exit","cd","export","unset",NULL};
        for (int i = 0; builtins[i] && nmatch < 32; i++) {
            if (strncmp(builtins[i], word, wlen) == 0) {
                strncpy(matches[nmatch], builtins[i], 255);
                nmatch++;
            }
        }
    } else {
        char dirp[VFS_MAX_PATH];
        const char *prefix = word;
        char *last_slash = NULL;
        for (int i = 0; word[i]; i++) if (word[i] == '/') last_slash = &word[i];
        if (last_slash) {
            int dlen = (int)(last_slash - word);
            char raw_dir[256];
            memcpy(raw_dir, word, dlen);
            raw_dir[dlen] = '\0';
            if (raw_dir[0] == '\0') strcpy(raw_dir, "/");
            resolve_path(cwd, raw_dir, dirp, sizeof(dirp));
            prefix = last_slash + 1;
        } else {
            strncpy(dirp, cwd, sizeof(dirp) - 1);
        }
        int plen = (int)strlen(prefix);
        list_dir_matches(dirp, prefix, plen, matches, &nmatch, 32);
    }

    if (nmatch == 0) {
        insert_str(buf, len, pos, maxlen, "    ", 4);
        return;
    }
    if (nmatch == 1) {
        const char *m = matches[0];
        int mlen = (int)strlen(m);
        int tail = mlen - wlen;
        if (tail > 0) {
            const char *suffix = m + wlen;
            if (is_first_word) {
                int need = tail;
                if (*len + need >= maxlen) return;
                int old_len = *len;
                for (int i = *len; i >= *pos; i--) buf[i + need] = buf[i];
                for (int i = 0; i < tail; i++) buf[*pos + i] = suffix[i];
                *len += need;
                buf[*len] = '\0';
                cursor_to(*pos);
                write(1, buf + *pos, *len - *pos);
                sync_start_row(*len);
                *pos += need;
                cursor_to(*pos);
            } else {
                insert_str(buf, len, pos, maxlen, suffix, tail);
            }
        }
        return;
    }
    int common = (int)strlen(matches[0]);
    for (int i = 1; i < nmatch; i++) {
        int j = 0;
        while (j < common && matches[0][j] == matches[i][j]) j++;
        common = j;
    }
    int extra = common - wlen;
    if (extra > 0) {
        insert_str(buf, len, pos, maxlen, matches[0] + wlen, extra);
        return;
    }
    putchar(10);
    for (int i = 0; i < nmatch; i++) {
        fputs("  ", stdout); fputs(matches[i], stdout);
    }
    putchar(10);
    print_prompt();
    write(1, buf, *len);
    sync_start_row(*len);
    cursor_to(*pos);
}

static int readline_edit(char *buf, int maxlen) {
    term_update_size();
    {
        int real_row = term_get_cursor_row();
        g_start_row = real_row - prompt_len / g_cols;
        if (g_start_row < 0) g_start_row = 0;
    }
    int len = 0, pos = 0, hidx = 0;
    static char saved[LINE_MAX];
    saved[0] = '\0'; buf[0] = '\0';

    for (;;) {
        char c;
        if (read(0, &c, 1) <= 0) return -1;

        if (c == '\x1b') {
            char s[4];
            if (read(0, &s[0], 1) <= 0) continue;
            if (s[0] != '[') continue;
            if (read(0, &s[1], 1) <= 0) continue;
            if (s[1] == 'A') {
                if (hidx == 0) strncpy(saved, buf, LINE_MAX - 1);
                if (hidx < hist_count) {
                    hidx++;
                    const char *h = hist_get(hidx);
                    if (h) {
                        int hl = (int)strlen(h);
                        if (hl > maxlen - 1) hl = maxlen - 1;
                        replace_line(buf, &len, &pos, h, hl);
                    }
                }
                continue;
            }
            if (s[1] == 'B') {
                if (hidx > 0) {
                    hidx--;
                    const char *h = hidx == 0 ? saved : hist_get(hidx);
                    if (!h) h = "";
                    int hl = (int)strlen(h);
                    if (hl > maxlen - 1) hl = maxlen - 1;
                    replace_line(buf, &len, &pos, h, hl);
                }
                continue;
            }
            if (s[1] == 'C') { if (pos < len) { pos++; cursor_to(pos); } continue; }
            if (s[1] == 'D') { if (pos > 0)  { pos--; cursor_to(pos); } continue; }
            if (s[1] == 'H') { pos = 0; cursor_to(0); continue; }
            if (s[1] == 'F') { pos = len; cursor_to(len); continue; }
            if (s[1] >= '1' && s[1] <= '6') {
                char tilde; read(0, &tilde, 1);
                if (tilde != '~') continue;
                if (s[1] == '3' && pos < len) {
                    for (int i = pos; i < len - 1; i++) buf[i] = buf[i + 1];
                    len--; buf[len] = '\0';
                    redraw(buf, pos, len, len + 1, pos);
                } else if (s[1] == '1') { pos = 0; cursor_to(0); }
                else if (s[1] == '4') { pos = len; cursor_to(len); }
            }
            continue;
        }

        if (c == '\n' || c == '\r') { buf[len] = '\0'; cursor_to(len); putchar(10); return len; }
        if (c == 3)  { fputs("^C", stdout); putchar(10); buf[0] = '\0'; return 0; }
        if (c == 4)  { if (len == 0) return -1; continue; }
        if (c == 1)  { pos = 0; cursor_to(0); continue; }
        if (c == 5)  { pos = len; cursor_to(len); continue; }
        if (c == '\t') {
            do_tab_complete(buf, &len, &pos, maxlen);
            continue;
        }
        if (c == 11) {
            if (pos < len) { int old_len = len; len = pos; buf[len] = '\0'; redraw(buf, pos, len, old_len, pos); }
            continue;
        }
        if (c == 21) {
            if (pos > 0) {
                int old_len = len, del = pos;
                for (int i = 0; i < len - del; i++) buf[i] = buf[i + del];
                len -= del; pos = 0; buf[len] = '\0';
                redraw(buf, 0, len, old_len, 0);
            }
            continue;
        }
        if (c == 23) {
            if (pos > 0) {
                int p = pos;
                while (p > 0 && buf[p - 1] == ' ') p--;
                while (p > 0 && buf[p - 1] != ' ') p--;
                int old_len = len, del = pos - p;
                for (int i = p; i < len - del; i++) buf[i] = buf[i + del];
                len -= del; pos = p; buf[len] = '\0';
                redraw(buf, p, len, old_len, p);
            }
            continue;
        }
        if (c == '\b' || c == 0x7F) {
            if (pos > 0) {
                int old_len = len;
                for (int i = pos - 1; i < len - 1; i++) buf[i] = buf[i + 1];
                len--; pos--; buf[len] = '\0';
                redraw(buf, pos, len, old_len, pos);
            }
            continue;
        }
        if (c >= 0x20 && c < 0x7F) {
            if (len >= maxlen - 1) continue;
            for (int i = len; i > pos; i--) buf[i] = buf[i - 1];
            buf[pos] = c; len++; buf[len] = '\0';
            cursor_to(pos); write(1, buf + pos, len - pos);
            sync_start_row(len); pos++;
            cursor_to(pos);
        }
    }
}

#define MAX_ARGS 32

static int tokenize(char *line, char *argv[], int maxargs) {
    int argc = 0;
    char *p = line;
    while (*p) {
        while (isspace((unsigned char)*p)) p++;
        if (!*p) break;
        if (argc >= maxargs - 1) break;
        char *out = p;
        argv[argc++] = out;
        int in_dquote = 0, in_squote = 0;
        while (*p) {
            if (in_dquote) {
                if (*p == '"') { in_dquote = 0; p++; }
                else           { *out++ = *p++; }
            } else if (in_squote) {
                if (*p == '\'') { in_squote = 0; p++; }
                else            { *out++ = *p++; }
            } else {
                if (*p == '"')  { in_dquote = 1; p++; }
                else if (*p == '\'') { in_squote = 1; p++; }
                else if (isspace((unsigned char)*p)) { p++; break; }
                else { *out++ = *p++; }
            }
        }
        *out = '\0';
        if (in_dquote || in_squote) { argv[argc] = NULL; return -1; }
    }
    argv[argc] = NULL;
    return argc;
}

static void cmd_help(void) {
    putchar(10);
    fputs("  " C_CYAN "Cervus Shell" C_RESET " - commands\n", stdout);
    fputs("  " C_GRAY "-----------------------------------" C_RESET "\n", stdout);
    fputs("  " C_BOLD "help" C_RESET "          show this message\n", stdout);
    fputs("  " C_BOLD "cd" C_RESET " <dir>      change directory\n", stdout);
    fputs("  " C_BOLD "export" C_RESET " N=V    set variable N to value V\n", stdout);
    fputs("  " C_BOLD "unset" C_RESET " N       delete variable N\n", stdout);
    fputs("  " C_BOLD "env" C_RESET "           list variables\n", stdout);
    fputs("  " C_BOLD "exit" C_RESET "          quit shell\n", stdout);
    fputs("  " C_GRAY "-----------------------------------" C_RESET "\n", stdout);
    fputs("  " C_BOLD "Programs:" C_RESET " ls, cat, echo, pwd, clear, uname\n", stdout);
    fputs("  meminfo, cpuinfo, ps, kill, find, stat, wc, yes, sleep\n", stdout);
    fputs("  mount, umount, mkfs, lsblk, mv, rm, mkdir, touch\n", stdout);
    fputs("  " C_RED "shutdown" C_RESET ", " C_CYAN "reboot" C_RESET "\n", stdout);
    fputs("  " C_GRAY "-----------------------------------" C_RESET "\n", stdout);
    fputs("  " C_BOLD "Operators:" C_RESET "  cmd1 " C_YELLOW ";" C_RESET
          " cmd2   " C_YELLOW "&&" C_RESET "   " C_YELLOW "||" C_RESET "\n", stdout);
    fputs("  " C_BOLD "Tab" C_RESET "       auto-complete / 4 spaces\n", stdout);
    fputs("  " C_BOLD "Ctrl+C" C_RESET "    interrupt\n", stdout);
    fputs("  " C_BOLD "Ctrl+A/E" C_RESET "  beginning/end of line\n", stdout);
    fputs("  " C_BOLD "Ctrl+K/U" C_RESET "  delete to end/beginning\n", stdout);
    fputs("  " C_BOLD "Ctrl+W" C_RESET "    delete word\n", stdout);
    fputs("  " C_BOLD "Arrows" C_RESET "    cursor / history\n", stdout);
    fputs("  " C_GRAY "-----------------------------------" C_RESET "\n", stdout);
    putchar(10);
}

static int cmd_cd(const char *path) {
    if (!path || !path[0] || strcmp(path, "~") == 0) {
        const char *home = env_get("HOME");
        path = (home && home[0]) ? home : "/";
    }
    char np[VFS_MAX_PATH];
    resolve_path(cwd, path, np, sizeof(np));
    struct stat st;
    if (stat(np, &st) < 0) { fputs(C_RED "cd: not found: " C_RESET, stdout); fputs(path, stdout); putchar(10); return 1; }
    if (st.st_type != 1)   { fputs(C_RED "cd: not a dir: " C_RESET, stdout); fputs(path, stdout); putchar(10); return 1; }
    strncpy(cwd, np, sizeof(cwd) - 1);
    cwd[sizeof(cwd) - 1] = '\0';
    chdir(np);
    return 0;
}

static int valid_varname(const char *s) {
    if (!s || !*s) return 0;
    if (!isalpha((unsigned char)*s) && *s != '_') return 0;
    for (s++; *s; s++)
        if (!isalnum((unsigned char)*s) && *s != '_') return 0;
    return 1;
}

static int cmd_export(int argc, char *argv[]) {
    if (argc < 2) { fputs(C_RED "export: usage: export NAME=VALUE\n" C_RESET, stdout); return 1; }
    if (argc > 2) { fputs(C_RED "export: invalid syntax\n" C_RESET, stdout); return 1; }
    char *arg = argv[1];
    char *eq_pos = strchr(arg, '=');
    if (!eq_pos) {
        if (!valid_varname(arg)) { fputs(C_RED "export: not a valid identifier\n" C_RESET, stdout); return 1; }
        env_set(arg, ""); return 0;
    }
    *eq_pos = '\0';
    const char *name = arg, *val = eq_pos + 1;
    if (!valid_varname(name)) { *eq_pos = '='; fputs(C_RED "export: not a valid identifier\n" C_RESET, stdout); return 1; }
    env_set(name, val);
    *eq_pos = '=';
    return 0;
}

static int cmd_unset(int argc, char *argv[]) {
    if (argc < 2) { fputs(C_RED "unset: usage: unset NAME\n" C_RESET, stdout); return 1; }
    for (int i = 1; i < argc; i++) env_unset(argv[i]);
    return 0;
}

static int find_in_path(const char *cmd, char *out, size_t outsz) {
    const char *pathvar = env_get("PATH");
    if (!pathvar || !pathvar[0]) {
        path_join("/bin", cmd, out, outsz);
        struct stat st;
        return stat(out, &st) == 0 && st.st_type != 1;
    }
    char tmp[ENV_VAL_MAX];
    strncpy(tmp, pathvar, sizeof(tmp) - 1);
    char *p = tmp;
    while (*p) {
        char *seg = p;
        while (*p && *p != ':') p++;
        if (*p == ':') *p++ = '\0';
        if (!seg[0]) continue;
        char candidate[VFS_MAX_PATH];
        path_join(seg, cmd, candidate, sizeof(candidate));
        struct stat st;
        if (stat(candidate, &st) == 0 && st.st_type != 1) { strncpy(out, candidate, outsz - 1); return 1; }
    }
    return 0;
}

typedef enum { REDIR_NONE = 0, REDIR_OUT, REDIR_APPEND, REDIR_IN } redir_type_t;

typedef struct {
    redir_type_t type;
    char path[VFS_MAX_PATH];
} redir_t;

static int parse_redirects(char *argv[], int *argc, redir_t redirs[], int max_redirs, int *nredirs) {
    *nredirs = 0;
    int new_argc = 0;
    for (int i = 0; i < *argc; i++) {
        const char *a = argv[i];
        redir_type_t rt = REDIR_NONE;
        const char *target = NULL;

        if (strcmp(a, ">>") == 0) {
            rt = REDIR_APPEND;
            if (i + 1 < *argc) target = argv[++i];
        } else if (strcmp(a, ">") == 0) {
            rt = REDIR_OUT;
            if (i + 1 < *argc) target = argv[++i];
        } else if (strcmp(a, "<") == 0) {
            rt = REDIR_IN;
            if (i + 1 < *argc) target = argv[++i];
        } else if (a[0] == '>' && a[1] == '>' && a[2] != '\0') {
            rt = REDIR_APPEND; target = a + 2;
        } else if (a[0] == '>' && a[1] != '>' && a[1] != '\0') {
            rt = REDIR_OUT; target = a + 1;
        } else if (a[0] == '<' && a[1] != '\0') {
            rt = REDIR_IN; target = a + 1;
        } else {
            argv[new_argc++] = argv[i];
            continue;
        }

        if (!target || !target[0]) {
            fputs(C_RED "syntax error: missing redirection target\n" C_RESET, stdout);
            return -1;
        }
        if (*nredirs < max_redirs) {
            redirs[*nredirs].type = rt;
            char resolved[VFS_MAX_PATH];
            resolve_path(cwd, target, resolved, sizeof(resolved));
            strncpy(redirs[*nredirs].path, resolved, VFS_MAX_PATH - 1);
            redirs[*nredirs].path[VFS_MAX_PATH - 1] = '\0';
            (*nredirs)++;
        }
    }
    argv[new_argc] = NULL;
    *argc = new_argc;
    return 0;
}

static int run_single(char *line) {
    char expanded[LINE_MAX];
    expand_vars(line, expanded, sizeof(expanded));
    char buf[LINE_MAX];
    strncpy(buf, expanded, LINE_MAX - 1);
    char *argv[MAX_ARGS];
    int argc = tokenize(buf, argv, MAX_ARGS);
    if (argc < 0) { fputs(C_RED "syntax error: unclosed quote\n" C_RESET, stdout); return 1; }
    if (!argc) return 0;

    redir_t redirs[8];
    int nredirs = 0;
    if (parse_redirects(argv, &argc, redirs, 8, &nredirs) < 0) return 1;
    if (!argc) return 0;

    const char *cmd = argv[0];

    if (strcmp(cmd, "help")   == 0) { cmd_help(); return 0; }
    if (strcmp(cmd, "exit")   == 0) { fputs("Goodbye!\n", stdout); exit(0); }
    if (strcmp(cmd, "cd")     == 0) return cmd_cd(argc > 1 ? argv[1] : NULL);
    if (strcmp(cmd, "export") == 0) return cmd_export(argc, argv);
    if (strcmp(cmd, "unset")  == 0) return cmd_unset(argc, argv);

    char binpath[VFS_MAX_PATH];
    if (cmd[0] == '/') {
        strncpy(binpath, cmd, sizeof(binpath) - 1);
        binpath[sizeof(binpath) - 1] = '\0';
    } else if (cmd[0] == '.') {
        resolve_path(cwd, cmd, binpath, sizeof(binpath));
    } else {
        if (!find_in_path(cmd, binpath, sizeof(binpath))) {
            char t_cwd[VFS_MAX_PATH];
            resolve_path(cwd, cmd, t_cwd, sizeof(t_cwd));
            struct stat st;
            if (stat(t_cwd, &st) == 0 && st.st_type != 1) {
                strncpy(binpath, t_cwd, sizeof(binpath) - 1);
                binpath[sizeof(binpath) - 1] = '\0';
            } else {
                fputs(C_RED "not found: " C_RESET, stdout); fputs(cmd, stdout); putchar(10); return 127;
            }
        }
    }

#define REAL_ARGV_MAX (MAX_ARGS + ENV_MAX_VARS + 4)
    char *real_argv_buf[REAL_ARGV_MAX];
    static char _cwd_flag[VFS_MAX_PATH + 8];
    static char _env_flags[ENV_MAX_VARS][ENV_NAME_MAX + ENV_VAL_MAX + 8];

    int ri = 0;
    real_argv_buf[ri++] = binpath;
    for (int i = 1; i < argc; i++) real_argv_buf[ri++] = argv[i];
    snprintf(_cwd_flag, sizeof(_cwd_flag), "--cwd=%s", cwd);
    real_argv_buf[ri++] = _cwd_flag;
    for (int ei = 0; ei < g_env_count && ri < REAL_ARGV_MAX - 1; ei++) {
        snprintf(_env_flags[ei], sizeof(_env_flags[ei]), "--env:%s=%s",
                 g_env[ei].name, g_env[ei].value);
        real_argv_buf[ri++] = _env_flags[ei];
    }
    real_argv_buf[ri] = NULL;

    pid_t child = fork();
    if (child < 0) { fputs(C_RED "fork failed" C_RESET "\n", stdout); return 1; }
    if (child == 0) {
        for (int i = 0; i < nredirs; i++) {
            int fd = -1;
            int target_fd = -1;
            if (redirs[i].type == REDIR_OUT) {
                fd = open(redirs[i].path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
                target_fd = 1;
            } else if (redirs[i].type == REDIR_APPEND) {
                fd = open(redirs[i].path, O_WRONLY | O_CREAT | O_APPEND, 0644);
                target_fd = 1;
            } else if (redirs[i].type == REDIR_IN) {
                fd = open(redirs[i].path, O_RDONLY, 0);
                target_fd = 0;
            }
            if (fd < 0) {
                fputs(C_RED "redirect: cannot open: " C_RESET, stdout);
                fputs(redirs[i].path, stdout);
                putchar('\n');
                exit(1);
            }
            dup2(fd, target_fd);
            close(fd);
        }
        execve(binpath, (char *const *)real_argv_buf, NULL);
        fputs(C_RED "exec failed: " C_RESET, stdout); fputs(binpath, stdout); putchar(10); exit(127);
    }
    int status = 0;
    waitpid(child, &status, 0);
    return (status >> 8) & 0xFF;
}

typedef enum { CH_NONE = 0, CH_SEQ, CH_AND, CH_OR } chain_t;

static void run_command(char *line) {
    char work[LINE_MAX];
    strncpy(work, line, LINE_MAX - 1);
    char *segs[64]; chain_t ops[64]; int ns = 1;
    segs[0] = work; ops[0] = CH_NONE;
    char *p = work;
    while (*p) {
        if (*p == '"')  { p++; while (*p && *p != '"')  p++; if (*p) p++; continue; }
        if (*p == '\'') { p++; while (*p && *p != '\'') p++; if (*p) p++; continue; }
        if (*p == '&' && *(p+1) == '&') { *p='\0'; p+=2; while(isspace((unsigned char)*p))p++; ops[ns]=CH_AND; segs[ns]=p; ns++; continue; }
        if (*p == '|' && *(p+1) == '|') { *p='\0'; p+=2; while(isspace((unsigned char)*p))p++; ops[ns]=CH_OR;  segs[ns]=p; ns++; continue; }
        if (*p == ';') { *p='\0'; p++;   while(isspace((unsigned char)*p))p++; ops[ns]=CH_SEQ; segs[ns]=p; ns++; continue; }
        p++;
    }
    int rc = 0;
    for (int i = 0; i < ns; i++) {
        char *s = segs[i];
        while (isspace((unsigned char)*s)) s++;
        size_t sl = strlen(s);
        while (sl > 0 && isspace((unsigned char)s[sl - 1])) s[--sl] = '\0';
        if (!s[0]) continue;
        if (i > 0) {
            if (ops[i] == CH_AND && rc != 0) continue;
            if (ops[i] == CH_OR  && rc == 0) continue;
        }
        rc = run_single(s);
    }
    g_last_rc = rc;
}

static void print_motd(void) {
    int fd = open("/mnt/etc/motd", O_RDONLY, 0);
    if (fd < 0) fd = open("/etc/motd", O_RDONLY, 0);
    if (fd < 0) { putchar(10); fputs("  Cervus OS v0.0.2\n  Type 'help' for commands.\n", stdout); putchar(10); return; }
    char buf[1024];
    ssize_t n = read(fd, buf, sizeof(buf) - 1);
    close(fd);
    if (n > 0) { buf[n] = '\0'; write(1, buf, n); }
}


static int g_installed = 0;

static int sys_disk_mount(const char *dev, const char *path) {
    return (int)syscall2(SYS_DISK_MOUNT, dev, path);
}

static int launch_installer(void) {
    const char *path = "/bin/install-on-disk";
    struct stat st;
    if (stat(path, &st) != 0) {
        fputs(C_RED "  install-on-disk not found on system.\n" C_RESET, stdout);
        return -1;
    }

    const char *argv[4];
    argv[0] = path;
    argv[1] = "--env:MODE=live";
    argv[2] = "--cwd=/";
    argv[3] = NULL;

    pid_t child = fork();
    if (child < 0) {
        fputs(C_RED "  fork failed\n" C_RESET, stdout);
        return -1;
    }
    if (child == 0) {
        execve(path, (char *const *)argv, NULL);
        fputs(C_RED "  exec install-on-disk failed\n" C_RESET, stdout);
        exit(127);
    }
    int status = 0;
    waitpid(child, &status, 0);
    return (status >> 8) & 0xFF;
}

static int ask_install_or_live(void) {
    fputs("\x1b[2J\x1b[H", stdout);
    fputs("\n", stdout);
    fputs(C_CYAN "  Cervus OS" C_RESET " - Live ISO\n", stdout);
    fputs(C_GRAY "  -----------------------------------" C_RESET "\n\n", stdout);
    fputs("  A disk has been detected on this machine.\n", stdout);
    fputs("  What would you like to do?\n\n", stdout);
    fputs("    [" C_BOLD "1" C_RESET "] Install Cervus to disk\n", stdout);
    fputs("    [" C_BOLD "2" C_RESET "] Continue in Live mode\n\n", stdout);
    fputs("  Choice [1-2]: ", stdout);

    char c = 0;
    while (1) {
        if (read(0, &c, 1) <= 0) continue;
        if (c == '1' || c == '2') { putchar(c); putchar(10); break; }
    }
    return (c == '1') ? 1 : 0;
}

int main(int argc, char **argv) {
    (void)argc; (void)argv;

    struct stat dev_st;
    int has_disk = (stat("/dev/hda", &dev_st) == 0);
    int has_hda2 = (stat("/dev/hda2", &dev_st) == 0);
    int has_hda_legacy = 0;

    int disk_mounted = 0;

    if (has_hda2) {
        int mr = sys_disk_mount("hda2", "/mnt");
        if (mr == 0) {
            disk_mounted = 1;
            g_installed = 1;
        }
    } else if (has_disk) {
        int mr = sys_disk_mount("hda", "/mnt");
        if (mr == 0) {
            disk_mounted = 1;
            g_installed = 1;
            has_hda_legacy = 1;
        }
    }
    (void)has_hda_legacy;

    if (!disk_mounted && has_disk) {
        if (ask_install_or_live() == 1) {
            launch_installer();
            struct stat retry_st;
            if (stat("/dev/hda2", &retry_st) == 0) {
                if (sys_disk_mount("hda2", "/mnt") == 0) {
                    disk_mounted = 1;
                    g_installed = 1;
                }
            }
        }
    }

    if (disk_mounted) {
        strncpy(cwd, "/mnt/home", sizeof(cwd));
        env_set("HOME", "/mnt/home");
        env_set("PATH", "/mnt/bin:/mnt/apps:/mnt/usr/bin");
        env_set("SHELL", "/mnt/bin/shell");
    } else {
        strncpy(cwd, "/", sizeof(cwd));
        env_set("HOME", "/");
        env_set("PATH", "/bin:/apps:/usr/bin");
        env_set("SHELL", "/bin/shell");
    }

    if (!disk_mounted && has_disk)    env_set("MODE", "live");
    else if (!has_disk)               env_set("MODE", "live");
    else                              env_set("MODE", "installed");

    print_motd();

    {
        static char hist_path[VFS_MAX_PATH];
        const char *h = env_get("HOME");
        if (h && h[0]) {
            path_join(h, ".history", hist_path, sizeof(hist_path));
            g_hist_file = hist_path;
            hist_load(hist_path);
        }
    }

    if (!has_disk) {
        fputs(C_YELLOW " [Live Mode]" C_RESET " No disk detected. All changes are in RAM.\n\n", stdout);
    } else if (!disk_mounted) {
        fputs(C_YELLOW " [Live Mode]" C_RESET " Disk not mounted.\n\n", stdout);
    }

    char line[LINE_MAX];
    for (;;) {
        print_prompt();
        int n = readline_edit(line, LINE_MAX);
        if (n < 0) {
            fputs("\nSession ended. Restarting shell...\n", stdout);
            const char *h = env_get("HOME");
            strncpy(cwd, (h && h[0]) ? h : "/", sizeof(cwd));
            print_motd();
            continue;
        }
        int len = (int)strlen(line);
        while (len > 0 && isspace((unsigned char)line[len - 1])) line[--len] = '\0';
        if (len > 0) { hist_push(line); run_command(line); }
    }
}