import SwiftUI
import Combine
import CairnquireSync

@main
struct CairnquireSyncApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        Settings {
            EmptyView()
        }
    }
}

@MainActor
class AppDelegate: NSObject, NSApplicationDelegate {
    var statusItem: NSStatusItem!
    var controller: MenuBarController!
    var popover: NSPopover!
    var contextMenu: NSMenu!
    var dropButton: DropStatusButton!
    private var clipboardHUDPanel: NSPanel?
    private var clipboardHUDDismissWorkItem: DispatchWorkItem?
    
    func applicationDidFinishLaunching(_ notification: Notification) {
        controller = MenuBarController()
        
        setupStatusItem()
        setupPopover()
        setupContextMenu()
        
        // Observe config changes to update icon
        controller.$config
            .sink { [weak self] config in
                self?.updateIcon(config: config)
            }
            .store(in: &controller.cancellables)
        
        // Observe upload progress to animate icon
        controller.$uploadProgress
            .sink { [weak self] progress in
                self?.updateIconForUpload(progress: progress)
            }
            .store(in: &controller.cancellables)

        controller.$clipboardNotice
            .compactMap { $0 }
            .sink { [weak self] notice in
                self?.showClipboardHUD(notice)
            }
            .store(in: &controller.cancellables)
        
        // Hide dock icon
        NSApp.setActivationPolicy(.accessory)
    }
    
    private func setupStatusItem() {
        statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
        
        // Create custom drop-aware button
        let button = DropStatusButton()
        button.imagePosition = .imageOnly
        button.target = self
        button.action = #selector(statusItemButtonClicked(_:))
        button.sendAction(on: [.leftMouseUp, .rightMouseUp])
        button.appDelegate = self
        
        // Replace the default button with our custom one
        if let oldButton = statusItem.button {
            oldButton.superview?.addSubview(button)
            button.frame = oldButton.frame
            button.autoresizingMask = [.width, .height]
            oldButton.isHidden = true
        }
        
        dropButton = button
        updateIcon(config: controller.config)
    }
    
    func updateIcon(config: SyncConfig) {
        guard controller.uploadProgress == nil else { return }
        let iconName = config.iconName
        dropButton.image = makeMenuBarIcon(named: iconName, accessibilityDescription: "Cairnquire")
        dropButton.setDropTargetActive(false)
    }
    
    private func updateIconForUpload(progress: Double?) {
        if progress != nil {
            // During upload: show tray.circle (outline)
            dropButton.image = makeMenuBarIcon(named: "tray.circle", accessibilityDescription: "Uploading")
            dropButton.setDropTargetActive(false)
        } else {
            // Upload complete: restore normal icon
            updateIcon(config: controller.config)
        }
    }

    private func makeMenuBarIcon(named iconName: String, accessibilityDescription: String) -> NSImage? {
        let image = NSImage(systemSymbolName: iconName, accessibilityDescription: accessibilityDescription)
            ?? NSImage(systemSymbolName: "tray.circle.fill", accessibilityDescription: accessibilityDescription)
        image?.isTemplate = true
        return image
    }
    
    func bounceIcon() {
        guard let button = dropButton else { return }
        
        let originalOrigin = button.frame.origin
        
        NSAnimationContext.runAnimationGroup { context in
            context.duration = 0.12
            context.timingFunction = CAMediaTimingFunction(name: .easeOut)
            button.animator().setFrameOrigin(NSPoint(
                x: originalOrigin.x,
                y: originalOrigin.y - 6
            ))
        } completionHandler: {
            NSAnimationContext.runAnimationGroup { context in
                context.duration = 0.15
                context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
                button.animator().setFrameOrigin(originalOrigin)
            }
        }
    }

    private func showClipboardHUD(_ notice: ClipboardNotice) {
        clipboardHUDDismissWorkItem?.cancel()

        let panel = clipboardHUDPanel ?? makeClipboardHUDPanel()
        panel.contentView = NSHostingView(rootView: ClipboardHUDView(filename: notice.filename))
        positionClipboardHUD(panel)
        panel.alphaValue = 0
        panel.orderFrontRegardless()

        NSAnimationContext.runAnimationGroup { context in
            context.duration = 0.15
            panel.animator().alphaValue = 1
        }

        let dismissWorkItem = DispatchWorkItem { [weak self, weak panel] in
            guard let self, let panel else { return }
            NSAnimationContext.runAnimationGroup { context in
                context.duration = 0.2
                panel.animator().alphaValue = 0
            } completionHandler: {
                panel.orderOut(nil)
            }
            self.clipboardHUDDismissWorkItem = nil
        }
        clipboardHUDDismissWorkItem = dismissWorkItem
        DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: dismissWorkItem)
    }

    private func makeClipboardHUDPanel() -> NSPanel {
        let panel = NSPanel(
            contentRect: NSRect(x: 0, y: 0, width: 280, height: 72),
            styleMask: [.borderless, .nonactivatingPanel],
            backing: .buffered,
            defer: false
        )
        panel.level = .floating
        panel.isOpaque = false
        panel.backgroundColor = .clear
        panel.hasShadow = true
        panel.hidesOnDeactivate = false
        panel.collectionBehavior = [.canJoinAllSpaces, .transient, .ignoresCycle]
        clipboardHUDPanel = panel
        return panel
    }

    private func positionClipboardHUD(_ panel: NSPanel) {
        guard let buttonWindow = dropButton.window else { return }
        let buttonFrame = buttonWindow.convertToScreen(dropButton.convert(dropButton.bounds, to: nil))
        let panelSize = panel.frame.size
        let visibleFrame = buttonWindow.screen?.visibleFrame ?? NSScreen.main?.visibleFrame ?? .zero
        let centeredX = buttonFrame.midX - panelSize.width / 2
        let origin = NSPoint(
            x: min(max(centeredX, visibleFrame.minX + 8), visibleFrame.maxX - panelSize.width - 8),
            y: buttonFrame.minY - panelSize.height - 8
        )
        panel.setFrameOrigin(origin)
    }
    
    private func setupPopover() {
        popover = NSPopover()
        popover.behavior = .transient
        popover.contentViewController = NSHostingController(
            rootView: StatusPopoverView(controller: controller)
        )
    }
    
    private func setupContextMenu() {
        contextMenu = NSMenu()
        
        contextMenu.addItem(NSMenuItem(title: "Sync Now", action: #selector(syncNow), keyEquivalent: "s"))
        contextMenu.addItem(NSMenuItem(title: "Upload File...", action: #selector(uploadFile), keyEquivalent: "u"))
        contextMenu.addItem(NSMenuItem(title: "New Note", action: #selector(createNote), keyEquivalent: "n"))
        contextMenu.addItem(NSMenuItem(title: "Drop Zone", action: #selector(showDropZone), keyEquivalent: "d"))
        contextMenu.addItem(NSMenuItem(title: "View Uploads", action: #selector(showUploads), keyEquivalent: "v"))
        
        contextMenu.addItem(NSMenuItem.separator())
        
        contextMenu.addItem(NSMenuItem(title: "Settings...", action: #selector(showSettings), keyEquivalent: ","))
        contextMenu.addItem(NSMenuItem(title: "Quit", action: #selector(quit), keyEquivalent: "q"))
    }
    
    @objc private func statusItemButtonClicked(_ sender: AnyObject?) {
        guard let event = NSApp.currentEvent else { return }
        
        switch event.type {
        case .rightMouseUp:
            statusItem.menu = contextMenu
            statusItem.button?.performClick(nil)
            statusItem.menu = nil
        default:
            togglePopover()
        }
    }
    
    private func togglePopover() {
        if popover.isShown {
            closePopover()
        } else {
            showPopover()
        }
    }
    
    private func showPopover() {
        guard let button = dropButton else { return }
        Task { @MainActor in
            controller.clearSearch()
            await controller.refreshRecentUploads()
        }
        NSApp.activate(ignoringOtherApps: true)
        popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
        
        NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown, .rightMouseDown]) { [weak self] event in
            guard let self = self, self.popover.isShown else { return }
            self.closePopover()
        }
    }
    
    private func closePopover() {
        popover.performClose(nil)
    }
    
    // MARK: - Actions
    
    @objc private func syncNow() {
        Task { @MainActor in
            await controller.performSync()
        }
    }
    
    @objc private func uploadFile() {
        Task { @MainActor in
            controller.quickUpload()
        }
    }
    
    @objc private func createNote() {
        Task { @MainActor in
            controller.createNote()
        }
    }
    
    @objc private func showDropZone() {
        Task { @MainActor in
            controller.showDropZone()
        }
    }
    
    @objc private func showUploads() {
        Task { @MainActor in
            controller.showUploads()
        }
    }
    
    @objc private func showSettings() {
        Task { @MainActor in
            controller.showSettings()
        }
    }
    
    @objc private func quit() {
        NSApp.terminate(nil)
    }
}

private struct ClipboardHUDView: View {
    let filename: String

    var body: some View {
        HStack(spacing: 12) {
            Image(systemName: "checkmark.circle.fill")
                .font(.system(size: 26))
                .foregroundColor(.green)

            VStack(alignment: .leading, spacing: 3) {
                Text("URL Copied to Clipboard")
                    .font(.system(size: 13, weight: .semibold))
                Text(filename)
                    .font(.caption)
                    .foregroundColor(.secondary)
                    .lineLimit(1)
                    .truncationMode(.middle)
            }

            Spacer()
        }
        .padding(.horizontal, 16)
        .frame(width: 280, height: 72)
        .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12))
    }
}

// MARK: - Drop Status Button

class DropStatusButton: NSButton {
    weak var appDelegate: AppDelegate?
    
    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        setup()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }
    
    private func setup() {
        isBordered = false
        bezelStyle = .regularSquare
        imageScaling = .scaleProportionallyDown
        registerForDraggedTypes([.fileURL])
    }

    func setDropTargetActive(_ isActive: Bool) {
        isBordered = isActive
        contentTintColor = isActive ? .systemBlue : nil
        needsDisplay = true
    }
    
    override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
        let pasteboard = sender.draggingPasteboard
        guard pasteboard.canReadObject(forClasses: [NSURL.self], options: nil) else {
            setDropTargetActive(false)
            return []
        }

        // Show the button frame only while the menu bar icon is a valid drop target.
        setDropTargetActive(true)
        return .copy
    }
    
    override func draggingExited(_ sender: NSDraggingInfo?) {
        setDropTargetActive(false)
        if let appDelegate = appDelegate {
            appDelegate.updateIcon(config: appDelegate.controller.config)
        }
    }
    
    override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
        let pasteboard = sender.draggingPasteboard
        guard let urls = pasteboard.readObjects(forClasses: [NSURL.self], options: nil) as? [URL],
              !urls.isEmpty else {
            setDropTargetActive(false)
            return false
        }
        
        setDropTargetActive(false)
        appDelegate?.bounceIcon()
        
        Task { @MainActor in
            appDelegate?.controller.uploadFiles(urls)
        }
        
        return true
    }
}
