import AppKit
import SwiftUI

// MARK: - StripedTextView

/// NSTextView subclass that draws alternating row background stripes.
/// Used by both the editor and answer column to provide zebra-striping
/// that visually connects lines across panes.
final class StripedTextView: NSTextView {
    /// Subtle stripe color for odd-numbered lines.
    static let stripeColor = NSColor.secondaryLabelColor.withAlphaComponent(0.04)

    override func drawBackground(in rect: NSRect) {
        super.drawBackground(in: rect)

        guard let layoutManager = layoutManager,
              let textContainer = textContainer else { return }

        let stripeColor = Self.stripeColor

        // We need the absolute line index (not just visible lines) so that
        // the stripe pattern stays consistent when scrolling and matches
        // the answer column. Walk all line fragments from the start of the
        // document, but only fill those that intersect the dirty rect.
        let fullGlyphRange = layoutManager.glyphRange(for: textContainer)
        var lineIndex = 0

        layoutManager.enumerateLineFragments(forGlyphRange: fullGlyphRange) {
            fragmentRect, _, _, _, _ in

            if lineIndex % 2 == 1 {
                var stripeRect = fragmentRect
                stripeRect.origin.x = 0
                stripeRect.size.width = self.bounds.width
                stripeRect.origin.y += self.textContainerInset.height

                // Only draw if the stripe intersects the dirty rect
                if stripeRect.intersects(rect) {
                    stripeColor.setFill()
                    NSBezierPath.fill(stripeRect)
                }
            }
            lineIndex += 1
        }
    }
}

// MARK: - EditorTextView

/// A SwiftUI wrapper around NSTextView for the editor pane.
/// Uses NSViewRepresentable to bridge AppKit's NSTextView into SwiftUI,
/// providing line-level control, scroll position access, and performance
/// that SwiftUI's TextEditor cannot match.
struct EditorTextView: NSViewRepresentable {
    @Binding var text: String
    @Binding var scrollOffset: CGFloat
    var font: NSFont
    var alignment: NSTextAlignment = .left

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

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

        let textView = StripedTextView()
        textView.isEditable = true
        textView.isSelectable = true
        textView.allowsUndo = true
        textView.isRichText = false
        textView.usesFontPanel = false
        textView.isAutomaticQuoteSubstitutionEnabled = false
        textView.isAutomaticDashSubstitutionEnabled = false
        textView.isAutomaticTextReplacementEnabled = false
        textView.isAutomaticSpellingCorrectionEnabled = false

        // Enable line wrapping — tracks text view width
        textView.isHorizontallyResizable = false
        textView.textContainer?.widthTracksTextView = true
        textView.textContainer?.containerSize = NSSize(
            width: 0,
            height: CGFloat.greatestFiniteMagnitude
        )
        textView.maxSize = NSSize(
            width: CGFloat.greatestFiniteMagnitude,
            height: CGFloat.greatestFiniteMagnitude
        )

        // Configure font and text color
        textView.font = font
        textView.textColor = .textColor
        textView.backgroundColor = .clear
        textView.drawsBackground = true
        textView.insertionPointColor = .textColor

        // Set alignment
        textView.alignment = alignment

        // Set the text
        textView.string = text

        // Configure the text container for consistent line spacing
        textView.textContainer?.lineFragmentPadding = 4
        textView.textContainerInset = NSSize(width: 8, height: 8)

        scrollView.documentView = textView
        context.coordinator.textView = textView
        context.coordinator.scrollView = scrollView

        // Observe text changes
        textView.delegate = context.coordinator

        // Observe scroll changes
        scrollView.contentView.postsBoundsChangedNotifications = true
        NotificationCenter.default.addObserver(
            context.coordinator,
            selector: #selector(Coordinator.scrollViewDidScroll(_:)),
            name: NSView.boundsDidChangeNotification,
            object: scrollView.contentView
        )

        return scrollView
    }

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

        // Update font if changed
        if textView.font != font {
            textView.font = font
            let range = NSRange(location: 0, length: textView.textStorage?.length ?? 0)
            textView.textStorage?.addAttribute(.font, value: font, range: range)
        }

        // Update alignment if changed
        if textView.alignment != alignment {
            textView.alignment = alignment
            let range = NSRange(location: 0, length: textView.textStorage?.length ?? 0)
            let paragraphStyle = NSMutableParagraphStyle()
            paragraphStyle.alignment = alignment
            textView.textStorage?.addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
        }

        // Update text only if it actually changed (avoid feedback loops)
        if textView.string != text {
            let selectedRanges = textView.selectedRanges
            textView.string = text
            textView.selectedRanges = selectedRanges
        }

        // Update scroll position if driven externally
        context.coordinator.isUpdatingScroll = true
        let currentOffset = scrollView.contentView.bounds.origin.y
        if abs(currentOffset - scrollOffset) > 0.5 {
            scrollView.contentView.scroll(to: NSPoint(x: 0, y: scrollOffset))
            scrollView.reflectScrolledClipView(scrollView.contentView)
        }
        context.coordinator.isUpdatingScroll = false
    }

    final class Coordinator: NSObject, NSTextViewDelegate {
        var parent: EditorTextView
        weak var textView: NSTextView?
        weak var scrollView: NSScrollView?
        var isUpdatingScroll = false

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

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

        @objc func scrollViewDidScroll(_ notification: Notification) {
            guard !isUpdatingScroll,
                  let scrollView = scrollView else { return }
            let offset = scrollView.contentView.bounds.origin.y
            DispatchQueue.main.async { [weak self] in
                self?.parent.scrollOffset = offset
            }
        }

        deinit {
            NotificationCenter.default.removeObserver(self)
        }
    }
}
