//
//  NativeTextEditor.swift
//  oAI
//
//  NSViewRepresentable text editor with correct Enter-key semantics:
//  plain Enter → send, Shift+Enter or Cmd+Enter → newline.
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen

import SwiftUI
import AppKit

struct NativeTextEditor: NSViewRepresentable {
    @Binding var text: String
    var font: NSFont
    var textColor: NSColor
    var isFocused: Bool

    /// Plain Enter (no modifiers). Return true if the event was consumed.
    var onReturn: () -> Bool
    /// Escape key. Return true if consumed.
    var onEscape: () -> Bool
    /// Up arrow. Return true if consumed.
    var onUpArrow: () -> Bool
    /// Down arrow. Return true if consumed.
    var onDownArrow: () -> Bool
    /// Called when the view gains or loses first-responder status.
    var onFocusChange: (Bool) -> Void

    // MARK: - NSViewRepresentable

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

        let tv = context.coordinator.textView
        tv.delegate = context.coordinator
        tv.isEditable = true
        tv.isRichText = false
        tv.drawsBackground = false
        tv.backgroundColor = .clear
        tv.isAutomaticQuoteSubstitutionEnabled = false
        tv.isAutomaticDashSubstitutionEnabled = false
        tv.isAutomaticSpellingCorrectionEnabled = true
        tv.isContinuousSpellCheckingEnabled = true
        tv.allowsUndo = true
        tv.isVerticallyResizable = true
        tv.isHorizontallyResizable = false
        tv.autoresizingMask = [.width]
        tv.textContainer?.widthTracksTextView = true
        tv.textContainerInset = NSSize(width: 8, height: 6)

        scrollView.documentView = tv
        return scrollView
    }

    func updateNSView(_ scrollView: NSScrollView, context: Context) {
        let tv = context.coordinator.textView
        let coord = context.coordinator

        // Update text only when it differs (avoids caret-jumping on every keystroke)
        if tv.string != text {
            let sel = tv.selectedRanges
            tv.string = text
            let len = (tv.string as NSString).length
            tv.selectedRanges = sel.map { v in
                let r = v.rangeValue
                let loc = min(r.location, len)
                let length = min(r.length, max(0, len - loc))
                return NSValue(range: NSRange(location: loc, length: length))
            }
        }

        if tv.font != font       { tv.font = font }
        if tv.textColor != textColor { tv.textColor = textColor }

        // Keep coordinator callbacks current with each SwiftUI render
        coord.textBinding  = $text
        coord.onReturn     = onReturn
        coord.onEscape     = onEscape
        coord.onUpArrow    = onUpArrow
        coord.onDownArrow  = onDownArrow
        coord.onFocusChange = onFocusChange

        if isFocused {
            DispatchQueue.main.async {
                guard let window = tv.window, window.firstResponder !== tv else { return }
                window.makeFirstResponder(tv)
            }
        }
    }

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

    // MARK: - Coordinator

    final class Coordinator: NSObject, NSTextViewDelegate {
        let textView = KeyableNSTextView()

        // Updated on every SwiftUI render via updateNSView
        var textBinding:   Binding<String>?
        var onReturn:      () -> Bool = { false }
        var onEscape:      () -> Bool = { false }
        var onUpArrow:     () -> Bool = { false }
        var onDownArrow:   () -> Bool = { false }
        var onFocusChange: (Bool) -> Void = { _ in }

        override init() {
            super.init()
            textView.coordinator = self
        }

        func textDidChange(_ notification: Notification) {
            guard let tv = notification.object as? NSTextView else { return }
            textBinding?.wrappedValue = tv.string
        }
    }
}

// MARK: - KeyableNSTextView

/// NSTextView that routes Return / Escape / arrow keys to the SwiftUI
/// coordinator before the AppKit default handling runs.
final class KeyableNSTextView: NSTextView {
    weak var coordinator: NativeTextEditor.Coordinator?

    override func keyDown(with event: NSEvent) {
        guard let coord = coordinator else { super.keyDown(with: event); return }

        let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
        let shift = flags.contains(.shift)
        let cmd   = flags.contains(.command)

        switch event.keyCode {
        case 36: // Return
            if shift || cmd {
                // Shift+Enter or Cmd+Enter → literal newline
                insertNewlineIgnoringFieldEditor(nil)
            } else {
                // Plain Enter → let SwiftUI decide (send or select dropdown item)
                if !coord.onReturn() {
                    insertNewlineIgnoringFieldEditor(nil)
                }
            }
        case 53: // Escape
            if !coord.onEscape() { super.keyDown(with: event) }
        case 126: // Up arrow
            if !coord.onUpArrow() { super.keyDown(with: event) }
        case 125: // Down arrow
            if !coord.onDownArrow() { super.keyDown(with: event) }
        default:
            super.keyDown(with: event)
        }
    }

    override func becomeFirstResponder() -> Bool {
        let ok = super.becomeFirstResponder()
        if ok { coordinator?.onFocusChange(true) }
        return ok
    }

    override func resignFirstResponder() -> Bool {
        let ok = super.resignFirstResponder()
        if ok { coordinator?.onFocusChange(false) }
        return ok
    }
}
