import AppKit
import SwiftUI

struct TagNavigationItem: Identifiable, Equatable {
    var id: String { name }
    let name: String
    let colorIndex: Int
    let customColor: TagCustomColor?
}

struct TagNavigationView: NSViewRepresentable {
    enum Orientation: Equatable {
        case horizontal
        case vertical
    }

    let items: [TagNavigationItem]
    let orientation: Orientation
    let contentInsets: NSEdgeInsets
    let dragModeActive: Bool
    let draggingItemID: String?
    let appDragModeActive: Bool
    let appDropTargetID: String?
    let onActivate: (String) -> Void
    let onDoubleActivate: (String) -> Void
    let onHoverChange: (String, Bool) -> Void
    let canReorder: (String) -> Bool
    let canAcceptAppDrop: (String) -> Bool
    let onReorderBegan: (String) -> Void
    let onReorderMoved: (String, String) -> Void
    let onReorderEnded: () -> Void
    let onAppDropHoverChange: (String, Bool) -> Void
    let onAppDrop: (String, String, String, Bool) -> Void

    func makeNSView(context: Context) -> TagNavigationHostView {
        let view = TagNavigationHostView()
        view.update(
            items: items,
            orientation: orientation,
            contentInsets: contentInsets,
            dragModeActive: dragModeActive,
            draggingItemID: draggingItemID,
            appDragModeActive: appDragModeActive,
            appDropTargetID: appDropTargetID,
            onActivate: onActivate,
            onDoubleActivate: onDoubleActivate,
            onHoverChange: onHoverChange,
            canReorder: canReorder,
            canAcceptAppDrop: canAcceptAppDrop,
            onReorderBegan: onReorderBegan,
            onReorderMoved: onReorderMoved,
            onReorderEnded: onReorderEnded,
            onAppDropHoverChange: onAppDropHoverChange,
            onAppDrop: onAppDrop
        )
        return view
    }

    func updateNSView(_ nsView: TagNavigationHostView, context: Context) {
        nsView.update(
            items: items,
            orientation: orientation,
            contentInsets: contentInsets,
            dragModeActive: dragModeActive,
            draggingItemID: draggingItemID,
            appDragModeActive: appDragModeActive,
            appDropTargetID: appDropTargetID,
            onActivate: onActivate,
            onDoubleActivate: onDoubleActivate,
            onHoverChange: onHoverChange,
            canReorder: canReorder,
            canAcceptAppDrop: canAcceptAppDrop,
            onReorderBegan: onReorderBegan,
            onReorderMoved: onReorderMoved,
            onReorderEnded: onReorderEnded,
            onAppDropHoverChange: onAppDropHoverChange,
            onAppDrop: onAppDrop
        )
    }
}

final class TagNavigationHostView: NSView {
    private let scrollView = NSScrollView()
    private let documentView = TagNavigationDocumentView()

    override var isFlipped: Bool { true }

    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        setup()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }

    func update(
        items: [TagNavigationItem],
        orientation: TagNavigationView.Orientation,
        contentInsets: NSEdgeInsets,
        dragModeActive: Bool,
        draggingItemID: String?,
        appDragModeActive: Bool,
        appDropTargetID: String?,
        onActivate: @escaping (String) -> Void,
        onDoubleActivate: @escaping (String) -> Void,
        onHoverChange: @escaping (String, Bool) -> Void,
        canReorder: @escaping (String) -> Bool,
        canAcceptAppDrop: @escaping (String) -> Bool,
        onReorderBegan: @escaping (String) -> Void,
        onReorderMoved: @escaping (String, String) -> Void,
        onReorderEnded: @escaping () -> Void,
        onAppDropHoverChange: @escaping (String, Bool) -> Void,
        onAppDrop: @escaping (String, String, String, Bool) -> Void
    ) {
        documentView.update(
            items: items,
            orientation: orientation,
            contentInsets: contentInsets,
            dragModeActive: dragModeActive,
            draggingItemID: draggingItemID,
            appDragModeActive: appDragModeActive,
            appDropTargetID: appDropTargetID,
            onActivate: onActivate,
            onDoubleActivate: onDoubleActivate,
            onHoverChange: onHoverChange,
            canReorder: canReorder,
            canAcceptAppDrop: canAcceptAppDrop,
            onReorderBegan: onReorderBegan,
            onReorderMoved: onReorderMoved,
            onReorderEnded: onReorderEnded,
            onAppDropHoverChange: onAppDropHoverChange,
            onAppDrop: onAppDrop
        )
        scrollView.hasHorizontalScroller = false
        scrollView.hasVerticalScroller = false
        needsLayout = true
    }

    override func layout() {
        super.layout()
        scrollView.frame = bounds
        let visibleSize = scrollView.contentView.bounds.size
        documentView.frame = NSRect(
            origin: .zero,
            size: documentView.contentSize(fitting: visibleSize)
        )
        documentView.needsLayout = true
    }

    private func setup() {
        wantsLayer = true
        layer?.backgroundColor = NSColor.clear.cgColor

        scrollView.drawsBackground = false
        scrollView.borderType = .noBorder
        scrollView.autohidesScrollers = true
        scrollView.scrollerStyle = .overlay
        scrollView.horizontalScrollElasticity = .allowed
        scrollView.verticalScrollElasticity = .allowed
        scrollView.hasHorizontalScroller = false
        scrollView.hasVerticalScroller = false
        scrollView.documentView = documentView
        addSubview(scrollView)
    }
}

final class TagNavigationDocumentView: NSView {
    private var items: [TagNavigationItem] = []
    private var orientation: TagNavigationView.Orientation = .horizontal
    private var contentInsets = NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    private var buttons: [TagNavigationButton] = []
    private var dragModeActive = false
    private var draggingItemID: String?
    private var appDragModeActive = false
    private var appDropTargetID: String?
    private var onActivate: (String) -> Void = { _ in }
    private var onDoubleActivate: (String) -> Void = { _ in }
    private var onHoverChange: (String, Bool) -> Void = { _, _ in }
    private var canReorder: (String) -> Bool = { _ in false }
    private var canAcceptAppDrop: (String) -> Bool = { _ in false }
    private var onReorderBegan: (String) -> Void = { _ in }
    private var onReorderMoved: (String, String) -> Void = { _, _ in }
    private var onReorderEnded: () -> Void = {}
    private var onAppDropHoverChange: (String, Bool) -> Void = { _, _ in }
    private var onAppDrop: (String, String, String, Bool) -> Void = { _, _, _, _ in }

    override var isFlipped: Bool { true }

    func update(
        items: [TagNavigationItem],
        orientation: TagNavigationView.Orientation,
        contentInsets: NSEdgeInsets,
        dragModeActive: Bool,
        draggingItemID: String?,
        appDragModeActive: Bool,
        appDropTargetID: String?,
        onActivate: @escaping (String) -> Void,
        onDoubleActivate: @escaping (String) -> Void,
        onHoverChange: @escaping (String, Bool) -> Void,
        canReorder: @escaping (String) -> Bool,
        canAcceptAppDrop: @escaping (String) -> Bool,
        onReorderBegan: @escaping (String) -> Void,
        onReorderMoved: @escaping (String, String) -> Void,
        onReorderEnded: @escaping () -> Void,
        onAppDropHoverChange: @escaping (String, Bool) -> Void,
        onAppDrop: @escaping (String, String, String, Bool) -> Void
    ) {
        let oldNames = self.items.map(\.name)
        let newNames = items.map(\.name)
        let needsRebuild = Set(oldNames) != Set(newNames) || self.orientation != orientation
        self.items = items
        self.orientation = orientation
        self.contentInsets = contentInsets
        self.dragModeActive = dragModeActive
        self.draggingItemID = draggingItemID
        self.appDragModeActive = appDragModeActive
        self.appDropTargetID = appDropTargetID
        self.onActivate = onActivate
        self.onDoubleActivate = onDoubleActivate
        self.onHoverChange = onHoverChange
        self.canReorder = canReorder
        self.canAcceptAppDrop = canAcceptAppDrop
        self.onReorderBegan = onReorderBegan
        self.onReorderMoved = onReorderMoved
        self.onReorderEnded = onReorderEnded
        self.onAppDropHoverChange = onAppDropHoverChange
        self.onAppDrop = onAppDrop

        if needsRebuild {
            rebuildButtons()
        } else {
            reuseButtonsInCurrentOrder()
        }
        updateButtonRuntimeState()
        needsLayout = true
    }

    func contentSize(fitting visibleSize: NSSize) -> NSSize {
        switch orientation {
        case .horizontal:
            let width = buttons.reduce(contentInsets.left + contentInsets.right) { total, button in
                total + button.preferredSize.width
            } + CGFloat(max(0, buttons.count - 1)) * 8
            let height = max(visibleSize.height, contentInsets.top + 28 + contentInsets.bottom)
            return NSSize(width: max(width, visibleSize.width), height: height)
        case .vertical:
            let rowHeight: CGFloat = 28
            let height = contentInsets.top
                + CGFloat(buttons.count) * rowHeight
                + CGFloat(max(0, buttons.count - 1)) * 6
                + contentInsets.bottom
            return NSSize(width: max(visibleSize.width, 1), height: max(height, visibleSize.height))
        }
    }

    override func layout() {
        super.layout()
        switch orientation {
        case .horizontal:
            layoutHorizontal()
        case .vertical:
            layoutVertical()
        }
    }

    private func rebuildButtons() {
        buttons.forEach { $0.removeFromSuperview() }
        buttons = items.map { item in
            let button = TagNavigationButton(item: item, orientation: orientation)
            button.onActivate = { [weak self] tagID in self?.onActivate(tagID) }
            button.onDoubleActivate = { [weak self] tagID in self?.onDoubleActivate(tagID) }
            button.onHoverChange = { [weak self] tagID, active in self?.onHoverChange(tagID, active) }
            button.canReorder = { [weak self] tagID in self?.canReorder(tagID) ?? false }
            button.onReorderBegan = { [weak self] tagID in self?.onReorderBegan(tagID) }
            button.onReorderMoved = { [weak self] tagID, screenPoint in
                self?.handleReorderMove(tagID: tagID, screenPoint: screenPoint)
            }
            button.onReorderEnded = { [weak self] in self?.onReorderEnded() }
            button.canAcceptAppDrop = { [weak self] tagID in self?.canAcceptAppDrop(tagID) ?? false }
            button.onAppDropHoverChange = { [weak self] tagID, active in
                self?.setLocalAppDropHover(tagID, active: active)
                self?.onAppDropHoverChange(tagID, active)
            }
            button.onAppDrop = { [weak self] path, source, targetTag, copy in
                self?.onAppDrop(path, source, targetTag, copy)
            }
            addSubview(button)
            return button
        }
    }

    private func reuseButtonsInCurrentOrder() {
        let existing = Dictionary(uniqueKeysWithValues: buttons.map { ($0.itemName, $0) })
        buttons = items.compactMap { item in
            guard let button = existing[item.name] else { return nil }
            button.configure(item: item, orientation: orientation)
            button.refreshAppDropRegistration()
            return button
        }
    }

    private func updateButtonRuntimeState() {
        for button in buttons {
            let isAppDropTarget = appDropTargetID == button.itemName
            let appDropVisualActive = appDropTargetID != nil
            button.configureRuntime(
                dragModeActive: (dragModeActive && canReorder(button.itemName)) || appDropVisualActive,
                isDragging: draggingItemID == button.itemName || isAppDropTarget
            )
            button.refreshAppDropRegistration()
        }
    }

    private func setLocalAppDropHover(_ tagID: String, active: Bool) {
        if active {
            appDropTargetID = tagID
        } else if appDropTargetID == tagID {
            appDropTargetID = nil
        }
        updateButtonRuntimeState()
    }

    private func handleReorderMove(tagID: String, screenPoint: NSPoint) {
        guard let window else { return }
        let windowPoint = window.convertPoint(fromScreen: screenPoint)
        let point = convert(windowPoint, from: nil)
        guard let target = buttons.first(where: { button in
            button.itemName != tagID
                && canReorder(button.itemName)
                && button.frame.insetBy(dx: -4, dy: -4).contains(point)
        }) else { return }
        onReorderMoved(tagID, target.itemName)
    }

    private func layoutHorizontal() {
        var x = contentInsets.left
        let height: CGFloat = 28
        let y = max(contentInsets.top, (bounds.height - height) / 2)
        for button in buttons {
            let size = button.preferredSize
            button.frame = NSRect(x: x, y: y, width: size.width, height: height)
            x += size.width + 8
        }
    }

    private func layoutVertical() {
        let rowHeight: CGFloat = 28
        let width = max(1, bounds.width - contentInsets.left - contentInsets.right)
        var y = contentInsets.top
        for button in buttons {
            button.frame = NSRect(x: contentInsets.left, y: y, width: width, height: rowHeight)
            y += rowHeight + 6
        }
    }
}

final class TagNavigationButton: NSButton, AppDropTargetReceivingView {
    var onActivate: (String) -> Void = { _ in }
    var onDoubleActivate: (String) -> Void = { _ in }
    var onHoverChange: (String, Bool) -> Void = { _, _ in }
    var canReorder: (String) -> Bool = { _ in false }
    var onReorderBegan: (String) -> Void = { _ in }
    var onReorderMoved: (String, NSPoint) -> Void = { _, _ in }
    var onReorderEnded: () -> Void = {}
    var canAcceptAppDrop: (String) -> Bool = { _ in false }
    var onAppDropHoverChange: (String, Bool) -> Void = { _, _ in }
    var onAppDrop: (String, String, String, Bool) -> Void = { _, _, _, _ in }

    private var item: TagNavigationItem
    private var orientation: TagNavigationView.Orientation
    private var trackingAreaRef: NSTrackingArea?
    private var isMouseInside = false
    private var dragModeActive = false
    private var isDragging = false
    private let appDropTargetID = UUID()
    private var appDropRegistered = false

    var itemName: String { item.name }

    var preferredSize: NSSize {
        let font = NSFont.systemFont(ofSize: 13, weight: .medium)
        let textWidth = (item.name as NSString).size(withAttributes: [.font: font]).width
        switch orientation {
        case .horizontal:
            return NSSize(width: ceil(textWidth + 24), height: 28)
        case .vertical:
            return NSSize(width: ceil(textWidth + 20), height: 28)
        }
    }

    init(item: TagNavigationItem, orientation: TagNavigationView.Orientation) {
        self.item = item
        self.orientation = orientation
        super.init(frame: .zero)
        isBordered = false
        setButtonType(.momentaryChange)
        target = self
        action = #selector(performActivation)
        wantsLayer = true
        configure(item: item, orientation: orientation)
    }

    required init?(coder: NSCoder) {
        self.item = TagNavigationItem(name: "", colorIndex: 0, customColor: nil)
        self.orientation = .horizontal
        super.init(coder: coder)
    }

    func configure(item: TagNavigationItem, orientation: TagNavigationView.Orientation) {
        if self.item.name != item.name {
            unregisterAppDropTarget()
        }
        self.item = item
        self.orientation = orientation
        title = item.name
        alignment = orientation == .horizontal ? .center : .left
        setAccessibilityLabel(item.name)
        updateAppearance()
        refreshAppDropRegistration()
        needsLayout = true
    }

    func configureRuntime(dragModeActive: Bool, isDragging: Bool) {
        self.dragModeActive = dragModeActive
        self.isDragging = isDragging
        updateAppearance()
    }

    func refreshAppDropRegistration() {
        guard window != nil, canAcceptAppDrop(item.id) else {
            unregisterAppDropTarget()
            return
        }
        AppDragCoordinator.shared.register(id: appDropTargetID, view: self, tag: item.name)
        appDropRegistered = true
    }

    override func viewDidMoveToWindow() {
        super.viewDidMoveToWindow()
        refreshAppDropRegistration()
    }

    func appDragHoverChanged(active: Bool) {
        guard canAcceptAppDrop(item.id) || !active else { return }
        onAppDropHoverChange(item.name, active)
    }

    func performDrop(path: String, source: String, copy: Bool) {
        guard canAcceptAppDrop(item.id) else { return }
        onAppDrop(path, source, item.name, copy)
    }

    deinit {
        unregisterAppDropTarget()
    }

    override func updateTrackingAreas() {
        super.updateTrackingAreas()
        if let trackingAreaRef {
            removeTrackingArea(trackingAreaRef)
        }
        let area = NSTrackingArea(
            rect: bounds,
            options: [.mouseEnteredAndExited, .activeAlways, .inVisibleRect],
            owner: self,
            userInfo: nil
        )
        trackingAreaRef = area
        addTrackingArea(area)
    }

    override func mouseEntered(with event: NSEvent) {
        super.mouseEntered(with: event)
        guard !isMouseInside else { return }
        isMouseInside = true
        onHoverChange(item.id, true)
    }

    override func mouseExited(with event: NSEvent) {
        super.mouseExited(with: event)
        guard isMouseInside else { return }
        isMouseInside = false
        onHoverChange(item.id, false)
    }

    override func mouseDown(with event: NSEvent) {
        if event.clickCount >= 2 {
            onDoubleActivate(item.id)
            return
        }

        guard canReorder(item.id) else {
            onActivate(item.id)
            return
        }

        let start = Date()
        let longPressDuration: TimeInterval = 0.35
        let maxTrackingDuration: TimeInterval = 8
        var reorderActive = false
        var finished = false

        while !finished {
            guard let window,
                  window.isVisible,
                  NSApp.isActive,
                  Date().timeIntervalSince(start) < maxTrackingDuration
            else {
                if reorderActive {
                    onReorderEnded()
                }
                break
            }

            if !reorderActive && Date().timeIntervalSince(start) >= longPressDuration {
                reorderActive = true
                onReorderBegan(item.id)
            }

            if NSEvent.pressedMouseButtons & (1 << 0) == 0 {
                if reorderActive {
                    onReorderEnded()
                } else {
                    onActivate(item.id)
                }
                break
            }

            let nextEvent = window.nextEvent(
                matching: [.leftMouseDragged, .leftMouseUp],
                until: Date().addingTimeInterval(0.04),
                inMode: .eventTracking,
                dequeue: true
            )

            guard let nextEvent else { continue }
            switch nextEvent.type {
            case .leftMouseDragged:
                if reorderActive {
                    onReorderMoved(item.id, NSEvent.mouseLocation)
                }
            case .leftMouseUp:
                if reorderActive {
                    onReorderEnded()
                } else {
                    onActivate(item.id)
                }
                finished = true
            default:
                break
            }
        }
    }

    @objc private func performActivation() {
        onActivate(item.id)
    }

    private func unregisterAppDropTarget() {
        guard appDropRegistered else { return }
        AppDragCoordinator.shared.unregister(id: appDropTargetID)
        appDropRegistered = false
    }

    private func updateAppearance() {
        let background = TagColor.nsColor(for: item.colorIndex, customColor: item.customColor)
        let dragScale: CGFloat = isDragging ? 1.04 : 1.0
        layer?.backgroundColor = background.cgColor
        layer?.cornerRadius = orientation == .horizontal ? 7 : 6
        layer?.shadowColor = NSColor.black.withAlphaComponent(isDragging ? 0.50 : 0.20).cgColor
        layer?.shadowOpacity = 1
        layer?.shadowRadius = isDragging ? 18 : 3
        layer?.shadowOffset = NSSize(width: 0, height: isDragging ? -9 : -1)
        layer?.zPosition = isDragging ? 30 : 0
        layer?.setAffineTransform(CGAffineTransform(scaleX: dragScale, y: dragScale))
        alphaValue = dragModeActive ? (isDragging ? 1.0 : 0.62) : 1.0

        let textColor: NSColor = TagColor.prefersDarkText(for: background) ? .labelColor : .white
        let paragraph = NSMutableParagraphStyle()
        paragraph.alignment = alignment
        if orientation == .vertical {
            paragraph.firstLineHeadIndent = 10
            paragraph.headIndent = 10
        }
        let attributes: [NSAttributedString.Key: Any] = [
            .font: NSFont.systemFont(ofSize: 13, weight: .medium),
            .foregroundColor: textColor,
            .paragraphStyle: paragraph
        ]
        attributedTitle = NSAttributedString(string: item.name, attributes: attributes)
    }
}
