#pragma once

#include <spider/runtime/common.hpp>

#include <iostream>
#include <string>
#include <string_view>
#include <sstream>

namespace spider {

    class Terminal {
    public:

        static const char* RESET;
        static const char* BOLD;
        static const char* ITALIC;
        static const char* FAINT;
        static const char* STRIKE;

        static const char* FG_BLACK;   static const char* FG_B_BLACK;
        static const char* FG_RED;     static const char* FG_B_RED;
        static const char* FG_GREEN;   static const char* FG_B_GREEN;
        static const char* FG_YELLOW;  static const char* FG_B_YELLOW;
        static const char* FG_BLUE;    static const char* FG_B_BLUE;
        static const char* FG_MAGENTA; static const char* FG_B_MAGENTA;
        static const char* FG_CYAN;    static const char* FG_B_CYAN;
        static const char* FG_WHITE;   static const char* FG_B_WHITE;

        static const char* BG_BLACK;   static const char* BG_B_BLACK;
        static const char* BG_RED;     static const char* BG_B_RED;
        static const char* BG_GREEN;   static const char* BG_B_GREEN;
        static const char* BG_YELLOW;  static const char* BG_B_YELLOW;
        static const char* BG_BLUE;    static const char* BG_B_BLUE;
        static const char* BG_MAGENTA; static const char* BG_B_MAGENTA;
        static const char* BG_CYAN;    static const char* BG_B_CYAN;
        static const char* BG_WHITE;   static const char* BG_B_WHITE;

    public:

        // Key Definitions (ASCII OK) //
        static constexpr const u8 UP = 0x80;
        static constexpr const u8 DOWN = 0x81;
        static constexpr const u8 LEFT = 0x82;
        static constexpr const u8 RIGHT = 0x83;
        static constexpr const u8 ENTER = 0x84;
        static constexpr const u8 ESC = 0x85;
        static constexpr const u8 BACKSPACE = 0x86;
        static constexpr const u8 UNKNOWN = 0xFF;

    public:

        Terminal();

        ~Terminal();

    public:

        /**
         * Sets a style
         */
        Terminal& style(const std::string_view code);

        /**
         * Fills the screen with a specific background color.
         * @param color The background color constant (e.g., Terminal::BG_BLUE)
         */
        Terminal& fill(const std::string_view color);

        /**
         * Moves the cursor.
         */
        Terminal& move(i32 row, i32 col);

        /**
         * Clears the screen
         */
        Terminal& cls();

        Terminal& altbuff(bool enable);

        Terminal& scrollRange(i32 start, i32 end);

        Terminal& undoSRange();

        /**
         * Clears a specific row.
         * @param row The 1-based index of the row to clear.
         */
        Terminal& clearRow(i32 row);

        /**
         * Clears a range of rows (inclusive).
         * @param start The first row.
         * @param end The last row.
         */
        Terminal& clearRows(i32 start, i32 end);

        /**
         * Shows / Hides the cursor.
         */
        Terminal& cursor(bool show);

    public:

        Terminal& drawBox(i32 startRow, i32 startCol, i32 width, i32 height, std::string_view title);

    public:

        /**
         * Flushes the output buffer
         */
        Terminal& flush();

        /**
         * Clears the input buffer.
         * Useful for some specific input cases
         */
        Terminal& sink();

    public:

        Terminal& wait();

        u8 getKey();

        /**
         * Get key non blocking
         */
        u8 getKeyNb();

        std::pair<i32, i32> getSize();

    public:

        template <typename T>
        Terminal& print(const T& msg) {
            std::cout << msg;
            return *this;
        }

        template <typename T>
        Terminal& println(const T& msg) {
            std::cout << msg << "\n";
            return *this;
        }

        template <typename T>
        Terminal& print_center(i32 width, const T& msg) {
            // to string
            std::ostringstream oss;
            oss << msg;
            std::string s = oss.str();

            // then print
            if (s.length() >= isize(width)) {
                std::cout << s;
            } else {
                i32 total_padding = width - s.length();
                i32 left_padding = total_padding / 2;
                std::cout << std::string(left_padding, ' ');
                std::cout << s;
                std::cout << std::string(total_padding - left_padding, ' ');
            }
            return *this;
        }

        template <typename T>
        Terminal& read(T& var) {
            std::cin >> var;
            return *this;
        }

    };

}