import XCTest
@testable import SaplingEditor

final class EditorScrollStabilityTests: XCTestCase {
    func testRenderMetricsCountScrollRestorationDuringDirtyEdit() {
        var instrumentation = EditorInstrumentationSnapshot()
        instrumentation.recordRenderPass(EditorRenderPassMetric(
            reason: .sourceChange,
            durationMilliseconds: 0.4,
            characterCount: 1_200,
            lineCount: 80,
            dirtyLineCount: 3,
            activeLineIndex: 40,
            isFullRender: false,
            restoredScrollPosition: true
        ))

        XCTAssertEqual(instrumentation.renderPassCount, 1)
        XCTAssertEqual(instrumentation.fullRenderCount, 0)
        XCTAssertEqual(instrumentation.totalDirtyLineCount, 3)
        XCTAssertEqual(instrumentation.lastDirtyLineCount, 3)
        XCTAssertEqual(instrumentation.scrollRestorationCount, 1)
    }

    func testRapidNavigationSchedulesOnlyOldAndNewActiveLines() {
        let source = (1...500).map { "Line \($0)" }.joined(separator: "\n")

        let firstJump = EditorDirtyLineInvalidator.plan(
            previousText: source,
            currentText: source,
            previousActiveLineIndex: 10,
            currentActiveLineIndex: 250
        )
        let secondJump = EditorDirtyLineInvalidator.plan(
            previousText: source,
            currentText: source,
            previousActiveLineIndex: 250,
            currentActiveLineIndex: 490
        )

        XCTAssertEqual(firstJump.dirtyLineIndexes, [10, 250])
        XCTAssertEqual(secondJump.dirtyLineIndexes, [250, 490])
        XCTAssertFalse(firstJump.isFullRender)
        XCTAssertFalse(secondJump.isFullRender)
    }

    func testEditingWhileScrolledDoesNotRequireFullRender() {
        let previous = (1...200).map { "Line \($0)" }.joined(separator: "\n")
        var lines = previous.components(separatedBy: "\n")
        lines[120] = "Line 121 edited while viewport is away from the top"
        let current = lines.joined(separator: "\n")

        let plan = EditorDirtyLineInvalidator.plan(
            previousText: previous,
            currentText: current,
            previousActiveLineIndex: 120,
            currentActiveLineIndex: 120
        )
        var instrumentation = EditorInstrumentationSnapshot()
        instrumentation.recordRenderPass(EditorRenderPassMetric(
            reason: plan.reason,
            durationMilliseconds: 0.5,
            characterCount: current.utf16.count,
            lineCount: 200,
            dirtyLineCount: plan.dirtyLineCount,
            activeLineIndex: 120,
            isFullRender: plan.isFullRender,
            restoredScrollPosition: true
        ))

        XCTAssertEqual(plan.reason, .sourceChange)
        XCTAssertFalse(plan.isFullRender)
        XCTAssertEqual(plan.dirtyLineIndexes, [119, 120, 121])
        XCTAssertEqual(instrumentation.scrollRestorationCount, 1)
    }
}
