#include "lumacs/window.hpp"
#include <iostream>

namespace lumacs {

Window::Window(std::shared_ptr<Buffer> buffer) 
    : buffer_(std::move(buffer)) 
{
}

void Window::set_buffer(std::shared_ptr<Buffer> buffer) {
    buffer_ = std::move(buffer);
    cursor_ = {0, 0};
    viewport_.scroll_offset = 0;
    viewport_.horizontal_offset = 0;
}

Window::~Window() = default;

void Window::set_cursor(Position pos) {
    cursor_ = buffer_->clamp_position(pos);
    adjust_scroll();
}

void Window::move_up() {
    if (cursor_.line > 0) {
        cursor_.line--;
        clamp_cursor();
        adjust_scroll();
    }
}

void Window::move_down() {
    if (cursor_.line < buffer_->line_count() - 1) {
        cursor_.line++;
        clamp_cursor();
        adjust_scroll();
    }
}

void Window::move_left() {
    if (cursor_.column > 0) {
        cursor_.column--;
    } else if (cursor_.line > 0) {
        cursor_.line--;
        cursor_.column = buffer_->line(cursor_.line).size();
        adjust_scroll();
    }
}

void Window::move_right() {
    const auto& line = buffer_->line(cursor_.line);
    if (cursor_.column < line.size()) {
        cursor_.column++;
    } else if (cursor_.line < buffer_->line_count() - 1) {
        cursor_.line++;
        cursor_.column = 0;
        adjust_scroll();
    }
}

void Window::move_to_line_start() {
    cursor_.column = 0;
}

void Window::move_to_line_end() {
    cursor_.column = buffer_->line(cursor_.line).size();
}

void Window::set_viewport_size(int width, int height) {
    viewport_.width = std::max(1, width);
    viewport_.height = std::max(1, height);
    adjust_scroll();
}

void Window::scroll_lines(int lines) {
    int max_lines = static_cast<int>(buffer_->line_count());
    int new_offset = viewport_.scroll_offset + lines;
    
    // Clamp offset
    if (new_offset < 0) new_offset = 0;
    // Don't scroll past end of buffer (allow some empty space though)
    if (new_offset >= max_lines) new_offset = max_lines - 1;
    if (new_offset < 0) new_offset = 0;

    viewport_.scroll_offset = new_offset;
    
    // If cursor is now out of view, move it into view
    int cursor_line = static_cast<int>(cursor_.line);
    int view_top = viewport_.scroll_offset;
    int view_bottom = view_top + viewport_.height; // Exclusive
    
    if (cursor_line < view_top) {
        cursor_.line = static_cast<size_t>(view_top);
    } else if (cursor_line >= view_bottom) {
        cursor_.line = static_cast<size_t>(std::max(0, view_bottom - 1));
    }
    
    clamp_cursor();
}

void Window::adjust_scroll() {
    // Vertical scrolling
    if (static_cast<int>(cursor_.line) >= viewport_.scroll_offset + viewport_.height - SCROLL_MARGIN) {
        viewport_.scroll_offset = static_cast<int>(cursor_.line) - viewport_.height + SCROLL_MARGIN + 1;
    }

    if (static_cast<int>(cursor_.line) < viewport_.scroll_offset + SCROLL_MARGIN) {
        viewport_.scroll_offset = (cursor_.line >= static_cast<size_t>(SCROLL_MARGIN))
                                ? static_cast<int>(cursor_.line) - SCROLL_MARGIN
                                : 0;
    }

    viewport_.scroll_offset = std::max(0, viewport_.scroll_offset);
    
    // Horizontal scrolling
    static constexpr int H_SCROLL_MARGIN = 5;
    
    // Scroll right if cursor is beyond right edge
    if (static_cast<int>(cursor_.column) >= viewport_.horizontal_offset + viewport_.width - H_SCROLL_MARGIN) {
        viewport_.horizontal_offset = static_cast<int>(cursor_.column) - viewport_.width + H_SCROLL_MARGIN + 1;
    }
    
    // Scroll left if cursor is before left edge
    if (static_cast<int>(cursor_.column) < viewport_.horizontal_offset + H_SCROLL_MARGIN) {
        viewport_.horizontal_offset = (cursor_.column >= static_cast<size_t>(H_SCROLL_MARGIN))
                                    ? static_cast<int>(cursor_.column) - H_SCROLL_MARGIN
                                    : 0;
    }
    
    viewport_.horizontal_offset = std::max(0, viewport_.horizontal_offset);
}

std::pair<size_t, size_t> Window::visible_line_range() const {
    size_t start = viewport_.scroll_offset;
    size_t end = std::min(buffer_->line_count(),
                         static_cast<size_t>(viewport_.scroll_offset + viewport_.height));
    return {start, end};
}

void Window::clamp_cursor() {
    cursor_ = buffer_->clamp_position(cursor_);
}

} // namespace lumacs
