import Foundation
import SwiftUI
import SaplingCore
import SaplingRenderer

#if os(macOS)
import AppKit
#elseif os(iOS)
import UIKit
#endif

@MainActor
public final class HybridMarkdownEditorViewModel: ObservableObject, EditorCoordinator {
    @Published public private(set) var state: EditorState
    public private(set) var instrumentation = EditorInstrumentationSnapshot()

    public init(document: MarkdownDocument, activeLineIndex: Int = 0) {
        self.state = EditorState(
            document: EditorDocument(markdownDocument: document),
            activeLineIndex: activeLineIndex
        )
    }

    public var document: MarkdownDocument {
        state.document.markdownDocument
    }

    public var hasUnsavedChanges: Bool {
        state.hasUnsavedChanges
    }

    public var activeLineIndex: Int {
        state.activeLineIndex
    }

    public func replaceDocument(_ document: EditorDocument) {
        state = EditorState(document: document)
        instrumentation = EditorInstrumentationSnapshot()
    }

    public func updateSource(_ source: String) {
        guard state.document.source != source else { return }
        let previousActiveLineIndex = state.activeLineIndex
        state.updateSource(source)
        instrumentation.recordSourceChange()
        recordActiveLineChangeIfNeeded(previousActiveLineIndex)
    }

    public func updateSelection(_ selection: EditorSelection) {
        guard state.selection != selection else { return }
        let previousActiveLineIndex = state.activeLineIndex
        state.updateSelection(selection)
        instrumentation.recordSelectionChange()
        recordActiveLineChangeIfNeeded(previousActiveLineIndex)
    }

    public func recordRenderPass(_ metric: EditorRenderPassMetric) {
        instrumentation.recordRenderPass(metric)
        #if DEBUG
        EditorDiagnostics.logRenderPass(metric)
        #endif
    }

    public func save() throws {
        try state.document.source.write(to: state.document.url, atomically: true, encoding: .utf8)
        state.markSaved()
    }

    public static func loadDocument(at url: URL) throws -> MarkdownDocument {
        let source = try String(contentsOf: url, encoding: .utf8)
        let title = url.deletingPathExtension().lastPathComponent
        return MarkdownDocument(url: url, title: title, content: source)
    }

    private func recordActiveLineChangeIfNeeded(_ previousActiveLineIndex: Int) {
        if previousActiveLineIndex != state.activeLineIndex {
            instrumentation.recordActiveLineChange()
        }
    }
}

public struct HybridMarkdownEditor: View, EditorView {
    @ObservedObject private var viewModel: HybridMarkdownEditorViewModel
    private let renderer: any MarkdownRendering

    public init(
        viewModel: HybridMarkdownEditorViewModel,
        renderer: any MarkdownRendering = MarkdownRenderer()
    ) {
        self.viewModel = viewModel
        self.renderer = renderer
    }

    public var state: EditorState {
        viewModel.state
    }

    public var body: some View {
        VStack(spacing: 0) {
            NativeMarkdownTextView(
                text: Binding(
                    get: { viewModel.state.document.source },
                    set: { viewModel.updateSource($0) }
                ),
                selection: Binding(
                    get: { viewModel.state.selection },
                    set: { viewModel.updateSelection($0) }
                ),
                activeLineIndex: viewModel.state.activeLineIndex,
                onRenderPass: viewModel.recordRenderPass
            )
            .frame(maxWidth: .infinity, maxHeight: .infinity)

            EditorStatusBar(
                activeLineIndex: viewModel.state.activeLineIndex,
                columnNumber: viewModel.state.activeColumnNumber,
                lineCount: viewModel.state.lines.count,
                hasUnsavedChanges: viewModel.state.hasUnsavedChanges
            )
        }
        .background(platformTextBackground)
    }
}

private struct EditorStatusBar: View {
    let activeLineIndex: Int
    let columnNumber: Int
    let lineCount: Int
    let hasUnsavedChanges: Bool

    var body: some View {
        HStack(spacing: 12) {
            Text("Line \(activeLineIndex + 1)")
            Text("Column \(columnNumber)")
            Text("\(lineCount) lines")
            Text(hasUnsavedChanges ? "Modified" : "Saved")
                .foregroundStyle(hasUnsavedChanges ? .orange : .secondary)
            Spacer()
        }
        .font(.caption.weight(.medium))
        .foregroundStyle(.secondary)
        .padding(.horizontal, 14)
        .padding(.vertical, 7)
        .background(.thinMaterial)
    }
}

#if os(macOS)
private struct NativeMarkdownTextView: NSViewRepresentable {
    @Binding var text: String
    @Binding var selection: EditorSelection
    let activeLineIndex: Int
    let onRenderPass: (EditorRenderPassMetric) -> Void

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeNSView(context: Context) -> NSScrollView {
        let scrollView = ComfortableEditorScrollView()
        scrollView.hasVerticalScroller = true
        scrollView.hasHorizontalScroller = false
        scrollView.autohidesScrollers = true
        scrollView.borderType = .noBorder
        scrollView.drawsBackground = true
        scrollView.backgroundColor = .textBackgroundColor

        let textStorage = NSTextStorage()
        let layoutManager = NSLayoutManager()
        let textContainer = NSTextContainer(size: NSSize(width: 0, height: CGFloat.greatestFiniteMagnitude))
        textContainer.widthTracksTextView = true
        textContainer.heightTracksTextView = false
        layoutManager.addTextContainer(textContainer)
        textStorage.addLayoutManager(layoutManager)

        let textView = EditorTextView(frame: scrollView.contentView.bounds, textContainer: textContainer)
        textView.autoresizingMask = [.width]
        textView.minSize = NSSize(width: 0, height: scrollView.contentSize.height)
        textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
        textView.isVerticallyResizable = true
        textView.isHorizontallyResizable = false

        textView.delegate = context.coordinator
        textView.string = text
        textView.isRichText = false
        textView.isEditable = true
        textView.isSelectable = true
        textView.isAutomaticQuoteSubstitutionEnabled = false
        textView.isAutomaticDashSubstitutionEnabled = false
        textView.isAutomaticTextReplacementEnabled = false
        textView.allowsUndo = true
        textView.usesFindPanel = true
        textView.isContinuousSpellCheckingEnabled = true
        textView.backgroundColor = .textBackgroundColor
        textView.insertionPointColor = .controlAccentColor
        textView.font = .systemFont(ofSize: 16, weight: .regular)
        textView.textContainer?.widthTracksTextView = true
        textView.textContainer?.containerSize = NSSize(
            width: scrollView.contentSize.width,
            height: CGFloat.greatestFiniteMagnitude
        )

        scrollView.documentView = textView
        scrollView.editorTextView = textView
        scrollView.updateEditorInsets()
        context.coordinator.applyHybridAttributes(to: textView)
        context.coordinator.requestInitialFocus(for: textView)
        return scrollView
    }

    func updateNSView(_ scrollView: NSScrollView, context: Context) {
        context.coordinator.parent = self
        guard let textView = scrollView.documentView as? NSTextView else { return }

        if textView.string != text {
            context.coordinator.performProgrammaticUpdate {
                textView.string = text
            }
            context.coordinator.invalidateStylingCache()
        }

        let selectedRange = selection.range
        if textView.selectedRange() != selectedRange,
           selectedRange.location <= textView.string.utf16.count {
            context.coordinator.setSelection(selectedRange, in: textView)
        }

        context.coordinator.applyHybridAttributes(to: textView)
        context.coordinator.requestInitialFocus(for: textView)
    }

    final class Coordinator: NSObject, NSTextViewDelegate {
        var parent: NativeMarkdownTextView
        private var programmaticUpdateDepth = 0
        private var lastStyledText: String?
        private var lastStyledActiveLineIndex: Int?
        private var didFocusTextView = false

        init(_ parent: NativeMarkdownTextView) {
            self.parent = parent
        }

        func textDidChange(_ notification: Notification) {
            guard !isPerformingProgrammaticUpdate else { return }
            guard let textView = notification.object as? NSTextView else { return }

            parent.text = textView.string
            parent.selection = EditorSelection(range: textView.selectedRange())
            applyHybridAttributes(to: textView)
        }

        func textViewDidChangeSelection(_ notification: Notification) {
            guard !isPerformingProgrammaticUpdate else { return }
            guard let textView = notification.object as? NSTextView else { return }
            let newSelection = EditorSelection(range: textView.selectedRange())
            guard parent.selection != newSelection else { return }
            applyHybridAttributes(to: textView)
            parent.selection = newSelection
        }

        func applyHybridAttributes(to textView: NSTextView) {
            guard let textStorage = textView.textStorage else { return }
            let invalidationPlan = invalidationPlan(for: textView.string)
            guard invalidationPlan.requiresStyling else { return }

            let selectedRange = textView.selectedRange()
            let visibleOrigin = textView.enclosingScrollView?.contentView.bounds.origin
            let start = Date()
            var stylingResult = MarkdownTextStylingResult.empty
            var didRestoreVisibleOrigin = false
            performProgrammaticUpdate {
                stylingResult = MarkdownTextStyler.apply(
                    to: textStorage,
                    invalidationPlan: invalidationPlan,
                    activeLineIndex: parent.activeLineIndex,
                    backgroundColor: .textBackgroundColor,
                    activeLineBackgroundColor: .controlAccentColor.withAlphaComponent(0.10),
                    textColor: .labelColor,
                    secondaryTextColor: .secondaryLabelColor,
                    accentColor: .controlAccentColor
                )
                if textView.selectedRange() != selectedRange,
                   selectedRange.location <= textView.string.utf16.count {
                    textView.setSelectedRange(selectedRange)
                }
                didRestoreVisibleOrigin = restoreVisibleOrigin(visibleOrigin, in: textView)
            }

            lastStyledText = textView.string
            lastStyledActiveLineIndex = parent.activeLineIndex
            parent.onRenderPass(EditorRenderPassMetric(
                reason: invalidationPlan.reason,
                durationMilliseconds: Date().timeIntervalSince(start) * 1000,
                characterCount: textView.string.utf16.count,
                lineCount: stylingResult.totalLineCount,
                dirtyLineCount: stylingResult.styledLineCount,
                activeLineIndex: parent.activeLineIndex,
                isFullRender: invalidationPlan.isFullRender,
                restoredScrollPosition: didRestoreVisibleOrigin
            ))
        }

        func setSelection(_ range: NSRange, in textView: NSTextView) {
            guard textView.selectedRange() != range else { return }
            performProgrammaticUpdate {
                textView.setSelectedRange(range)
                textView.scrollRangeToVisible(range)
            }
        }

        func requestInitialFocus(for textView: NSTextView) {
            guard !didFocusTextView else { return }

            DispatchQueue.main.async { [weak self, weak textView] in
                guard let self,
                      let textView,
                      let window = textView.window,
                      !self.didFocusTextView
                else { return }

                if window.firstResponder !== textView {
                    window.makeFirstResponder(textView)
                }

                self.didFocusTextView = window.firstResponder === textView
            }
        }

        func performProgrammaticUpdate(_ updates: () -> Void) {
            programmaticUpdateDepth += 1
            defer {
                programmaticUpdateDepth -= 1
            }
            updates()
        }

        func invalidateStylingCache() {
            lastStyledText = nil
            lastStyledActiveLineIndex = nil
        }

        private var isPerformingProgrammaticUpdate: Bool {
            programmaticUpdateDepth > 0
        }

        private func invalidationPlan(for text: String) -> EditorDirtyLineInvalidationPlan {
            EditorDirtyLineInvalidator.plan(
                previousText: lastStyledText,
                currentText: text,
                previousActiveLineIndex: lastStyledActiveLineIndex,
                currentActiveLineIndex: parent.activeLineIndex
            )
        }

        private func restoreVisibleOrigin(_ origin: NSPoint?, in textView: NSTextView) -> Bool {
            guard let origin,
                  let scrollView = textView.enclosingScrollView
            else { return false }

            let maxY = max(0, textView.bounds.height - scrollView.contentView.bounds.height)
            let maxX = max(0, textView.bounds.width - scrollView.contentView.bounds.width)
            let clampedOrigin = NSPoint(
                x: max(0, min(origin.x, maxX)),
                y: max(0, min(origin.y, maxY))
            )
            scrollView.contentView.scroll(to: clampedOrigin)
            scrollView.reflectScrolledClipView(scrollView.contentView)
            return true
        }
    }
}

private final class EditorTextView: NSTextView {
    override var acceptsFirstResponder: Bool {
        true
    }
}

private final class ComfortableEditorScrollView: NSScrollView {
    weak var editorTextView: NSTextView?

    override func layout() {
        super.layout()
        updateEditorInsets()
    }

    func updateEditorInsets() {
        guard let editorTextView else { return }

        let readableWidth: CGFloat = 760
        let horizontalInset = max(36, floor((contentView.bounds.width - readableWidth) / 2))
        let targetInset = NSSize(width: horizontalInset, height: 38)

        if editorTextView.textContainerInset != targetInset {
            editorTextView.textContainerInset = targetInset
        }
    }
}
#elseif os(iOS)
private struct NativeMarkdownTextView: UIViewRepresentable {
    @Binding var text: String
    @Binding var selection: EditorSelection
    let activeLineIndex: Int
    let onRenderPass: (EditorRenderPassMetric) -> Void

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.delegate = context.coordinator
        textView.text = text
        textView.allowsEditingTextAttributes = false
        textView.autocorrectionType = .yes
        textView.smartDashesType = .no
        textView.smartQuotesType = .no
        textView.font = .systemFont(ofSize: 17, weight: .regular)
        textView.textContainerInset = UIEdgeInsets(top: 28, left: 24, bottom: 28, right: 24)
        textView.backgroundColor = .systemBackground
        context.coordinator.applyHybridAttributes(to: textView)
        context.coordinator.requestInitialFocus(for: textView)
        return textView
    }

    func updateUIView(_ textView: UITextView, context: Context) {
        context.coordinator.parent = self
        if textView.text != text {
            context.coordinator.performProgrammaticUpdate {
                textView.text = text
            }
            context.coordinator.invalidateStylingCache()
        }
        context.coordinator.applyHybridAttributes(to: textView)
        context.coordinator.requestInitialFocus(for: textView)
    }

    final class Coordinator: NSObject, UITextViewDelegate {
        var parent: NativeMarkdownTextView
        private var programmaticUpdateDepth = 0
        private var lastStyledText: String?
        private var lastStyledActiveLineIndex: Int?
        private var didFocusTextView = false

        init(_ parent: NativeMarkdownTextView) {
            self.parent = parent
        }

        func textViewDidChange(_ textView: UITextView) {
            guard !isPerformingProgrammaticUpdate else { return }
            parent.text = textView.text
            parent.selection = EditorSelection(range: textView.selectedRange)
            applyHybridAttributes(to: textView)
        }

        func textViewDidChangeSelection(_ textView: UITextView) {
            guard !isPerformingProgrammaticUpdate else { return }
            let newSelection = EditorSelection(range: textView.selectedRange)
            guard parent.selection != newSelection else { return }
            applyHybridAttributes(to: textView)
            parent.selection = newSelection
        }

        func applyHybridAttributes(to textView: UITextView) {
            let invalidationPlan = invalidationPlan(for: textView.text)
            guard invalidationPlan.requiresStyling else { return }

            let selectedRange = textView.selectedRange
            let contentOffset = textView.contentOffset
            let start = Date()
            var stylingResult = MarkdownTextStylingResult.empty
            var didRestoreContentOffset = false
            performProgrammaticUpdate {
                stylingResult = MarkdownTextStyler.apply(
                    to: textView.textStorage,
                    invalidationPlan: invalidationPlan,
                    activeLineIndex: parent.activeLineIndex,
                    backgroundColor: .systemBackground,
                    activeLineBackgroundColor: .systemBlue.withAlphaComponent(0.10),
                    textColor: .label,
                    secondaryTextColor: .secondaryLabel,
                    accentColor: .systemBlue
                )
                if textView.selectedRange != selectedRange,
                   selectedRange.location <= textView.text.utf16.count {
                    textView.selectedRange = selectedRange
                }
                textView.setContentOffset(clampedContentOffset(contentOffset, in: textView), animated: false)
                didRestoreContentOffset = true
            }

            lastStyledText = textView.text
            lastStyledActiveLineIndex = parent.activeLineIndex
            parent.onRenderPass(EditorRenderPassMetric(
                reason: invalidationPlan.reason,
                durationMilliseconds: Date().timeIntervalSince(start) * 1000,
                characterCount: textView.text.utf16.count,
                lineCount: stylingResult.totalLineCount,
                dirtyLineCount: stylingResult.styledLineCount,
                activeLineIndex: parent.activeLineIndex,
                isFullRender: invalidationPlan.isFullRender,
                restoredScrollPosition: didRestoreContentOffset
            ))
        }

        func performProgrammaticUpdate(_ updates: () -> Void) {
            programmaticUpdateDepth += 1
            defer {
                programmaticUpdateDepth -= 1
            }
            updates()
        }

        func requestInitialFocus(for textView: UITextView) {
            guard !didFocusTextView else { return }

            DispatchQueue.main.async { [weak self, weak textView] in
                guard let self,
                      let textView,
                      textView.window != nil,
                      !self.didFocusTextView
                else { return }

                self.didFocusTextView = textView.becomeFirstResponder()
            }
        }

        func invalidateStylingCache() {
            lastStyledText = nil
            lastStyledActiveLineIndex = nil
        }

        private var isPerformingProgrammaticUpdate: Bool {
            programmaticUpdateDepth > 0
        }

        private func invalidationPlan(for text: String) -> EditorDirtyLineInvalidationPlan {
            EditorDirtyLineInvalidator.plan(
                previousText: lastStyledText,
                currentText: text,
                previousActiveLineIndex: lastStyledActiveLineIndex,
                currentActiveLineIndex: parent.activeLineIndex
            )
        }

        private func clampedContentOffset(_ offset: CGPoint, in textView: UITextView) -> CGPoint {
            let maxX = max(0, textView.contentSize.width - textView.bounds.width)
            let maxY = max(0, textView.contentSize.height - textView.bounds.height)
            return CGPoint(x: max(0, min(offset.x, maxX)), y: max(0, min(offset.y, maxY)))
        }
    }
}
#endif

struct MarkdownTextStylingResult {
    var totalLineCount: Int
    var styledLineCount: Int

    static let empty = MarkdownTextStylingResult(totalLineCount: 0, styledLineCount: 0)
}

enum MarkdownTextStyler {
    #if os(macOS)
    typealias PlatformColor = NSColor
    #elseif os(iOS)
    typealias PlatformColor = UIColor
    #endif

    @discardableResult
    static func apply(
        to textStorage: NSTextStorage,
        invalidationPlan: EditorDirtyLineInvalidationPlan,
        activeLineIndex: Int,
        backgroundColor: PlatformColor,
        activeLineBackgroundColor: PlatformColor,
        textColor: PlatformColor,
        secondaryTextColor: PlatformColor,
        accentColor: PlatformColor
    ) -> MarkdownTextStylingResult {
        let source = textStorage.string as NSString
        let fullRange = NSRange(location: 0, length: source.length)
        let lines = EditorActiveLineTracker.lines(from: source as String, activeLineIndex: activeLineIndex)
        guard fullRange.length > 0 else {
            return MarkdownTextStylingResult(totalLineCount: lines.count, styledLineCount: lines.count)
        }

        textStorage.beginEditing()
        if invalidationPlan.isFullRender {
            textStorage.setAttributes(baseAttributes(textColor: textColor), range: fullRange)
        }

        let renderer = HybridMarkdownLineRenderer()
        let dirtyLineIndexes = Set(invalidationPlan.dirtyLineIndexes)
        var styledLineCount = 0
        for line in lines {
            guard invalidationPlan.isFullRender || dirtyLineIndexes.contains(line.index) else {
                continue
            }

            resetAttributes(in: textStorage, line: line, textColor: textColor)
            styledLineCount += 1

            if line.index == activeLineIndex {
                textStorage.addAttributes([
                    .backgroundColor: activeLineBackgroundColor,
                    .font: monospacedFont(size: 15, weight: .regular)
                ], range: line.range)
                styleSourceLineMarkers(in: textStorage, line: line, secondaryTextColor: secondaryTextColor)
            } else {
                styleRenderedLine(
                    in: textStorage,
                    line: line,
                    renderPlan: renderer.renderPlan(for: line),
                    secondaryTextColor: secondaryTextColor,
                    accentColor: accentColor
                )
            }
        }

        textStorage.endEditing()
        return MarkdownTextStylingResult(totalLineCount: lines.count, styledLineCount: styledLineCount)
    }

    private static func resetAttributes(
        in textStorage: NSTextStorage,
        line: EditorLine,
        textColor: PlatformColor
    ) {
        guard line.range.length > 0 else { return }
        textStorage.setAttributes(baseAttributes(textColor: textColor), range: line.range)
    }

    private static func styleRenderedLine(
        in textStorage: NSTextStorage,
        line: EditorLine,
        renderPlan: HybridMarkdownLineRenderPlan,
        secondaryTextColor: PlatformColor,
        accentColor: PlatformColor
    ) {
        guard line.range.length > 0 else { return }

        if case .heading(let level, let markerRange, let textRange) = renderPlan.kind {
            textStorage.addAttributes([
                .foregroundColor: secondaryTextColor,
                .font: monospacedFont(size: 13, weight: .regular)
            ], range: markerRange)
            textStorage.addAttributes([
                .font: systemFont(size: headingFontSize(level: level), weight: .semibold)
            ], range: textRange)
        }

        styleInlineSpans(
            in: textStorage,
            renderPlan: renderPlan,
            secondaryTextColor: secondaryTextColor,
            accentColor: accentColor
        )
    }

    private static func styleInlineSpans(
        in textStorage: NSTextStorage,
        renderPlan: HybridMarkdownLineRenderPlan,
        secondaryTextColor: PlatformColor,
        accentColor: PlatformColor
    ) {
        for span in renderPlan.spans {
            switch span.kind {
            case .bold:
                textStorage.addAttributes([.font: systemFont(size: 16, weight: .semibold)], range: span.range)
            case .italic:
                textStorage.addAttributes([.font: italicSystemFont(size: 16)], range: span.range)
            case .inlineCode:
                textStorage.addAttributes([
                    .font: monospacedFont(size: 15, weight: .regular),
                    .backgroundColor: accentColor.withAlphaComponent(0.12)
                ], range: span.range)
            case .markdownDelimiter:
                textStorage.addAttributes([.foregroundColor: secondaryTextColor], range: span.range)
            }
        }
    }

    private static func styleSourceLineMarkers(
        in textStorage: NSTextStorage,
        line: EditorLine,
        secondaryTextColor: PlatformColor
    ) {
        applyRegex("(#{1,6}|\\*\\*|\\*|`)", in: textStorage, line: line) { match in
            textStorage.addAttributes([.foregroundColor: secondaryTextColor], range: match.range)
        }
    }

    private static func baseAttributes(textColor: PlatformColor) -> [NSAttributedString.Key: Any] {
        let paragraph = NSMutableParagraphStyle()
        paragraph.lineSpacing = 4
        paragraph.paragraphSpacing = 5
        return [
            .font: systemFont(size: 16, weight: .regular),
            .foregroundColor: textColor,
            .paragraphStyle: paragraph
        ]
    }

    private static func applyRegex(
        _ pattern: String,
        in textStorage: NSTextStorage,
        line: EditorLine,
        handler: (NSTextCheckingResult) -> Void
    ) {
        guard line.range.length > 0,
              let regex = try? NSRegularExpression(pattern: pattern)
        else { return }

        regex.matches(in: textStorage.string, range: line.range).forEach(handler)
    }

    private static func headingFontSize(level: Int) -> CGFloat {
        switch level {
        case 1: 28
        case 2: 23
        case 3: 20
        default: 17
        }
    }

    #if os(macOS)
    private static func systemFont(size: CGFloat, weight: NSFont.Weight) -> NSFont {
        NSFont.systemFont(ofSize: size, weight: weight)
    }

    private static func italicSystemFont(size: CGFloat) -> NSFont {
        NSFontManager.shared.convert(NSFont.systemFont(ofSize: size), toHaveTrait: .italicFontMask)
    }

    private static func monospacedFont(size: CGFloat, weight: NSFont.Weight) -> NSFont {
        NSFont.monospacedSystemFont(ofSize: size, weight: weight)
    }
    #elseif os(iOS)
    private static func systemFont(size: CGFloat, weight: UIFont.Weight) -> UIFont {
        UIFont.systemFont(ofSize: size, weight: weight)
    }

    private static func italicSystemFont(size: CGFloat) -> UIFont {
        UIFont.italicSystemFont(ofSize: size)
    }

    private static func monospacedFont(size: CGFloat, weight: UIFont.Weight) -> UIFont {
        UIFont.monospacedSystemFont(ofSize: size, weight: weight)
    }
    #endif
}

private extension NSRange {
    var upperBound: Int {
        location + length
    }
}

private var platformTextBackground: Color {
    #if os(macOS)
    Color(nsColor: .textBackgroundColor)
    #else
    Color(uiColor: .systemBackground)
    #endif
}
