import AppKit
import SwiftUI

@main
struct TetherShotApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
    @StateObject private var model = AppModel()

    var body: some Scene {
        Window("TetherShot", id: "main") {
            MainWindow(model: model, appDelegate: appDelegate)
                .frame(width: 620, height: 680)
        }
        .windowResizability(.contentSize)
        .commands { CommandGroup(replacing: .newItem) {} }

        MenuBarExtra(
            "TetherShot",
            systemImage: "iphone",
            isInserted: Binding(
                get: { model.showInMenuBar },
                set: { model.setShowInMenuBar($0) }
            )
        ) {
            MenuContent(model: model, showMainWindow: appDelegate.showMainWindow)
                .tint(TetherShotTheme.accent)
        }
        .menuBarExtraStyle(.menu)
    }
}

/// Keeps capture, hotkeys, and update checks alive after the main window closes.
/// AppKit is used only for the close/reopen edge that SwiftUI scenes do not expose.
@MainActor
final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
    private weak var mainWindow: NSWindow?
    private weak var appModel: AppModel?

    func applicationDidFinishLaunching(_ notification: Notification) {
        if AppInstallation.relaunchCanonicalCopyIfNeeded() { return }
        AppInstallation.archiveDuplicateUserCopyIfNeeded()
    }

    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { false }

    func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
        if !flag { showMainWindow() }
        return true
    }

    func windowShouldClose(_ sender: NSWindow) -> Bool {
        sender.orderOut(nil)
        applyDockPreference()
        return false
    }

    func attach(to window: NSWindow?, model: AppModel) {
        guard let window else { return }
        let isNewWindow = mainWindow !== window
        mainWindow = window
        appModel = model
        model.dockVisibilityDidChange = { [weak self] _ in self?.applyDockPreference() }
        window.delegate = self
        if isNewWindow { presentMainWindow(refreshDevices: false) }
    }

    func showMainWindow() {
        presentMainWindow(refreshDevices: true)
    }

    private func presentMainWindow(refreshDevices: Bool) {
        applyDockPreference()
        NSApp.activate(ignoringOtherApps: true)
        mainWindow?.makeKeyAndOrderFront(nil)
        if refreshDevices { appModel?.refreshDevices() }
    }

    private func applyDockPreference() {
        NSApp.setActivationPolicy((appModel?.showInDock ?? true) ? .regular : .accessory)
    }
}

/// Resolves the SwiftUI scene's NSWindow once and hands lifecycle control to AppDelegate.
private struct WindowReader: NSViewRepresentable {
    let onResolve: (NSWindow?) -> Void

    func makeNSView(context: Context) -> WindowReaderView {
        let view = WindowReaderView()
        view.onResolve = onResolve
        return view
    }

    func updateNSView(_ nsView: WindowReaderView, context: Context) {
        nsView.onResolve = onResolve
    }
}

/// Reports only actual window attachment changes. Scheduling work from every
/// `updateNSView` creates a SwiftUI/AppKit invalidation loop on macOS 26.
private final class WindowReaderView: NSView {
    var onResolve: ((NSWindow?) -> Void)?
    private weak var resolvedWindow: NSWindow?

    override func viewDidMoveToWindow() {
        super.viewDidMoveToWindow()
        guard window !== resolvedWindow else { return }
        resolvedWindow = window
        onResolve?(window)
    }
}

extension View {
    func readWindow(_ action: @escaping (NSWindow?) -> Void) -> some View {
        background(WindowReader(onResolve: action).frame(width: 0, height: 0))
    }
}
