//
//  NativeTextEditorPureLogicTests.swift
//  oAITests
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen

import Testing
import Foundation
import AppKit
import SwiftUI
@testable import Confab

@Suite("NativeTextEditor.inlineCodeRanges")
struct NativeTextEditorPureLogicTests {

    @Test("No backticks yields no ranges")
    func noBackticks() {
        #expect(NativeTextEditor.inlineCodeRanges(in: "hello world").isEmpty)
    }

    @Test("A single closed backtick pair is matched, backticks included")
    func singleClosedPair() {
        let text = "Hello `code` world"
        let ranges = NativeTextEditor.inlineCodeRanges(in: text)
        #expect(ranges.count == 1)
        let matched = (text as NSString).substring(with: ranges[0])
        #expect(matched == "`code`")
    }

    @Test("An unterminated single backtick is not matched")
    func unterminatedBacktick() {
        let text = "Hello `code world"
        #expect(NativeTextEditor.inlineCodeRanges(in: text).isEmpty)
    }

    @Test("Multiple closed pairs are all matched")
    func multiplePairs() {
        let text = "`one` and `two` and `three`"
        let ranges = NativeTextEditor.inlineCodeRanges(in: text)
        #expect(ranges.count == 3)
        let nsText = text as NSString
        #expect(ranges.map { nsText.substring(with: $0) } == ["`one`", "`two`", "`three`"])
    }

    @Test("Backtick pair cannot span a newline")
    func doesNotSpanNewline() {
        let text = "`code\nblock`"
        #expect(NativeTextEditor.inlineCodeRanges(in: text).isEmpty)
    }

    @Test("Empty string yields no ranges")
    func emptyString() {
        #expect(NativeTextEditor.inlineCodeRanges(in: "").isEmpty)
    }

    @Test("Adjacent backtick pairs on the same line are each matched separately")
    func adjacentPairs() {
        let text = "`a``b`"
        let ranges = NativeTextEditor.inlineCodeRanges(in: text)
        let nsText = text as NSString
        #expect(ranges.map { nsText.substring(with: $0) } == ["`a`", "`b`"])
    }
}

@Suite("NativeTextEditor.fencedCodeBlockRanges")
struct NativeTextEditorFencedBlockTests {

    @Test("No fences yields no ranges")
    func noFences() {
        #expect(NativeTextEditor.fencedCodeBlockRanges(in: "hello world").isEmpty)
    }

    @Test("A closed multi-line fence is matched in full, including the language tag")
    func closedMultilineFence() {
        let text = "```python\nprint(\"hi\")\n```"
        let ranges = NativeTextEditor.fencedCodeBlockRanges(in: text)
        #expect(ranges.count == 1)
        #expect((text as NSString).substring(with: ranges[0]) == text)
    }

    @Test("An unterminated fence (no closing triple) is not matched")
    func unterminatedFence() {
        let text = "```python\nprint(\"hi\")"
        #expect(NativeTextEditor.fencedCodeBlockRanges(in: text).isEmpty)
    }

    @Test("Text before and after a fence is excluded from the match")
    func surroundingTextExcluded() {
        let text = "before\n```\ncode\n```\nafter"
        let ranges = NativeTextEditor.fencedCodeBlockRanges(in: text)
        #expect(ranges.count == 1)
        #expect((text as NSString).substring(with: ranges[0]) == "```\ncode\n```")
    }
}

@Suite("NativeTextEditor.codeStyledRanges")
struct NativeTextEditorCodeStyledRangesTests {

    @Test("Combines a fenced block and a separate inline span")
    func combinesFencedAndInline() {
        let text = "See `foo` then:\n```\nbar\n```"
        let ranges = NativeTextEditor.codeStyledRanges(in: text)
        let nsText = text as NSString
        #expect(ranges.map { nsText.substring(with: $0) } == ["`foo`", "```\nbar\n```"])
    }

    @Test("A backtick inside a fenced block's own content is not separately re-matched as inline")
    func backtickInsideFenceNotDoubleMatched() {
        let text = "```\nuse `code` here\n```"
        let ranges = NativeTextEditor.codeStyledRanges(in: text)
        #expect(ranges.count == 1)
        #expect((text as NSString).substring(with: ranges[0]) == text)
    }
}

@Suite("NativeTextEditor.fencedCodeBlocks (language + code-content extraction)")
struct NativeTextEditorFencedCodeBlocksTests {

    @Test("Extracts the language tag and the code content, excluding fences and the tag line")
    func extractsLanguageAndCodeContent() {
        let text = "```python\nprint(\"hi\")\n```"
        let blocks = NativeTextEditor.fencedCodeBlocks(in: text)
        #expect(blocks.count == 1)
        #expect(blocks[0].language == "python")
        let nsText = text as NSString
        #expect(nsText.substring(with: blocks[0].codeRange) == "print(\"hi\")\n")
    }

    @Test("No language tag yields a nil language")
    func noLanguageTagYieldsNil() {
        let text = "```\ncode\n```"
        let blocks = NativeTextEditor.fencedCodeBlocks(in: text)
        #expect(blocks.count == 1)
        #expect(blocks[0].language == nil)
    }

    @Test("Language alias is passed through as typed, unresolved (SyntaxHighlighter resolves aliases itself)")
    func languageAliasPassthrough() {
        let text = "```py\nprint(1)\n```"
        let blocks = NativeTextEditor.fencedCodeBlocks(in: text)
        #expect(blocks[0].language == "py")
    }

    @Test("Unterminated fence yields no blocks")
    func unterminatedFenceYieldsNoBlocks() {
        #expect(NativeTextEditor.fencedCodeBlocks(in: "```python\nprint(1)").isEmpty)
    }
}

@Suite("SyntaxHighlighter runs → NSColor")
struct SyntaxHighlighterBridgingTests {

    // NB: bridging via `NSAttributedString(highlighted)` does NOT work — that path stores
    // SwiftUI's `.foregroundColor` under a private `SwiftUI.ForegroundColor` key, not the
    // standard Cocoa `.foregroundColor` key (confirmed empirically before landing on this
    // `.runs`-based approach, which is what `NativeTextEditor` actually uses in production).

    @Test("A Python keyword's foreground color is readable via AttributedString.runs and converts to NSColor")
    func keywordColorReadableViaRuns() {
        let highlighted = SyntaxHighlighter.highlight(code: "def foo():", language: "python")
        #expect(!highlighted.runs.isEmpty)

        var foundKeywordColor = false
        let expected = NSColor(SyntaxHighlighter.keywordColor)
        for run in highlighted.runs {
            guard let color = run.foregroundColor else { continue }
            if NSColor(color).usingColorSpace(.deviceRGB) == expected.usingColorSpace(.deviceRGB) {
                foundKeywordColor = true
            }
        }
        #expect(foundKeywordColor)
    }
}
