#include "gtest/gtest.h"
#include "lumacs/window.hpp"
#include "lumacs/buffer.hpp"

using namespace lumacs;

class WindowTest : public ::testing::Test {
protected:
    std::shared_ptr<Buffer> buffer;
    std::unique_ptr<Window> window;

    void SetUp() override {
        buffer = std::make_shared<Buffer>("test_buffer");
        window = std::make_unique<Window>(buffer);
        // Set a viewport large enough to avoid scroll margin conflicts
        // Margin is 3 (vertical) and 5 (horizontal).
        // Need height > 2*3 = 6, width > 2*5 = 10.
        window->set_viewport_size(20, 10); 
    }
};

TEST_F(WindowTest, InitialState) {
    ASSERT_EQ(window->cursor().line, 0);
    ASSERT_EQ(window->cursor().column, 0);
    
    const auto& vp = window->viewport();
    ASSERT_EQ(vp.scroll_offset, 0);
    ASSERT_EQ(vp.horizontal_offset, 0);
    ASSERT_EQ(vp.width, 20);
    ASSERT_EQ(vp.height, 10);
}

TEST_F(WindowTest, VerticalScrolling) {
    // Fill buffer with enough lines
    for (int i = 0; i < 30; ++i) {
        size_t last_line = buffer->line_count() - 1;
        size_t last_col = buffer->line(last_line).size();
        buffer->insert_newline({last_line, last_col});
    }

    // Viewport height 10, Margin 3.
    // Trigger point for scroll down: scroll + height - margin
    // 0 + 10 - 3 = 7.
    // So line 6 should not scroll. Line 7 should scroll.

    window->set_cursor({6, 0});
    ASSERT_EQ(window->viewport().scroll_offset, 0);

    window->set_cursor({7, 0});
    // New scroll = cursor - height + margin + 1
    // 7 - 10 + 3 + 1 = 1.
    ASSERT_EQ(window->viewport().scroll_offset, 1);
    
    // Verify visible range: [1, 11) (10 lines)
    auto range = window->visible_line_range();
    ASSERT_EQ(range.first, 1);
    ASSERT_EQ(range.second, 11);
}

TEST_F(WindowTest, HorizontalScrolling) {
    // Fill line 0 with text
    std::string long_text = "012345678901234567890123456789"; // 30 chars
    buffer->insert({0,0}, long_text);

    // Viewport width 20, Margin 5.
    // Trigger point for scroll right: scroll + width - margin
    // 0 + 20 - 5 = 15.
    // Col 14 -> no scroll. Col 15 -> scroll.

    window->set_cursor({0, 14});
    ASSERT_EQ(window->viewport().horizontal_offset, 0);
    
    window->set_cursor({0, 15});
    // New scroll = col - width + margin + 1
    // 15 - 20 + 5 + 1 = 1.
    ASSERT_EQ(window->viewport().horizontal_offset, 1);
}

TEST_F(WindowTest, ScrollLinesExplicitly) {
    for (int i = 0; i < 30; ++i) {
        size_t last_line = buffer->line_count() - 1;
        size_t last_col = buffer->line(last_line).size();
        buffer->insert_newline({last_line, last_col});
    }
    
    // Scroll down 2 lines
    window->scroll_lines(2);
    ASSERT_EQ(window->viewport().scroll_offset, 2);
    
    // Cursor was at 0. Top of view is 2.
    // Cursor should be moved to 2.
    ASSERT_EQ(window->cursor().line, 2);
    
    // Scroll up 1 line
    window->scroll_lines(-1);
    ASSERT_EQ(window->viewport().scroll_offset, 1);
}

TEST_F(WindowTest, VisibleLineRange) {
    for (int i = 0; i < 20; ++i) {
        size_t last_line = buffer->line_count() - 1;
        size_t last_col = buffer->line(last_line).size();
        buffer->insert_newline({last_line, last_col});
    }
    
    window->scroll_lines(2);
    // Viewport height 10, offset 2. Range: [2, 12)
    auto range = window->visible_line_range();
    ASSERT_EQ(range.first, 2);
    ASSERT_EQ(range.second, 12);
}