#ifndef MALACHSCRIPT_INPUTWINDOW_HPP
#define MALACHSCRIPT_INPUTWINDOW_HPP
#include <ncurses.h>
#include <string>
#include <vector>

namespace MalachScriptRepl {
    class InputWindow {
    private:
        WINDOW* _window;

        std::vector<std::u8string> _lines = {u8""};
        int _row = 0;
        int _col = 0;
        int _rightPos = 0;
        int _scroll = 0;
        int _lineCount;

        std::function<void(const std::vector<std::u8string>&)> _onChange;
        const MalachScript::Diagnostics::Diagnostic* _diag;

    public:
        InputWindow(int height, int width, int y, int x) {
            _window = newwin(height, width, y, x);
            keypad(_window, true);
            _lineCount = height;
        }

        inline void RegisterOnChange(std::function<void(const std::vector<std::u8string>&)> listener) {
            _onChange = listener;
        }

        inline void Refresh() { wrefresh(_window); }
        inline void Clear() { wclear(_window); }

        [[nodiscard]] inline int GetInput() const { return wgetch(_window); }
        inline std::pair<int, int> GetCursorPosition() {
            int row, col;
            getyx(_window, row, col);
            return std::pair<int, int>(row, col);
        }

        inline void MoveCursorPosition(int row, int col) {
            auto& line = _lines[row + _scroll];
            if (col > (int)line.size()) {
                col = line.size();
            }
            wmove(_window, row, col);
        }

        inline void MoveCursorLeft() {
            if (_col > 0) {
                wmove(_window, _row, --_col);
                _rightPos = _col;
            }
        }

        inline void MoveCursorRight() {
            auto& line = _lines[_row + _scroll];
            if (_col < (int)line.size()) {
                wmove(_window, _row, ++_col);
                _rightPos = _col;
            }
        }

        inline void MoveCursorUp() {
            if (_row > 0) {
                _col = _rightPos;
                if (_col > (int)_lines[_row - 1 + _scroll].size()) {
                    _col = (int)_lines[_row - 1 + _scroll].size();
                }
                wmove(_window, --_row, _col);
            } else {
                ScrollUp();
            }
        }

        inline void MoveCursorDown() {
            if (_row < _lineCount - 1 && _row + _scroll < (int)_lines.size() - 1) {
                _col = _rightPos;
                if (_col > (int)_lines[_row + 1 + _scroll].size()) {
                    _col = (int)_lines[_row + 1 + _scroll].size();
                }
                wmove(_window, ++_row, _col);
            } else {
                ScrollDown();
            }
        }

        inline void Backspace() {
            if (_col > 0) {
                mvwdelch(_window, _row, --_col);
                auto& line = _lines[_row + _scroll];
                if (_col < (int)line.size()) {
                    line = line.erase(_col, 1);
                    _onChange(_lines);
                }
            }
        }

        inline void Return() {
            _lines.insert(_lines.begin() + _row + _scroll + 1, u8"");
            if (_row >= _lineCount - 1) {
                ScrollDown();
            } else {
                _row++;
                _col = 0;
                for (size_t i = _row - 1; i < _lines.size(); i++) {
                    wmove(_window, i, 0);
                    wclrtoeol(_window);
                    waddstr(_window, (char*)_lines[i].c_str());
                }
                wmove(_window, _row, _col);
            }
            _onChange(_lines);
        }
        inline void Tab() {
            wmove(_window, _row, 0);
            wclrtoeol(_window);
            auto& line = _lines[_row + _scroll];
            if ((int)line.length() <= _col) {
                line += u8"    ";
            } else {
                for (size_t i = 0; i < 4; i++) {
                    line.insert(line.begin() + _col, u8' ');
                }
            }
            waddstr(_window, (char*)line.c_str());
            _col += 4;
            _rightPos = _col;
            wmove(_window, _row, _col);
            _onChange(_lines);
        }

        inline void GoToEndOfLine() {
            _col = _lines[_row + _scroll].size();
            _rightPos = _col;
            wmove(_window, _row, _col);
        }

        inline void GoToStartOfLine() {
            _col = 0;
            _rightPos = 0;
            wmove(_window, _row, _col);
        }

        inline void Input(int c) {
            wmove(_window, _row, 0);
            auto& line = _lines[_row + _scroll];
            if ((int)line.length() <= _col) {
                line += (char8_t)c;
            } else {
                line.insert(line.begin() + _col, c);
            }
            waddstr(_window, (char*)line.c_str());
            _col++;
            _rightPos = _col;
            wmove(_window, _row, _col);
            _onChange(_lines);
        }

        inline void ResetText() {
            std::u8string script;
            for (size_t i = 0; i < _lines.size(); i++) {
                script += _lines[i];
                if (i != _lines.size() - 1) {
                    script += '\n';
                }
            }
            SetScriptWithDiagnostics(script, _diag);
        }

        inline void ScrollDown() {
            if (_scroll + _row < (int)_lines.size() - 1) {
                _scroll++;
                ResetText();

                _col = _rightPos;
                if (_col > (int)_lines[_row + _scroll].size()) {
                    _col = (int)_lines[_row + _scroll].size();
                }
                wmove(_window, _row, _col);
            }
        }
        inline void ScrollUp() {
            if (_scroll <= 0) {
                return;
            }
            _scroll--;
            ResetText();

            _col = _rightPos;
            if (_col > (int)_lines[_row + _scroll].size()) {
                _col = (int)_lines[_row + _scroll].size();
            }
            wmove(_window, _row, _col);
        }

        inline void SetScriptWithDiagnostics(std::u8string& script, const MalachScript::Diagnostics::Diagnostic* diag) {
            _diag = diag;
            auto yx = GetCursorPosition();
            Clear();
            script += u8" ";

            size_t linenum = 0;
            size_t index = 0;
            size_t linestart = 0;
            size_t lineend = 0;

            std::istringstream f((char*)script.data());
            std::string line;
            while (std::getline(f, line)) {
                if ((int)linenum == _scroll) {
                    linestart = index;
                }
                if ((int)linenum == _scroll + _lineCount - 1 || linenum == _lines.size() - 1) {
                    lineend = index + line.size() + 1;
                    break;
                }
                index += line.size() + 1;
                linenum++;
            }
            if (lineend == 0) {
                lineend = script.size();
            }

            if (diag != nullptr && diag->GetSpan().GetStart() >= linestart && diag->GetSpan().GetStart() <= lineend) {
                auto start = diag->GetSpan().GetStart() - linestart;
                auto end = diag->GetSpan().GetEnd() - linestart + 1;
                if (diag->GetSpan().GetStart() > script.size()) {
                    start = script.size() - linestart - 1;
                }
                if (diag->GetSpan().GetEnd() >= script.size()) {
                    end = script.size() - linestart;
                }

                waddnstr(_window, (char*)script.substr(linestart, start).data(), start);
                wattron(_window, COLOR_PAIR(1));
                wattron(_window, A_UNDERLINE);
                waddnstr(_window, (char*)script.substr(start + linestart, end - start).data(), end - start);
                wattroff(_window, A_UNDERLINE);
                wattroff(_window, COLOR_PAIR(1));

                waddnstr(_window, (char*)script.substr(end + linestart, script.size() - end + linestart + 1).data(),
                         script.size() - end + linestart + 1);

                if (start >= script.size() - 1) {
                    wattron(_window, COLOR_PAIR(1));
                    waddch(_window, ' ');
                    wattroff(_window, COLOR_PAIR(1));
                }
            } else {
                waddstr(_window, (char*)script.substr(linestart).data());
            }
            MoveCursorPosition(yx.first, yx.second);
            Refresh();
        }
    };
}

#endif // MALACHSCRIPT_INPUTWINDOW_HPP