/*
    * main.cpp
    * Entry point, input loop, command dispatch, and chaining
    * Copyright (c) 2025-2026 Daniel Hammer
*/

#include "shell.h"

// ---- Shared state definitions ----

char cwd[128] = "";
int current_drive = 0;
int last_exit = 0;
char session_user[32] = "";
char session_home[64] = "";

void sync_cwd() {
    char abs[128];
    if (montauk::getcwd(abs, sizeof(abs)) < 0) {
        current_drive = 0;
        cwd[0] = '\0';
        return;
    }

    int drive = parse_drive_prefix(abs);
    if (drive < 0) {
        current_drive = 0;
        cwd[0] = '\0';
        return;
    }

    current_drive = drive;
    int plen = drive_prefix_len(abs);
    const char* rel = abs + plen;
    if (*rel == '/') rel++;
    scopy(cwd, rel, sizeof(cwd));
}

// ---- Session info (read once at startup) ----

void read_session() {
    // TODO Why is getuser treated as not defined in VSCode intellisense?

    montauk::getuser(
        (char *)&session_user, // Buffer
        32                     // Buffer max size
    );

    scopy(session_home, "0:/users/", sizeof(session_home));
    scat(session_home, session_user, sizeof(session_home));
}

// ---- Command history ----

static constexpr int HISTORY_MAX = 32;
static char history[HISTORY_MAX][256];
static int history_count = 0;
static int history_next = 0;

static void history_add(const char* line) {
    if (line[0] == '\0') return;
    if (history_count > 0) {
        int prev = (history_next + HISTORY_MAX - 1) % HISTORY_MAX;
        if (streq(history[prev], line)) return;
    }
    scopy(history[history_next], line, 256);
    history_next = (history_next + 1) % HISTORY_MAX;
    if (history_count < HISTORY_MAX) history_count++;
}

static const char* history_get(int idx) {
    if (idx < 0 || idx >= history_count) return nullptr;
    int pos = (history_next + HISTORY_MAX - 1 - idx) % HISTORY_MAX;
    return history[pos];
}

// ---- Prompt ----

static void prompt() {
    char drv[4];
    if (current_drive >= 10) {
        drv[0] = '0' + current_drive / 10;
        drv[1] = '0' + current_drive % 10;
        drv[2] = '\0';
    } else {
        drv[0] = '0' + current_drive;
        drv[1] = '\0';
    }
    montauk::print(drv);
    montauk::print(":/");
    if (cwd[0]) montauk::print(cwd);
    montauk::print("> ");
}

// ---- Line editing ----

static void erase_input(int len) {
    for (int i = 0; i < len; i++) montauk::putchar('\b');
    for (int i = 0; i < len; i++) montauk::putchar(' ');
    for (int i = 0; i < len; i++) montauk::putchar('\b');
}

static void replace_line(char* line, int* pos, const char* newContent) {
    int oldLen = *pos;
    erase_input(oldLen);
    int newLen = slen(newContent);
    if (newLen > 255) newLen = 255;
    for (int i = 0; i < newLen; i++) {
        line[i] = newContent[i];
        montauk::putchar(newContent[i]);
    }
    line[newLen] = '\0';
    *pos = newLen;
}

// ---- Command dispatch (single command, already expanded) ----

static int process_command(const char* line) {
    line = skip_spaces(line);
    if (*line == '\0') return 0;

    // Variable assignment: NAME=VALUE
    {
        int eq = -1;
        bool valid = (line[0] >= 'A' && line[0] <= 'Z') ||
                     (line[0] >= 'a' && line[0] <= 'z') || line[0] == '_';
        if (valid) {
            for (int i = 1; line[i]; i++) {
                if (line[i] == '=') { eq = i; break; }
                if (!is_var_char(line[i])) break;
            }
        }
        if (eq > 0) {
            char name[32];
            int nlen = eq < 31 ? eq : 31;
            for (int i = 0; i < nlen; i++) name[i] = line[i];
            name[nlen] = '\0';
            const char* val = line + eq + 1;
            int vlen = slen(val);
            char vbuf[128];
            if (vlen >= 2 && ((val[0] == '"' && val[vlen-1] == '"') ||
                              (val[0] == '\'' && val[vlen-1] == '\''))) {
                int j = 0;
                for (int i = 1; i < vlen - 1 && j < 127; i++) vbuf[j++] = val[i];
                vbuf[j] = '\0';
                var_set(name, vbuf);
            } else {
                var_set(name, val);
            }
            return 0;
        }
    }

    // Parse command name and arguments
    char cmd[128];
    int i = 0;
    while (line[i] && line[i] != ' ' && i < 127) {
        cmd[i] = line[i];
        i++;
    }
    cmd[i] = '\0';

    const char* args = nullptr;
    if (line[i] == ' ') {
        args = skip_spaces(line + i);
        if (*args == '\0') args = nullptr;
    }

    // Bare drive switch: "1:", "2:", etc.
    if (has_drive_prefix(cmd) && cmd[drive_prefix_len(cmd)] == '\0' && args == nullptr) {
        int drive = parse_drive_prefix(cmd);
        if (!switch_drive(drive)) {
            montauk::print("No such drive: ");
            montauk::print(cmd);
            montauk::print("/\n");
            return 1;
        }
        return 0;
    }

    // Builtins
    if (streq(cmd, "help")) { cmd_help(); return 0; }
    if (streq(cmd, "ls")) { cmd_ls(args ? args : ""); return 0; }
    if (streq(cmd, "cd")) { return cmd_cd(args ? args : ""); }
    if (streq(cmd, "man")) { return cmd_man(args ? args : ""); }
    if (streq(cmd, "true")) { return 0; }
    if (streq(cmd, "false")) { return 1; }

    if (streq(cmd, "pwd")) {
        sync_cwd();
        char path[128];
        build_dir_path(cwd, path, sizeof(path));
        montauk::print(path);
        montauk::putchar('\n');
        return 0;
    }

    if (streq(cmd, "echo")) {
        if (!args) {
            montauk::putchar('\n');
            return 0;
        }
        bool no_newline = false;
        if (starts_with(args, "-n ")) {
            no_newline = true;
            args = skip_spaces(args + 3);
        } else if (streq(args, "-n")) {
            return 0;
        }
        montauk::print(args);
        if (!no_newline) montauk::putchar('\n');
        return 0;
    }

    if (streq(cmd, "set")) {
        if (!args) {
            // List all variables
            if (session_user[0]) {
                montauk::print("USER=");
                montauk::print(session_user);
                montauk::putchar('\n');
            }
            if (session_home[0]) {
                montauk::print("HOME=");
                montauk::print(session_home);
                montauk::putchar('\n');
            }
            sync_cwd();
            char path[128];
            build_dir_path(cwd, path, sizeof(path));
            montauk::print("PWD=");
            montauk::print(path);
            montauk::putchar('\n');
            int vc = var_user_count();
            for (int j = 0; j < vc; j++) {
                montauk::print(var_user_name(j));
                montauk::putchar('=');
                montauk::print(var_user_value(j));
                montauk::putchar('\n');
            }
            return 0;
        }
        // set VAR=value
        int eq = -1;
        for (int j = 0; args[j]; j++) {
            if (args[j] == '=') { eq = j; break; }
        }
        if (eq > 0) {
            char name[32];
            int nlen = eq < 31 ? eq : 31;
            for (int j = 0; j < nlen; j++) name[j] = args[j];
            name[nlen] = '\0';
            var_set(name, args + eq + 1);
            return 0;
        }
        // set VAR (show value)
        const char* val = var_get(args);
        if (val) {
            montauk::print(args);
            montauk::putchar('=');
            montauk::print(val);
            montauk::putchar('\n');
        } else {
            montauk::print(args);
            montauk::print(": not set\n");
        }
        return 0;
    }

    if (streq(cmd, "unset")) {
        if (!args) {
            montauk::print("Usage: unset <variable>\n");
            return 1;
        }
        var_unset(args);
        return 0;
    }

    if (streq(cmd, "exit")) {
        montauk::print("Have a pleasant day.\n");
        montauk::exit(last_exit);
    }

    // External command
    return exec_external(cmd, args);
}

// ---- Command line execution with chaining ----

static void execute_line(const char* raw) {
    // Step 1: expand tilde
    char texp[512];
    expand_tilde(raw, texp, sizeof(texp));

    // Step 2: expand variables
    char expanded[512];
    expand_vars(texp, expanded, sizeof(expanded));

    // Step 3: strip comments
    strip_comment(expanded);

    // Step 4: split on ;, &&, || and execute with chaining logic
    const char* p = expanded;
    int prev = 0;
    enum { OP_NONE, OP_SEMI, OP_AND, OP_OR } pending = OP_NONE;

    while (true) {
        p = skip_spaces(p);
        if (!*p) break;

        // Extract next command segment
        char seg[256];
        int si = 0;
        int op = OP_NONE;
        bool in_sq = false, in_dq = false;

        while (*p && si < 255) {
            if (*p == '\'' && !in_dq) { in_sq = !in_sq; seg[si++] = *p++; continue; }
            if (*p == '"' && !in_sq) { in_dq = !in_dq; seg[si++] = *p++; continue; }
            if (!in_sq && !in_dq) {
                if (*p == ';') { op = OP_SEMI; p++; break; }
                if (*p == '&' && p[1] == '&') { op = OP_AND; p += 2; break; }
                if (*p == '|' && p[1] == '|') { op = OP_OR; p += 2; break; }
            }
            seg[si++] = *p++;
        }
        seg[si] = '\0';

        // Trim trailing spaces
        while (si > 0 && seg[si - 1] == ' ') seg[--si] = '\0';

        // Decide whether to run based on pending operator
        bool run = true;
        if (pending == OP_AND && prev != 0) run = false;
        if (pending == OP_OR && prev == 0) run = false;

        if (run && seg[0]) {
            prev = process_command(seg);
        }

        pending = (decltype(pending))op;
        if (op == OP_NONE) break;
    }

    last_exit = prev;
}

// ---- Arrow key scancodes ----

static constexpr uint8_t SC_UP    = 0x48;
static constexpr uint8_t SC_DOWN  = 0x50;
static constexpr uint8_t SC_LEFT  = 0x4B;
static constexpr uint8_t SC_RIGHT = 0x4D;

static bool empty_key_event(const Montauk::KeyEvent& ev) {
    return ev.scancode == 0 && ev.ascii == 0 && !ev.pressed &&
           !ev.shift && !ev.ctrl && !ev.alt;
}

static void wait_key_event(Montauk::KeyEvent* out) {
    if (!out) return;

    for (;;) {
        montauk::getkey(out);
        if (!empty_key_event(*out))
            return;

        uint64_t serial = montauk::input_wait(0, 0);
        if (!montauk::is_key_available())
            montauk::input_wait(serial, ~0ULL);
    }
}

// ---- Entry point ----

extern "C" void _start() {
    sync_cwd();
    read_session();

    montauk::print("\n");
    montauk::print("  MontaukOS\n");
    montauk::print("  Copyright (c) 2025-2026 Daniel Hammer\n");
    montauk::print("\n");

    if (session_user[0]) {
        montauk::print("  Logged in as ");
        montauk::print(session_user);
        montauk::putchar('\n');
        montauk::print("\n");
    }

    montauk::print("  Type 'help' for available commands.\n");
    montauk::print("\n");

    char line[256];
    int pos = 0;
    int hist_nav = -1;

    prompt();

    while (true) {
        Montauk::KeyEvent ev;
        wait_key_event(&ev);

        if (!ev.pressed) continue;

        // Arrow keys: ascii == 0, check scancode
        if (ev.ascii == 0) {
            if (ev.scancode == SC_UP) {
                int next = hist_nav + 1;
                const char* entry = history_get(next);
                if (entry) {
                    hist_nav = next;
                    replace_line(line, &pos, entry);
                }
            } else if (ev.scancode == SC_DOWN) {
                if (hist_nav > 0) {
                    hist_nav--;
                    const char* entry = history_get(hist_nav);
                    if (entry) {
                        replace_line(line, &pos, entry);
                    }
                } else if (hist_nav == 0) {
                    hist_nav = -1;
                    erase_input(pos);
                    pos = 0;
                    line[0] = '\0';
                }
            }
            continue;
        }

        if (ev.ascii == '\n') {
            montauk::putchar('\n');
            line[pos] = '\0';
            history_add(line);
            execute_line(line);
            pos = 0;
            hist_nav = -1;
            prompt();
        } else if (ev.ascii == '\b') {
            if (pos > 0) {
                pos--;
                montauk::putchar('\b');
                montauk::putchar(' ');
                montauk::putchar('\b');
            }
        } else if (ev.ascii >= ' ' && pos < 255) {
            line[pos++] = ev.ascii;
            montauk::putchar(ev.ascii);
        }
    }
}
