#include "LiveDebug.hpp"

#include <spider/runtime/Runtime.hpp>
#include <spider/runtime/util/Terminal.hpp>
#include <spider/runtime/native/distro.hpp>

#include <vector>
#include <string>
#include <iomanip>
#include <iostream>
#include <chrono>
#include <format>
#include <thread>
namespace spider {

    void drawHead(Terminal& t) {
        t.move(1, 1)
            .style(Terminal::FG_YELLOW)
            .print(" Spider Runtime Live Debug ")
            .style(Terminal::RESET).print(" | ")
            .style(Terminal::FG_B_CYAN).print(" Sintek Analytics @ 2026 ")
            .style(Terminal::RESET).print(" | ")
            .style(Terminal::FG_B_BLACK).print("Press ESC to exit")
            .style(Terminal::FG_BLACK)
            .style(Terminal::BG_YELLOW)
            .move(3, 1).print("  // __ \\\\").print("                 ") // 27
            .move(4, 1).print("  \\\\(  )//").print("   SPIDER v0.1   ")
            .move(5, 1).print("   //()\\\\ ").print("      alpha      ")
            .move(6, 1).print("   \\\\  // ").print("                 ")
            .style(Terminal::RESET)
            .style(Terminal::FG_B_BLACK) // 4x8 for the menu
            .move(3, 28).print("[ STEP ]")
            .move(4, 28).print("[ STOP ]")
            .move(5, 28).print("[ RUN  ]")
            .move(6, 28).print("[ MENU ]")
            .style(Terminal::RESET)
            ;
    }

    void drawCPUTempl(Terminal& t, CPU& cpu) {
        i32 r = 8, c = 1;
        i32 w = 35, h = 31;
        t.drawBox(r, c, w, h, "CPU");

        const std::string regs[] = {
            "RA", "RB", "RC", "RD",
            "RX", "RY", "R0", "R1",
            "R2", "R3", "R4", "R5",
            "R6", "R7", "R8", "R9",
            "RF", "RI", "RS", "RZ",
            "RE", "RN", "RV", "RM",
            "ALU0", "ALU1"
        };
        const std::string alt[] = {
            Terminal::FG_WHITE,
            Terminal::FG_B_BLACK,
        };

        r++;
        c++;

        t.move(r++, c);
        t.style(Terminal::FG_B_YELLOW);
        t.print_center(w - 2, "GP Registers");
        t.style(Terminal::RESET);
        for (i32 i = 0; i < 8; i++) {
            t.style(alt[i & 1]);
            t.move(r + i * 2, c);
            t.print(regs[i * 2]);
            t.move(r + i * 2, c + 17);
            t.print(regs[i * 2 + 1]);
        }

        t.move(r += 16, c);
        t.style(Terminal::FG_B_CYAN);
        t.print_center(w - 2, "System Registers");
        t.style(Terminal::RESET);
        r++;
        for (i32 j = 0, i = 8; i < 12; j++, i++) {
            t.style(alt[j & 1]);
            t.move(r + j * 2, c);
            t.print(regs[i * 2]);
            t.move(r + j * 2, c + 17);
            t.print(regs[i * 2 + 1]);
        }

        t.move(r += 8, c);
        t.style(Terminal::FG_GREEN);
        t.print_center(w - 2, "Extra Registers");
        t.style(Terminal::RESET);
        r++;
        for (i32 j = 0, i = 12; i < 13; j++, i++) {
            t.style(alt[j & 1]);
            t.move(r + j * 2, c);
            t.print(regs[i * 2]);
            t.move(r + j * 2, c + 17);
            t.print(regs[i * 2 + 1]);
        }

        t.flush();
    }

    void printU64Hex(u64 n) {
        std::ios state(nullptr);
        state.copyfmt(std::cout);
        std::cout
            << std::hex
            << std::uppercase
            << std::setfill('0')
            << std::setw(16)
            << n;
        std::cout.copyfmt(state);
    }

    void drawCPU(Terminal& t, CPU& cpu) {
        i32 r = 8, c = 1;

        const register_t* regs[] = {
            &cpu.RA, &cpu.RB, &cpu.RC, &cpu.RD,
            &cpu.RX, &cpu.RY, &cpu.R0, &cpu.R1,
            &cpu.R2, &cpu.R3, &cpu.R4, &cpu.R5,
            &cpu.R6, &cpu.R7, &cpu.R8, &cpu.R9,
            //&cpu.RF, &cpu.RI, &cpu.RS, &cpu.RZ,
            //&cpu.RE, &cpu.RN, &cpu.RV, &cpu.RM,
            &cpu.ALU0, &cpu.ALU1
        };
        const u64* sys_regs[] = {
            &cpu.RF, &cpu.RI, &cpu.RS, &cpu.RZ,
            &cpu.RE, &cpu.RN, &cpu.RV, &cpu.RM,
        };
        const std::string alt[] = {
            Terminal::FG_WHITE,
            Terminal::FG_B_BLACK,
        };

        r++;
        c++;
        t.move(r++, c);
        t.style(Terminal::RESET);
        r++;
        for (i32 i = 0; i < 8; i++) {
            t.style(alt[i & 1]);
            t.move(r + i * 2, c);
            printU64Hex(regs[i * 2]->_u64);
            t.move(r + i * 2, c + 17);
            printU64Hex(regs[i * 2 + 1]->_u64);
        }

        t.move(r += 16, c);
        r++;
        for (i32 j = 0, i = 8; i < 12; j++, i++) {
            t.style(alt[j & 1]);
            t.move(r + j * 2, c);
            printU64Hex(*sys_regs[i * 2]);
            t.move(r + j * 2, c + 17);
            printU64Hex(*sys_regs[i * 2 + 1]);
        }

        t.move(r += 8, c);
        r++;
        for (i32 j = 0; j < 1; j++) {
            t.style(alt[j & 1]);
            t.move(r + j * 2, c);
            printU64Hex(regs[16 + j * 2]->_u64);
            t.move(r + j * 2, c + 17);
            printU64Hex(regs[16 + j * 2 + 1]->_u64);
        }

        t.flush();
    }

    i32 addressWidth(isize ramSize) {
        if (ramSize == 0) return 1;
        isize maxAddr = ramSize - 1;
        i32 digits = 0;
        // Shift by increments of 4 (one hex nibble)
        // We use a do-while to ensure at least 1 digit is returned for small RAMs
        do {
            digits++;
            maxAddr >>= 4;
        } while (maxAddr > 0);

        return digits;
    }

    /**
     * Draws a vertical scrollbar
     * @param x The column where the bar should be placed (usually box_x + width - 1)
     * @param y The starting row of the track (usually box_y + 1)
     * @param trackHeight The internal height of the box (box_height - 2)
     * @param progress The current progress
     * @param total The total
     */
    void drawScrollThumb(Terminal& term, i32 x, i32 y, i32 trackHeight, isize progress, isize total) {
        if (total == 0 || trackHeight <= 0) return;

        // 1. Draw the background track (Light Shade: ░)
        term.style(Terminal::FG_B_BLACK); // Dim the track
        for (int i = 0; i < trackHeight; ++i) {
            term.move(y + i, x).print("░");
        }

        // 2. Calculate Thumb Position
        // Cap progress to total to avoid overflow
        if (progress > total) progress = total;

        // Calculate ratio (0.0 to 1.0)
        f64 ratio = f64(progress) / f64(total);

        // Map to track coordinates
        i32 thumbOffset = i32(ratio * (trackHeight - 1));

        // 3. Draw the Thumb (Full Block: █)
        term.move(y + thumbOffset, x);
        term.style(Terminal::FG_WHITE).print("█");
        term.style(Terminal::RESET);
    }

    /**
     * Draws a hex dump of memory within a styled terminal box.
     * @param term Reference to your Terminal instance
     * @param ram The RAM
     * @param scrollPos The starting address to display
     * @param x Starting column
     * @param y Starting row
     * @param width Width of the box
     * @param height Height of the box
     */
    void drawRAM(Terminal& term, RAM& ram, u64 scrollPos) {
        // 1. Draw the container box
        i32 y = 3;
        i32 height = 36;

        // 2. Configuration for the hex layout
        int addrWidth = addressWidth(ram.size());
        int bytesPerRow = 8;
        int displayRows = height - 2; // Subtract top/bottom borders
        i32 width = (2 + 2 + 16 + 7 + 3 + 8 + 4) + addrWidth;
        i32 x = 37;

        // create box
        term.drawBox(y, x, width, height, "RAM");
        drawScrollThumb(term, x + width - 2, y + 1, height - 2, scrollPos, ram.size());

        // Ensure scrollPos is within bounds and aligned
        if (scrollPos < 0) scrollPos = 0;
        if (scrollPos > ram.size()) scrollPos = ram.size();

        for (int i = 0; i < displayRows; ++i) {
            isize currentRowAddr = scrollPos + (i * bytesPerRow);

            // address lock
            if (currentRowAddr >= ram.size()) {
                term.move(y + 1 + i, x + 1);
                term.print(std::string(width - 3, ' '));
                continue;
            }

            std::stringstream ssaddr;
            std::stringstream ss;

            // setup ss
            ssaddr << std::setfill('0') << std::uppercase << std::hex;
            ss << std::setfill('0') << std::uppercase << std::hex;

            // address
            ssaddr << std::setw(addrWidth) << currentRowAddr << "  ";

            // Hex Bytes
            std::string asciiPart = "";
            for (int j = 0; j < bytesPerRow; ++j) {
                isize targetAddr = currentRowAddr + j;
                if (targetAddr >= ram.size()) {
                    ss << ""; // Padding for end of memory
                    asciiPart += "";
                    continue;
                }

                u8 byte = ram[targetAddr];
                ss << std::setfill('0') << std::setw(2) << std::hex << (u32)byte << " ";
                asciiPart += (std::isprint(byte) ? (char)byte : '.');
            }

            // --- Combine and Print ---
            term.move(y + 1 + i, x + 2); // Move inside the box
            term.style(Terminal::FG_B_CYAN).print(ssaddr.str()); // Hex part in Cyan
            term.style(Terminal::FG_WHITE).print(ss.str());
            term.style(Terminal::FG_B_YELLOW).print(" | ");
            term.style(Terminal::FG_WHITE).print(asciiPart); // ASCII part in White
        }

        term.style(Terminal::RESET);
        term.flush();
    }

    std::string getTimestamp() {
        std::time_t t = std::time(nullptr);
        std::tm lt;
#if defined(SPIDER_OS_WINDOWS)
        localtime_s(&lt, &t);
#endif
#if defined(SPIDER_OS_LINUX) || defined(SPIDER_OS_MACOS)
        localtime_r(&t, &lt);
#endif
        return std::format("{:02}:{:02}:{:02} {:02}/{:02}/{}",
            lt.tm_hour, lt.tm_min, lt.tm_sec,
            lt.tm_mday, lt.tm_mon + 1, lt.tm_year + 1900);
    }

    void drawTime(Terminal& t) {
        //auto now = std::chrono::system_clock::now();
        //auto now_l = std::chrono::current_zone()->to_local(now);
        //auto now_s = std::chrono::floor<std::chrono::seconds>(now_l);
        //std::string time_str = std::format("{:%H:%M:%S}", now_s); // Format: HH:mm:ss
        //std::string date_str = std::format("{:%d/%m/%Y}", now_s); // Format: dd/MM/YYYY

        t.move(1, 76);
        t.style(Terminal::RESET);
        t.print(" | ").style(Terminal::FG_GREEN).print(getTimestamp());
    }

    void redraw(Terminal& t, Runtime& r, u64 scroll) {
        // draw CPU, RAM
        drawCPU(t, r.cpu);
        drawRAM(t, r.ram, scroll);
    }

    int liveDebugMain() {
        Terminal t;
        Runtime runtime(1024);

        bool running = true, update = true;
        u64 ramScroll = 0;
        u8 key = Terminal::UNKNOWN;

        t.println("Starting Spider live debug...");
        t.altbuff(true).cursor(false);

        drawTime(t);
        drawHead(t);
        drawCPUTempl(t, runtime.cpu);

        // delay for time
        auto last_exec = std::chrono::steady_clock::now();
        auto delay = std::chrono::milliseconds(1000);

        while (running) {
            // draw time
            auto now = std::chrono::steady_clock::now();
            if (now - last_exec >= delay) {
                drawTime(t);
                last_exec = now;
            }

            // redraw something if it updated
            if (update) {
                redraw(t, runtime, ramScroll);
                update = false;
            }

            // Handle Input
            key = t.getKeyNb();
            switch (key) {
            case Terminal::ESC:
                running = false;
                break;
            case Terminal::UP:
                if (ramScroll >= 16) ramScroll -= 16;
                update = true;
                break;
            case Terminal::DOWN:
                if (runtime.ram.size() >= 16 && ramScroll <= runtime.ram.size() - 16) ramScroll += 16;
                update = true;
                break;
            default:
                break;
            }

            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }

        t.altbuff(false).println("Stopped Spider live debug.").flush();
        return 0;
    }

}
