import XCTest
@testable import SaplingEditor

final class EditorCursorRegressionTests: XCTestCase {
    func testCursorMovementUpdatesActiveLineWithoutChangingSelectionLength() {
        let source = "Alpha\nBeta\nGamma"
        var state = EditorState(document: document(source: source))

        state.updateSelection(EditorSelection(location: location(of: "Beta", in: source), length: 0))

        XCTAssertEqual(state.selection, EditorSelection(location: 6, length: 0))
        XCTAssertEqual(state.activeLineIndex, 1)
        XCTAssertEqual(state.activeColumnNumber, 1)
    }

    func testSelectionChangeWithinLinePreservesActiveLine() {
        let source = "Alpha\nBeta\nGamma"
        var state = EditorState(document: document(source: source))

        state.updateSelection(EditorSelection(location: location(of: "eta", in: source), length: 2))

        XCTAssertEqual(state.selection.length, 2)
        XCTAssertEqual(state.activeLineIndex, 1)
        XCTAssertEqual(state.activeColumnNumber, 2)
    }

    func testMultiLineSelectionUsesSelectionAnchorForActiveLine() {
        let source = "Alpha\nBeta\nGamma"
        var state = EditorState(document: document(source: source))
        let anchor = location(of: "Beta", in: source)
        let end = location(of: "Gamma", in: source) + "Gamma".utf16.count

        state.updateSelection(EditorSelection(location: anchor, length: end - anchor))

        XCTAssertEqual(state.selection.location, anchor)
        XCTAssertEqual(state.selection.length, 10)
        XCTAssertEqual(state.activeLineIndex, 1)
    }

    func testInsertionKeepsCursorAndActiveLineOnEditedLine() {
        let source = "Alpha\nBeta\nGamma"
        let updated = "Alpha\nBeta updated\nGamma"
        var state = EditorState(document: document(source: source))

        state.updateSelection(EditorSelection(location: location(of: "Beta", in: source) + 4, length: 0))
        state.updateSource(updated)
        state.updateSelection(EditorSelection(location: location(of: "updated", in: updated) + 7, length: 0))

        XCTAssertEqual(state.activeLineIndex, 1)
        XCTAssertEqual(state.activeColumnNumber, 13)
    }

    func testDeletionClampsSelectionAndActiveLine() {
        let source = "Alpha\nBeta\nGamma"
        var state = EditorState(document: document(source: source))

        state.updateSelection(EditorSelection(location: source.utf16.count, length: 0))
        state.updateSource("Alpha")

        XCTAssertEqual(state.selection.location, 5)
        XCTAssertEqual(state.selection.length, 0)
        XCTAssertEqual(state.activeLineIndex, 0)
    }

    func testLineBreakMovesActiveLineAfterNativeSelectionUpdate() {
        let source = "Alpha Beta"
        let updated = "Alpha\n Beta"
        var state = EditorState(document: document(source: source))

        state.updateSelection(EditorSelection(location: 5, length: 0))
        state.updateSource(updated)
        state.updateSelection(EditorSelection(location: 6, length: 0))

        XCTAssertEqual(state.activeLineIndex, 1)
        XCTAssertEqual(state.activeColumnNumber, 1)
    }

    func testActiveLineTransitionsAcrossDocument() {
        let source = "One\nTwo\nThree\nFour"
        var state = EditorState(document: document(source: source))

        state.updateSelection(EditorSelection(location: location(of: "Four", in: source), length: 0))
        XCTAssertEqual(state.activeLineIndex, 3)

        state.updateSelection(EditorSelection(location: location(of: "One", in: source), length: 0))
        XCTAssertEqual(state.activeLineIndex, 0)
    }

    private func document(source: String) -> EditorDocument {
        EditorDocument(
            url: URL(fileURLWithPath: "/tmp/EditorCursorRegressionTests.md"),
            title: "EditorCursorRegressionTests",
            source: source
        )
    }

    private func location(of needle: String, in source: String) -> Int {
        (source as NSString).range(of: needle).location
    }
}
