import AppKit
import CodexBarCore
import SwiftUI

/// Sidebar destinations of the settings window: fixed app panes plus one entry per provider.
enum SettingsPane: Hashable {
    case general
    case iCloudSync
    case usageSpend
    case notifications
    case menuBar
    case menu
    case advanced
    case hooks
    case plugins
    case about
    case debug
    case provider(ProviderInstanceID)

    static let windowWidth: CGFloat = 880
    static let windowHeight: CGFloat = 620
    static let windowMinWidth: CGFloat = 800
    static let windowMinHeight: CGFloat = 540
    static let sidebarWidth: CGFloat = 260
    static let sidebarMinWidth: CGFloat = 200
    static let sidebarMaxWidth: CGFloat = 380
    static let sidebarWidthDefaultsKey = "settingsSidebarWidth"
    static let detailMaxWidth: CGFloat = 780

    var title: String {
        switch self {
        case .general: L("tab_general")
        case .iCloudSync: L("iCloud Sync")
        case .usageSpend: L("tab_usage_spend")
        case .notifications: L("tab_notifications")
        case .menuBar: L("tab_menu_bar")
        case .menu: L("tab_menu")
        case .advanced: L("tab_advanced")
        case .hooks: L("tab_hooks")
        case .plugins: L("Plugins")
        case .about: L("tab_about")
        case .debug: L("tab_debug")
        case let .provider(instanceID):
            instanceID.firstPartyProvider
                .map { ProviderDescriptorRegistry.descriptor(for: $0).metadata.displayName }
                ?? instanceID.rawValue
        }
    }
}

@MainActor
struct PreferencesView: View {
    @Bindable var settings: SettingsStore
    @Bindable var store: UsageStore
    @Bindable var cloudSyncState: CloudSyncState
    let updater: UpdaterProviding
    @Bindable var selection: PreferencesSelection
    let managedCodexAccountCoordinator: ManagedCodexAccountCoordinator
    let codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator
    let runProviderLoginFlow: @MainActor (UsageProvider) async -> Void
    @Environment(\.colorScheme) private var colorScheme
    @AppStorage(SettingsPane.sidebarWidthDefaultsKey) private var sidebarWidth: Double = SettingsPane.sidebarWidth

    /// The persisted width, guarded against out-of-range values (edited defaults,
    /// bounds that shrank in an update) so a bad stored value can't wreck the layout.
    private var clampedSidebarWidth: CGFloat {
        min(max(self.sidebarWidth, SettingsPane.sidebarMinWidth), SettingsPane.sidebarMaxWidth)
    }

    init(
        settings: SettingsStore,
        store: UsageStore,
        cloudSyncState: CloudSyncState = CloudSyncState(),
        updater: UpdaterProviding,
        selection: PreferencesSelection,
        managedCodexAccountCoordinator: ManagedCodexAccountCoordinator = ManagedCodexAccountCoordinator(),
        codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator? = nil,
        runProviderLoginFlow: @escaping @MainActor (UsageProvider) async -> Void = { _ in })
    {
        self.settings = settings
        self.store = store
        self.cloudSyncState = cloudSyncState
        self.updater = updater
        self.selection = selection
        self.managedCodexAccountCoordinator = managedCodexAccountCoordinator
        self.codexAccountPromotionCoordinator = codexAccountPromotionCoordinator
            ?? CodexAccountPromotionCoordinator(
                settingsStore: settings,
                usageStore: store,
                managedAccountCoordinator: managedCodexAccountCoordinator)
        self.runProviderLoginFlow = runProviderLoginFlow
    }

    var body: some View {
        HStack(spacing: 0) {
            // Golden Gate-style sidebar: edge-to-edge material with a hairline separator,
            // no floating card chrome. The material ignores the safe area so it runs up
            // behind the transparent titlebar. The width is user-resizable via the
            // input-only drag strip overlaid on the detail pane's leading edge.
            SettingsSidebarView(settings: self.settings, store: self.store, selection: self.$selection.pane)
                .frame(width: self.clampedSidebarWidth)
                .background {
                    SettingsSidebarMaterial()
                        .ignoresSafeArea()
                }

            Divider()
                .ignoresSafeArea()

            self.detailView
                .frame(
                    maxWidth: SettingsPane.detailMaxWidth,
                    maxHeight: .infinity,
                    alignment: .topLeading)
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
                .overlay(alignment: .leading) {
                    self.sidebarResizeHandle
                }
        }
        .frame(
            minWidth: SettingsPane.windowMinWidth,
            idealWidth: SettingsPane.windowWidth,
            maxWidth: .infinity,
            minHeight: SettingsPane.windowMinHeight,
            idealHeight: SettingsPane.windowHeight,
            maxHeight: .infinity)
        .id(self.settings.appLanguage)
        .background {
            SettingsWindowAppearanceBridge(colorScheme: self.colorScheme, windowTitle: self.selection.pane.title)
                .allowsHitTesting(false)
        }
        .onAppear {
            self.ensureValidSelection()
        }
        .onChange(of: self.settings.debugMenuEnabled) { _, _ in
            self.ensureValidSelection()
        }
        .onChange(of: self.settings.shouldRequestAdaptiveActivityScanConsent) { _, shouldRequest in
            guard shouldRequest else { return }
            AdaptiveActivityConsentPresenter.presentIfNeeded(settings: self.settings)
        }
    }

    @ViewBuilder
    private var sidebarResizeHandle: some View {
        let handle = SidebarResizeHandle(
            width: self.$sidebarWidth,
            minWidth: SettingsPane.sidebarMinWidth,
            maxWidth: SettingsPane.sidebarMaxWidth)
            .frame(width: SidebarResizeHandleView.grabWidth)
            .ignoresSafeArea()
        if #available(macOS 15.0, *) {
            handle.pointerStyle(.columnResize)
        } else {
            handle
        }
    }

    @ViewBuilder
    private var detailView: some View {
        switch self.selection.pane {
        case .general:
            GeneralPane(settings: self.settings)
        case .iCloudSync:
            ICloudSyncPane(settings: self.settings, state: self.cloudSyncState)
        case .usageSpend:
            SpendDashboardPane(settings: self.settings, store: self.store)
        case .notifications:
            NotificationsPane(settings: self.settings)
        case .menuBar:
            MenuBarPane(settings: self.settings, store: self.store)
        case .menu:
            MenuPane(settings: self.settings, store: self.store)
        case .advanced:
            AdvancedPane(settings: self.settings, store: self.store)
        case .hooks:
            HooksPane(settings: self.settings)
        case .plugins:
            PluginsPane(settings: self.settings, store: self.store)
        case .about:
            AboutPane(updater: self.updater)
        case .debug:
            DebugPane(settings: self.settings, store: self.store)
        case let .provider(instanceID):
            if let provider = instanceID.firstPartyProvider {
                ProvidersPane(
                    provider: provider,
                    settings: self.settings,
                    store: self.store,
                    managedCodexAccountCoordinator: self.managedCodexAccountCoordinator,
                    codexAccountPromotionCoordinator: self.codexAccountPromotionCoordinator,
                    runProviderLoginFlow: self.runProviderLoginFlow)
                    .id(instanceID)
            }
        }
    }

    private func ensureValidSelection() {
        if !self.settings.debugMenuEnabled, self.selection.pane == .debug {
            self.selection.pane = .general
        }
    }
}

@MainActor
enum SettingsWindowSizing {
    static func enforceMinimumSize(_ window: NSWindow) {
        let toolbarHeight = max(0, window.frame.height - window.contentLayoutRect.height)
        let minimumSize = NSSize(
            width: SettingsPane.windowMinWidth,
            height: SettingsPane.windowMinHeight + toolbarHeight)
        window.minSize = minimumSize

        if window.frame.width < minimumSize.width || window.frame.height < minimumSize.height {
            var frame = window.frame
            let repairedSize = NSSize(
                width: max(frame.width, minimumSize.width),
                height: max(frame.height, minimumSize.height))
            frame.origin.y += frame.height - repairedSize.height
            frame.size = repairedSize
            window.setFrame(frame, display: true)
        }
    }
}

@MainActor
enum SettingsWindowStageBehavior {
    /// Keep Settings on the current Stage Manager stage / Space instead of
    /// yanking focus back to wherever the window last appeared.
    static let collectionBehavior: NSWindow.CollectionBehavior = [
        .moveToActiveSpace,
        .fullScreenAuxiliary,
    ]

    static func applyCollectionBehavior(_ window: NSWindow) {
        // Assign the exact OptionSet. `.canJoinAllSpaces` is mutually exclusive
        // with `.moveToActiveSpace`, and SwiftUI may restore a stale Space mask.
        if window.collectionBehavior != self.collectionBehavior {
            window.collectionBehavior = self.collectionBehavior
        }
    }

    static func present(_ window: NSWindow) {
        self.applyCollectionBehavior(window)
        if window.isMiniaturized {
            window.deminiaturize(nil)
        }
        window.makeKeyAndOrderFront(nil)
    }
}

@MainActor
enum SettingsWindowAppearance {
    typealias ResetAction = @MainActor @Sendable () -> Void
    typealias ResetScheduler = @MainActor @Sendable (@escaping ResetAction) -> Void

    static func refresh(
        _ window: NSWindow,
        application: NSApplication = NSApp,
        scheduleReset: ResetScheduler = Self.scheduleReset)
    {
        SettingsWindowSizing.enforceMinimumSize(window)
        window.appearanceSource = application
        // Pulse the exact effective appearance so the native toolbar redraws without
        // dropping inherited accessibility attributes, then restore KVO inheritance.
        window.appearance = application.effectiveAppearance
        scheduleReset { [weak window] in
            if let window {
                SettingsWindowSizing.enforceMinimumSize(window)
            }
            window?.appearance = nil
            window?.viewsNeedDisplay = true
        }
    }

    static func scheduleReset(_ action: @escaping ResetAction) {
        Task { @MainActor in
            await Task.yield()
            action()
        }
    }
}

@MainActor
struct SettingsWindowAppearanceBridge: NSViewRepresentable {
    let colorScheme: ColorScheme
    let windowTitle: String

    func makeNSView(context: Context) -> SettingsWindowAppearanceView {
        SettingsWindowAppearanceView()
    }

    func updateNSView(_ nsView: SettingsWindowAppearanceView, context: Context) {
        nsView.refreshWindowAppearance(for: self.colorScheme, windowTitle: self.windowTitle)
    }
}

@MainActor
final class SettingsWindowAppearanceView: NSView {
    private let scheduleReset: SettingsWindowAppearance.ResetScheduler
    private var colorScheme: ColorScheme?
    private var windowTitle: String?

    init(scheduleReset: @escaping SettingsWindowAppearance.ResetScheduler = SettingsWindowAppearance.scheduleReset) {
        self.scheduleReset = scheduleReset
        super.init(frame: .zero)
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    override func viewDidMoveToWindow() {
        super.viewDidMoveToWindow()
        NotificationCenter.default.removeObserver(self, name: NSWindow.didUpdateNotification, object: nil)
        if let window {
            NotificationCenter.default.addObserver(
                self,
                selector: #selector(self.windowDidUpdate(_:)),
                name: NSWindow.didUpdateNotification,
                object: window)
        }
        self.configureWindowStyle()
        self.refreshWindowAppearance()
    }

    @objc private func windowDidUpdate(_ notification: Notification) {
        self.configureWindowStyle()
    }

    func refreshWindowAppearance(for colorScheme: ColorScheme, windowTitle: String? = nil) {
        let colorSchemeChanged = self.colorScheme != colorScheme
        let windowTitleChanged = self.windowTitle != windowTitle
        guard colorSchemeChanged || windowTitleChanged else { return }
        self.colorScheme = colorScheme
        self.windowTitle = windowTitle

        guard let window else { return }
        self.configureWindowStyle()
        if windowTitleChanged, let windowTitle {
            window.title = windowTitle
        }
        if colorSchemeChanged {
            SettingsWindowAppearance.refresh(window, scheduleReset: self.scheduleReset)
        }
    }

    private func refreshWindowAppearance() {
        guard let window else { return }
        self.configureWindowStyle()
        if let windowTitle {
            window.title = windowTitle
        }
        SettingsWindowAppearance.refresh(window, scheduleReset: self.scheduleReset)
    }

    override func layout() {
        super.layout()
        self.configureWindowStyle()
    }

    private func configureWindowStyle() {
        guard let window else { return }
        SettingsWindowStageBehavior.applyCollectionBehavior(window)
        DockIconController.shared.registerSettingsWindow(window)
        if !window.styleMask.contains(.resizable) {
            window.styleMask.insert(.resizable)
        }
        if !window.styleMask.contains(.miniaturizable) {
            window.styleMask.insert(.miniaturizable)
        }
        if !window.titlebarAppearsTransparent {
            window.titlebarAppearsTransparent = true
        }
        if window.titleVisibility != .visible {
            window.titleVisibility = .visible
        }
        if window.titlebarSeparatorStyle != .none {
            window.titlebarSeparatorStyle = .none
        }
        if window.toolbar != nil {
            window.toolbar = nil
        }
        // Full-size content lets the sidebar material extend behind the titlebar so the
        // edge-to-edge sidebar reaches the top of the window; content stays below the
        // titlebar via the safe area.
        if !window.styleMask.contains(.fullSizeContentView) {
            window.styleMask.insert(.fullSizeContentView)
        }
    }
}

@MainActor
private struct SettingsSidebarMaterial: NSViewRepresentable {
    func makeNSView(context: Context) -> NSVisualEffectView {
        let view = NSVisualEffectView()
        self.configure(view)
        return view
    }

    func updateNSView(_ nsView: NSVisualEffectView, context: Context) {
        self.configure(nsView)
    }

    private func configure(_ view: NSVisualEffectView) {
        view.material = .sidebar
        view.blendingMode = .behindWindow
        view.state = .followsWindowActiveState
    }
}
