#include "Terminal.hpp"

#include <spider/runtime/native/distro.hpp>

#if defined(SPIDER_OS_WINDOWS)
#include <conio.h>
#include <windows.h>
#endif

#if defined(SPIDER_OS_LINUX) || defined(SPIDER_OS_MACOS)
#include <termios.h>
#include <unistd.h>
#include <stdio.h>
#endif

#if defined(SPIDER_DISTRO_DESKTOP)

namespace spider {

    // Style //
    const char* Terminal::RESET = "\033[0m";
    const char* Terminal::BOLD = "\033[1m";
    const char* Terminal::ITALIC = "\033[3m";
    const char* Terminal::FAINT = "\033[2m";
    const char* Terminal::STRIKE = "\033[9m";

    // Foreground //
    const char* Terminal::FG_BLACK = "\033[30m";  const char* Terminal::FG_B_BLACK = "\033[90m";
    const char* Terminal::FG_RED = "\033[31m";  const char* Terminal::FG_B_RED = "\033[91m";
    const char* Terminal::FG_GREEN = "\033[32m";  const char* Terminal::FG_B_GREEN = "\033[92m";
    const char* Terminal::FG_YELLOW = "\033[33m";  const char* Terminal::FG_B_YELLOW = "\033[93m";
    const char* Terminal::FG_BLUE = "\033[34m";  const char* Terminal::FG_B_BLUE = "\033[94m";
    const char* Terminal::FG_MAGENTA = "\033[35m";  const char* Terminal::FG_B_MAGENTA = "\033[95m";
    const char* Terminal::FG_CYAN = "\033[36m";  const char* Terminal::FG_B_CYAN = "\033[96m";
    const char* Terminal::FG_WHITE = "\033[37m";  const char* Terminal::FG_B_WHITE = "\033[97m";

    // Background //
    const char* Terminal::BG_BLACK = "\033[40m";  const char* Terminal::BG_B_BLACK = "\033[100m";
    const char* Terminal::BG_RED = "\033[41m";  const char* Terminal::BG_B_RED = "\033[101m";
    const char* Terminal::BG_GREEN = "\033[42m";  const char* Terminal::BG_B_GREEN = "\033[102m";
    const char* Terminal::BG_YELLOW = "\033[43m";  const char* Terminal::BG_B_YELLOW = "\033[103m";
    const char* Terminal::BG_BLUE = "\033[44m";  const char* Terminal::BG_B_BLUE = "\033[104m";
    const char* Terminal::BG_MAGENTA = "\033[45m";  const char* Terminal::BG_B_MAGENTA = "\033[105m";
    const char* Terminal::BG_CYAN = "\033[46m";  const char* Terminal::BG_B_CYAN = "\033[106m";
    const char* Terminal::BG_WHITE = "\033[47m";  const char* Terminal::BG_B_WHITE = "\033[107m";

    Terminal::Terminal() {
#if defined(SPIDER_OS_WINDOWS)
        // Enable UTF-8
        SetConsoleOutputCP(CP_UTF8);
        SetConsoleCP(CP_UTF8);

        // enable vtp
        HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
        DWORD dwMode = 0;
        GetConsoleMode(hOut, &dwMode);
        dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
        SetConsoleMode(hOut, dwMode);
#endif
    }

    Terminal::~Terminal() {
        altbuff(false).style(RESET).cursor(true).flush();
    }

    Terminal& Terminal::style(const std::string_view code) {
        std::cout << code;
        return *this;
    }

    Terminal& Terminal::move(i32 row, i32 col) {
        std::cout << "\033[" << row << ";" << col << "H";
        return *this;
    }

    Terminal& Terminal::altbuff(bool enable) {
        std::cout << (enable ? "\033[?1049h" : "\033[?1049l");
        return *this;
    }

    Terminal& Terminal::cls() {
        std::cout << "\033[2J";
        return *this;
    }

    Terminal& Terminal::scrollRange(i32 start, i32 end) {
        std::cout << "\033[" << start << ";" << end << "r\033[?6h";
        return *this;
    }

    Terminal& Terminal::undoSRange() {
        std::cout << "\033[r\033[?6l\033[H";
        return *this;
    }

    Terminal& Terminal::fill(const std::string_view color) {
        this->style(color);
        this->cls();
        return *this;
    }

    Terminal& Terminal::clearRow(i32 row) {
        // Move to row, column 1
        this->move(row, 1);
        // \033[2K clears the entire line
        std::cout << "\033[2K";
        return *this;
    }

    Terminal& Terminal::clearRows(i32 start, i32 end) {
        // Ensure we don't loop infinitely if start > end
        if (start > end) std::swap(start, end);

        for (i32 i = start; i <= end; ++i) {
            this->clearRow(i);
        }

        // Optional: Move cursor back to the start of the cleared block
        this->move(start, 1);
        return *this;
    }

    Terminal& Terminal::cursor(bool show) {
        std::cout << (show ? "\033[?25h" : "\033[?25l");
        return *this;
    }

    Terminal& Terminal::drawBox(i32 startRow, i32 startCol, i32 width, i32 height, std::string_view title) {
        // 1. Draw the top border
        move(startRow, startCol);
        std::cout << "┌";
        for (i32 i = 0; i < width - 2; ++i) std::cout << "─";
        std::cout << "┐";

        // 2. Draw the sides
        for (i32 i = 1; i < height - 1; ++i) {
            move(startRow + i, startCol);
            std::cout << "│";
            move(startRow + i, startCol + width - 1);
            std::cout << "│";
        }

        // 3. Draw the bottom border
        move(startRow + height - 1, startCol);
        std::cout << "└";
        for (i32 i = 0; i < width - 2; ++i) std::cout << "─";
        std::cout << "┘";

        // 4. Overlay the title if provided
        if (!title.empty()) {
            move(startRow, startCol + (width - title.size() - 2) / 2);
            std::cout << " " << title << " ";
        }

        std::cout.flush();
        return *this;
    }

    Terminal& Terminal::flush() {
        std::cout << std::flush;
        return *this;
    }

    Terminal& Terminal::sink() {
        std::cin.clear();
#if defined(SPIDER_OS_WINDOWS)
        while (_kbhit()) _getch();
#endif
#if defined(SPIDER_OS_LINUX) || defined(SPIDER_OS_MACOS)
        tcflush(STDIN_FILENO, TCIFLUSH);
#endif
        return *this;
    }

    std::pair<i32, i32> Terminal::getSize() {
        std::pair<i32, i32> pair;
#if defined(SPIDER_OS_WINDOWS)
        CONSOLE_SCREEN_BUFFER_INFO csbi;
        if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
            pair.first = csbi.srWindow.Right - csbi.srWindow.Left + 1;
            pair.second = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
        }
#endif
#if defined(SPIDER_OS_LINUX) || defined(SPIDER_OS_MACOS)
        struct winsize w;
        if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0) {
            pair.first = w.ws_col;
            pair.second = w.ws_row;
        }
#endif
        return pair;
    }

    Terminal& Terminal::wait() {
        sink();
        std::cin.get();
        return *this;
    }

    u8 Terminal::getKey() {
#if defined(SPIDER_OS_WINDOWS)
        i32 ch = _getch();
        if (ch == 0 || ch == 224) {
            switch (_getch()) {
            case 72: return Terminal::UP;
            case 80: return Terminal::DOWN;
            case 75: return Terminal::LEFT;
            case 77: return Terminal::RIGHT;
            default: return Terminal::UNKNOWN;
            }
        }
        if (ch == 13) return Terminal::ENTER;
        if (ch == 27) return Terminal::ESC;
        if (ch == 8)  return Terminal::BACKSPACE;
        return Terminal::UNKNOWN;
#endif
#if defined(SPIDER_OS_LINUX) || defined(SPIDER_OS_MACOS)
        struct termios oldt, newt;
        tcgetattr(STDIN_FILENO, &oldt);
        newt = oldt;
        newt.c_lflag &= ~(ICANON | ECHO);
        tcsetattr(STDIN_FILENO, TCSANOW, &newt);

        u8 result = Terminal::UNKNOWN;
        int ch = getchar();

        if (ch == 27) { // Potential Escape Sequence
            // Use a small timeout or check if more chars are in buffer
            // to distinguish between 'Esc' key and 'Arrow' sequence
            // Another Win for the Win API
            struct timeval tv = { 0, 10000 }; // 10ms wait
            fd_set fds;
            FD_ZERO(&fds);
            FD_SET(STDIN_FILENO, &fds);

            if (select(1, &fds, NULL, NULL, &tv) > 0) {
                if (getchar() == '[') {
                    switch (getchar()) {
                    case 'A': result = Terminal::UP; break;
                    case 'B': result = Terminal::DOWN; break;
                    case 'D': result = Terminal::LEFT; break;
                    case 'C': result = Terminal::RIGHT; break;
                    }
                }
            } else {
                result = Terminal::ESC;
            }
        } else if (ch == 10)  result = Terminal::ENTER;
        else if (ch == 127) result = Terminal::BACKSPACE;
        else result = (u8)ch;

        tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
        return result;
#endif
    }

    u8 Terminal::getKeyNb() {
#if defined(SPIDER_OS_WINDOWS)
        if (_kbhit()) return getKey();
        return Terminal::UNKNOWN;
#endif
#if defined(SPIDER_OS_LINUX) || defined(SPIDER_OS_MACOS)
        struct timeval tv = { 0, 0 };
        fd_set fds;
        FD_ZERO(&fds);
        FD_SET(STDIN_FILENO, &fds);
        // select() returns > 0 if there is data to read
        if (select(1, &fds, NULL, NULL, &tv) > 0) {
            return getKey();
        }
        return Terminal::UNKNOWN;
#endif
    }

}

#endif
