import XCTest
@testable import SaplingEditor

#if os(macOS)
import AppKit

final class MarkdownTextStylerRenderingTests: XCTestCase {
    func testRenderedHeadingHidesMarkerWhileActiveHeadingKeepsSourceVisible() {
        let source = "Intro\n# Heading"
        let renderedStorage = styledStorage(source: source, activeLineIndex: 0)
        let activeStorage = styledStorage(source: source, activeLineIndex: 1)
        let headingMarker = (source as NSString).range(of: "#")
        let headingText = (source as NSString).range(of: "Heading")

        XCTAssertTrue(isHidden(renderedStorage, at: headingMarker.location))
        XCTAssertGreaterThan(font(in: renderedStorage, at: headingText.location).pointSize, 20)
        XCTAssertFalse(isHidden(activeStorage, at: headingMarker.location))
    }

    func testRenderedInlineMarkdownHidesDelimitersOnly() {
        let source = "Intro\nThis has **bold**, *italic*, and `code`."
        let storage = styledStorage(source: source, activeLineIndex: 0)
        let nsSource = source as NSString

        XCTAssertTrue(isHidden(storage, at: nsSource.range(of: "**").location))
        XCTAssertFalse(isHidden(storage, at: nsSource.range(of: "bold").location))
        XCTAssertTrue(isHidden(storage, at: nsSource.range(of: "*italic*").location))
        XCTAssertFalse(isHidden(storage, at: nsSource.range(of: "italic").location))
        XCTAssertTrue(isHidden(storage, at: nsSource.range(of: "`").location))
        XCTAssertNotNil(storage.attribute(.backgroundColor, at: nsSource.range(of: "code").location, effectiveRange: nil))
    }

    func testRenderedTaskListHidesListMarkerAndShowsTaskState() {
        let source = "Intro\n* [x] Done\n* [ ] Todo"
        let storage = styledStorage(source: source, activeLineIndex: 0)
        let nsSource = source as NSString
        let checkedMarker = nsSource.range(of: "* [x]")
        let uncheckedMarker = nsSource.range(of: "* [ ]")
        let doneRange = nsSource.range(of: "Done")

        XCTAssertTrue(isHidden(storage, at: checkedMarker.location))
        XCTAssertFalse(isHidden(storage, at: checkedMarker.location + 2))
        XCTAssertNotNil(storage.attribute(.backgroundColor, at: checkedMarker.location + 2, effectiveRange: nil))
        XCTAssertTrue(isHidden(storage, at: uncheckedMarker.location))
        XCTAssertFalse(isHidden(storage, at: uncheckedMarker.location + 2))
        XCTAssertNotNil(storage.attribute(.strikethroughStyle, at: doneRange.location, effectiveRange: nil))
    }

    func testRenderedCodeBlockHidesFencesAndStylesCodeContent() {
        let source = "Intro\n```swift\nstruct Example {\n    let value = 42\n}\n```"
        let storage = styledStorage(source: source, activeLineIndex: 0)
        let nsSource = source as NSString
        let openingFence = nsSource.range(of: "```swift")
        let language = nsSource.range(of: "swift")
        let code = nsSource.range(of: "let value = 42")
        let keyword = nsSource.range(of: "struct")
        let closingFence = nsSource.range(of: "```", options: .backwards)

        XCTAssertTrue(isHidden(storage, at: openingFence.location))
        XCTAssertFalse(isHidden(storage, at: language.location))
        XCTAssertNotNil(storage.attribute(.backgroundColor, at: code.location, effectiveRange: nil))
        XCTAssertEqual(font(in: storage, at: code.location).fontDescriptor.symbolicTraits.contains(.monoSpace), true)
        XCTAssertGreaterThan(paragraphStyle(in: storage, at: code.location).headIndent, 0)
        XCTAssertEqual(font(in: storage, at: keyword.location).fontDescriptor.symbolicTraits.contains(.bold), true)
        XCTAssertTrue(isHidden(storage, at: closingFence.location))
    }

    func testDirtyCodeBlockRenderingResolvesBlockContext() {
        let source = "Intro\n```swift\nlet value = 42\n```"
        let storage = NSTextStorage(string: source)
        let lineIndex = DocumentLineIndex(source: source)
        let codeLine = 2

        MarkdownTextStyler.apply(
            to: storage,
            lineIndex: lineIndex,
            invalidationPlan: EditorDirtyLineInvalidationPlan(
                reason: .activeLineChange,
                isFullRender: false,
                dirtyLineIndexes: [codeLine]
            ),
            activeLineIndex: 0,
            backgroundColor: .textBackgroundColor,
            activeLineBackgroundColor: .controlAccentColor.withAlphaComponent(0.10),
            textColor: .labelColor,
            secondaryTextColor: .secondaryLabelColor,
            accentColor: .controlAccentColor
        )

        let code = (source as NSString).range(of: "let value = 42")
        XCTAssertNotNil(storage.attribute(.backgroundColor, at: code.location, effectiveRange: nil))
        XCTAssertEqual(font(in: storage, at: code.location).fontDescriptor.symbolicTraits.contains(.monoSpace), true)
    }

    func testRenderedJsonYamlAndMarkdownCodeHighlighting() {
        let json = styledStorage(source: "```json\n{\"name\": \"Sapling\", \"enabled\": true}\n```", activeLineIndex: -1)
        let yaml = styledStorage(source: "```yaml\nname: Sapling\nactive: true\n```", activeLineIndex: -1)
        let markdown = styledStorage(source: "```markdown\n# Heading\nSee **bold** text\n```", activeLineIndex: -1)

        XCTAssertEqual(font(in: json, at: ("```json\n{" as NSString).length).fontDescriptor.symbolicTraits.contains(.bold), true)
        XCTAssertEqual(font(in: yaml, at: ("```yaml\n" as NSString).length).fontDescriptor.symbolicTraits.contains(.bold), true)
        XCTAssertEqual(font(in: markdown, at: ("```markdown\n" as NSString).length).fontDescriptor.symbolicTraits.contains(.bold), true)
    }

    private func styledStorage(source: String, activeLineIndex: Int) -> NSTextStorage {
        let storage = NSTextStorage(string: source)
        let lineIndex = DocumentLineIndex(source: source)
        MarkdownTextStyler.apply(
            to: storage,
            lineIndex: lineIndex,
            invalidationPlan: EditorDirtyLineInvalidator.plan(
                previousText: nil,
                currentLineIndex: lineIndex,
                edit: nil,
                previousActiveLineIndex: nil,
                currentActiveLineIndex: activeLineIndex
            ),
            activeLineIndex: activeLineIndex,
            backgroundColor: .textBackgroundColor,
            activeLineBackgroundColor: .controlAccentColor.withAlphaComponent(0.10),
            textColor: .labelColor,
            secondaryTextColor: .secondaryLabelColor,
            accentColor: .controlAccentColor
        )
        return storage
    }

    private func isHidden(_ storage: NSTextStorage, at location: Int) -> Bool {
        let color = storage.attribute(.foregroundColor, at: location, effectiveRange: nil) as? NSColor
        return color?.alphaComponent == 0 && font(in: storage, at: location).pointSize < 1
    }

    private func font(in storage: NSTextStorage, at location: Int) -> NSFont {
        guard let font = storage.attribute(.font, at: location, effectiveRange: nil) as? NSFont else {
            return NSFont.systemFont(ofSize: 0)
        }
        return font
    }

    private func paragraphStyle(in storage: NSTextStorage, at location: Int) -> NSParagraphStyle {
        storage.attribute(.paragraphStyle, at: location, effectiveRange: nil) as? NSParagraphStyle ?? NSParagraphStyle()
    }
}
#endif
