import AppKit
import CodexBarCore
import Testing
@testable import CodexBar

@MainActor
extension StatusMenuTests {
    final class HighlightProbeView: NSView, MenuCardHighlighting {
        private(set) var states: [Bool] = []

        func setHighlighted(_ highlighted: Bool) {
            self.states.append(highlighted)
        }
    }

    @Test
    func `menu highlight updates only previous and current custom rows`() {
        self.disableMenuCardsForTesting()
        let settings = self.makeSettings()
        let store = self.makeCodexStore(settings: settings, dashboardAuthorized: false)
        store._cancelPlanUtilizationHistoryLoadForTesting()
        let controller = StatusItemController(
            store: store,
            settings: settings,
            account: UsageFetcher().loadAccountInfo(),
            updater: DisabledUpdaterController(),
            preferencesSelection: PreferencesSelection(),
            statusBar: self.makeStatusBarForTesting())
        defer { controller.releaseStatusItemsForTesting() }

        let menu = NSMenu()
        let firstView = HighlightProbeView()
        let secondView = HighlightProbeView()
        let thirdView = HighlightProbeView()
        let first = NSMenuItem()
        first.view = firstView
        first.isEnabled = true
        let second = NSMenuItem()
        second.view = secondView
        second.isEnabled = true
        let third = NSMenuItem()
        third.view = thirdView
        third.isEnabled = true
        menu.addItem(first)
        menu.addItem(second)
        menu.addItem(third)

        controller.menu(menu, willHighlight: first)
        controller.menu(menu, willHighlight: second)
        controller.menu(menu, willHighlight: second)

        #expect(firstView.states == [true, false])
        #expect(secondView.states == [true])
        #expect(thirdView.states.isEmpty)
    }

    @Test
    func `native highlight preserves coalesced baseline resync until pointer leaves native rows`() async {
        self.disableMenuCardsForTesting()
        let settings = self.makeSettings()
        settings.statusChecksEnabled = false
        settings.refreshFrequency = .manual
        settings.mergeIcons = false
        let store = self.makeCodexStore(settings: settings, dashboardAuthorized: false)
        store._cancelPlanUtilizationHistoryLoadForTesting()
        let controller = StatusItemController(
            store: store,
            settings: settings,
            account: UsageFetcher().loadAccountInfo(),
            updater: DisabledUpdaterController(),
            preferencesSelection: PreferencesSelection(),
            statusBar: self.makeStatusBarForTesting())
        defer { controller.releaseStatusItemsForTesting() }

        let menu = controller.makeMenu()
        controller.menuWillOpen(menu)
        defer { controller.menuDidClose(menu) }
        let key = ObjectIdentifier(menu)
        controller.cancelMenuWork(key)
        controller.openMenus[key] = menu
        let planUsage = NSMenuItem(title: "Plan Usage", action: nil, keyEquivalent: "")
        planUsage.isEnabled = true
        let cost = NSMenuItem(title: "Cost", action: nil, keyEquivalent: "")
        cost.isEnabled = true
        menu.addItem(planUsage)
        menu.addItem(cost)

        controller.menu(menu, willHighlight: planUsage)
        #expect(controller.highlightedMenuItems[key] === planUsage)
        #expect(controller.isNativeMenuItemHighlighted(in: menu))
        controller.lastMenuAdjunctReadinessSignature = "stale-baseline"
        controller.menuSession.invalidate(allowsStaleContent: false, requiresRebuild: true)

        var rebuildCount = 0
        controller._test_openMenuRebuildObserver = { _ in rebuildCount += 1 }
        defer { controller._test_openMenuRebuildObserver = nil }
        controller.scheduleOpenMenuRebuildIfStillVisible(
            menu,
            provider: .codex,
            resyncReadinessBaselineAfterRebuild: true)
        controller.scheduleOpenMenuRebuildIfStillVisible(menu, provider: .codex)
        for _ in 0..<20 where controller.nativeHighlightDeferredMenuRebuilds[key] == nil {
            await Task.yield()
        }

        #expect(rebuildCount == 0)
        #expect(controller.nativeHighlightDeferredMenuRebuilds[key] != nil)
        #expect(controller.pendingMenuBaselineResyncs.contains(key))
        #expect(controller.menuNeedsRefresh(menu))

        controller.menu(menu, willHighlight: cost)
        for _ in 0..<20 {
            await Task.yield()
        }
        #expect(rebuildCount == 0)
        #expect(controller.nativeHighlightDeferredMenuRebuilds[key] != nil)

        controller.menu(menu, willHighlight: nil)
        for _ in 0..<20 where rebuildCount == 0 {
            await Task.yield()
        }

        #expect(rebuildCount == 1)
        #expect(controller.nativeHighlightDeferredMenuRebuilds[key] == nil)
        #expect(!controller.pendingMenuBaselineResyncs.contains(key))
        #expect(!controller.menuNeedsRefresh(menu))
        #expect(controller.lastMenuAdjunctReadinessSignature == controller.menuAdjunctReadinessSignature())
    }

    @Test
    func `native highlight preserves explicit rebuild even when menu is already fresh`() async {
        self.disableMenuCardsForTesting()
        let settings = self.makeSettings()
        settings.statusChecksEnabled = false
        settings.refreshFrequency = .manual
        settings.mergeIcons = false
        let store = self.makeCodexStore(settings: settings, dashboardAuthorized: false)
        store._cancelPlanUtilizationHistoryLoadForTesting()
        let controller = StatusItemController(
            store: store,
            settings: settings,
            account: UsageFetcher().loadAccountInfo(),
            updater: DisabledUpdaterController(),
            preferencesSelection: PreferencesSelection(),
            statusBar: self.makeStatusBarForTesting())
        defer { controller.releaseStatusItemsForTesting() }

        let menu = controller.makeMenu()
        controller.populateMenu(menu, provider: .codex)
        controller.markMenuFresh(menu)
        let key = ObjectIdentifier(menu)
        controller.openMenus[key] = menu
        defer { controller.menuDidClose(menu) }

        let nativeItem = NSMenuItem(title: "Plan Usage", action: nil, keyEquivalent: "")
        nativeItem.isEnabled = true
        menu.addItem(nativeItem)
        controller.menu(menu, willHighlight: nativeItem)
        #expect(!controller.menuNeedsRefresh(menu))

        var rebuildCount = 0
        controller._test_openMenuRebuildObserver = { _ in rebuildCount += 1 }
        defer { controller._test_openMenuRebuildObserver = nil }
        controller.scheduleOpenMenuRebuildIfStillVisible(menu, provider: .claude)
        for _ in 0..<20 where controller.nativeHighlightDeferredMenuRebuilds[key] == nil {
            await Task.yield()
        }

        #expect(rebuildCount == 0)
        #expect(controller.nativeHighlightDeferredMenuRebuilds[key]?.provider == .claude)
        #expect(!controller.menuNeedsRefresh(menu))

        controller.menu(menu, willHighlight: nil)
        for _ in 0..<20 where rebuildCount == 0 {
            await Task.yield()
        }

        #expect(rebuildCount == 1)
        #expect(controller.nativeHighlightDeferredMenuRebuilds[key] == nil)
        #expect(!controller.menuNeedsRefresh(menu))
    }

    @Test
    func `hosted submenu close resumes deferred explicit rebuild on fresh parent`() async {
        self.disableMenuCardsForTesting()
        let settings = self.makeSettings()
        settings.statusChecksEnabled = false
        settings.refreshFrequency = .manual
        settings.mergeIcons = false
        let store = self.makeCodexStore(settings: settings, dashboardAuthorized: false)
        store._cancelPlanUtilizationHistoryLoadForTesting()
        let controller = StatusItemController(
            store: store,
            settings: settings,
            account: UsageFetcher().loadAccountInfo(),
            updater: DisabledUpdaterController(),
            preferencesSelection: PreferencesSelection(),
            statusBar: self.makeStatusBarForTesting())
        defer { controller.releaseStatusItemsForTesting() }
        controller.menuRefreshEnabledOverrideForTesting = true

        let parent = controller.makeMenu()
        controller.populateMenu(parent, provider: .codex)
        controller.markMenuFresh(parent)
        let parentKey = ObjectIdentifier(parent)
        controller.openMenus[parentKey] = parent
        defer { controller.menuDidClose(parent) }

        let nativeItem = NSMenuItem(title: "Plan Usage", action: nil, keyEquivalent: "")
        nativeItem.isEnabled = true
        parent.addItem(nativeItem)
        controller.menu(parent, willHighlight: nativeItem)

        var rebuildCount = 0
        controller._test_openMenuRebuildObserver = { menu in
            if menu === parent {
                rebuildCount += 1
            }
        }
        defer { controller._test_openMenuRebuildObserver = nil }
        controller.scheduleOpenMenuRebuildIfStillVisible(parent, provider: .claude)
        for _ in 0..<20 where controller.nativeHighlightDeferredMenuRebuilds[parentKey] == nil {
            await Task.yield()
        }

        #expect(rebuildCount == 0)
        #expect(controller.nativeHighlightDeferredMenuRebuilds[parentKey]?.provider == .claude)
        #expect(!controller.menuNeedsRefresh(parent))

        let submenu = controller.makeHostedSubviewPlaceholderMenu(
            chartID: StatusItemController.costHistoryChartID,
            provider: .codex)
        let submenuKey = ObjectIdentifier(submenu)
        controller.openMenus[submenuKey] = submenu
        controller.menu(parent, willHighlight: nil)
        for _ in 0..<20 {
            await Task.yield()
        }

        #expect(rebuildCount == 0)
        #expect(controller.nativeHighlightDeferredMenuRebuilds[parentKey]?.provider == .claude)
        #expect(!controller.menuNeedsRefresh(parent))

        controller.menuDidClose(submenu)
        for _ in 0..<40 where rebuildCount == 0 {
            await Task.yield()
        }

        #expect(controller.openMenus[submenuKey] == nil)
        #expect(rebuildCount == 1)
        #expect(controller.nativeHighlightDeferredMenuRebuilds[parentKey] == nil)
        #expect(!controller.menuNeedsRefresh(parent))
    }

    @Test
    func `hosted submenu close keeps explicit rebuild ahead of dirty parent refresh`() async {
        self.disableMenuCardsForTesting()
        let settings = self.makeSettings()
        settings.statusChecksEnabled = false
        settings.refreshFrequency = .manual
        settings.mergeIcons = false
        let store = self.makeCodexStore(settings: settings, dashboardAuthorized: false)
        store._cancelPlanUtilizationHistoryLoadForTesting()
        let controller = StatusItemController(
            store: store,
            settings: settings,
            account: UsageFetcher().loadAccountInfo(),
            updater: DisabledUpdaterController(),
            preferencesSelection: PreferencesSelection(),
            statusBar: self.makeStatusBarForTesting())
        defer { controller.releaseStatusItemsForTesting() }
        controller.menuRefreshEnabledOverrideForTesting = true

        let parent = controller.makeMenu()
        controller.populateMenu(parent, provider: .codex)
        controller.markMenuFresh(parent)
        let parentKey = ObjectIdentifier(parent)
        controller.openMenus[parentKey] = parent
        defer { controller.menuDidClose(parent) }

        let nativeItem = NSMenuItem(title: "Plan Usage", action: nil, keyEquivalent: "")
        nativeItem.isEnabled = true
        parent.addItem(nativeItem)
        controller.menu(parent, willHighlight: nativeItem)

        var rebuildCount = 0
        controller._test_openMenuRebuildObserver = { menu in
            if menu === parent {
                rebuildCount += 1
            }
        }
        defer { controller._test_openMenuRebuildObserver = nil }
        controller.scheduleOpenMenuRebuildIfStillVisible(parent, provider: .claude)
        for _ in 0..<20 where controller.nativeHighlightDeferredMenuRebuilds[parentKey] == nil {
            await Task.yield()
        }

        let submenu = controller.makeHostedSubviewPlaceholderMenu(
            chartID: StatusItemController.costHistoryChartID,
            provider: .codex)
        controller.openMenus[ObjectIdentifier(submenu)] = submenu
        controller.menuSession.invalidate(allowsStaleContent: false, requiresRebuild: true)
        #expect(controller.menuNeedsRefresh(parent))

        controller.menuDidClose(submenu)
        for _ in 0..<40 {
            await Task.yield()
        }

        #expect(rebuildCount == 0)
        #expect(controller.nativeHighlightDeferredMenuRebuilds[parentKey]?.provider == .claude)
        #expect(controller.menuNeedsRefresh(parent))

        controller.menu(parent, willHighlight: nil)
        for _ in 0..<40 where rebuildCount == 0 {
            await Task.yield()
        }

        #expect(rebuildCount == 1)
        #expect(controller.nativeHighlightDeferredMenuRebuilds[parentKey] == nil)
        #expect(!controller.menuNeedsRefresh(parent))
    }

    @Test
    func `hosted submenu close preserves pending parent baseline resync`() async {
        self.disableMenuCardsForTesting()
        let settings = self.makeSettings()
        settings.statusChecksEnabled = false
        settings.refreshFrequency = .manual
        settings.mergeIcons = false
        let store = self.makeCodexStore(settings: settings, dashboardAuthorized: false)
        store._cancelPlanUtilizationHistoryLoadForTesting()
        let controller = StatusItemController(
            store: store,
            settings: settings,
            account: UsageFetcher().loadAccountInfo(),
            updater: DisabledUpdaterController(),
            preferencesSelection: PreferencesSelection(),
            statusBar: self.makeStatusBarForTesting())
        defer { controller.releaseStatusItemsForTesting() }
        controller.menuRefreshEnabledOverrideForTesting = true

        let parent = controller.makeMenu()
        controller.populateMenu(parent, provider: .codex)
        controller.markMenuFresh(parent)
        let parentKey = ObjectIdentifier(parent)
        controller.openMenus[parentKey] = parent
        defer { controller.menuDidClose(parent) }

        let submenu = controller.makeHostedSubviewPlaceholderMenu(
            chartID: StatusItemController.costHistoryChartID,
            provider: .codex)
        let submenuKey = ObjectIdentifier(submenu)
        controller.openMenus[submenuKey] = submenu

        controller.lastMenuAdjunctReadinessSignature = "stale-baseline"
        controller.menuSession.invalidate(allowsStaleContent: false, requiresRebuild: true)
        var rebuildCount = 0
        controller._test_openMenuRebuildObserver = { menu in
            if menu === parent {
                rebuildCount += 1
            }
        }
        defer { controller._test_openMenuRebuildObserver = nil }

        controller.scheduleOpenMenuRebuildIfStillVisible(
            parent,
            provider: .codex,
            resyncReadinessBaselineAfterRebuild: true)
        for _ in 0..<20 where controller.openMenuRebuildTasks[parentKey] != nil {
            await Task.yield()
        }

        #expect(rebuildCount == 0)
        #expect(controller.pendingMenuBaselineResyncs.contains(parentKey))
        #expect(controller.menuNeedsRefresh(parent))

        controller.menuDidClose(submenu)
        for _ in 0..<40 where rebuildCount == 0 {
            await Task.yield()
        }

        #expect(controller.openMenus[submenuKey] == nil)
        #expect(rebuildCount == 1)
        #expect(!controller.pendingMenuBaselineResyncs.contains(parentKey))
        #expect(!controller.menuNeedsRefresh(parent))
        #expect(controller.lastMenuAdjunctReadinessSignature == controller.menuAdjunctReadinessSignature())
    }

    @Test
    func `menu close clears native highlight deferral and pending baseline resync`() async {
        self.disableMenuCardsForTesting()
        let settings = self.makeSettings()
        settings.statusChecksEnabled = false
        settings.refreshFrequency = .manual
        settings.mergeIcons = false
        let store = self.makeCodexStore(settings: settings, dashboardAuthorized: false)
        store._cancelPlanUtilizationHistoryLoadForTesting()
        let controller = StatusItemController(
            store: store,
            settings: settings,
            account: UsageFetcher().loadAccountInfo(),
            updater: DisabledUpdaterController(),
            preferencesSelection: PreferencesSelection(),
            statusBar: self.makeStatusBarForTesting())
        defer { controller.releaseStatusItemsForTesting() }

        let menu = controller.makeMenu()
        let key = ObjectIdentifier(menu)
        controller.openMenus[key] = menu
        let nativeItem = NSMenuItem(title: "Settings", action: nil, keyEquivalent: "")
        nativeItem.isEnabled = true
        menu.addItem(nativeItem)
        controller.menu(menu, willHighlight: nativeItem)
        controller.menuSession.invalidate(allowsStaleContent: false, requiresRebuild: true)

        var rebuildCount = 0
        controller._test_openMenuRebuildObserver = { _ in rebuildCount += 1 }
        defer { controller._test_openMenuRebuildObserver = nil }
        controller.scheduleOpenMenuRebuildIfStillVisible(
            menu,
            provider: .codex,
            resyncReadinessBaselineAfterRebuild: true)
        for _ in 0..<20 where controller.nativeHighlightDeferredMenuRebuilds[key] == nil {
            await Task.yield()
        }

        #expect(controller.nativeHighlightDeferredMenuRebuilds[key] != nil)
        #expect(controller.pendingMenuBaselineResyncs.contains(key))
        controller.menuDidClose(menu)
        for _ in 0..<10 {
            await Task.yield()
        }

        #expect(rebuildCount == 0)
        #expect(controller.openMenus[key] == nil)
        #expect(controller.highlightedMenuItems[key] == nil)
        #expect(controller.nativeHighlightDeferredMenuRebuilds[key] == nil)
        #expect(!controller.pendingMenuBaselineResyncs.contains(key))
        #expect(controller.openMenuRebuildTasks[key] == nil)
        #expect(controller.openMenuRebuildRequests.tokens[key] == nil)
    }

    @Test
    func `hosted native highlight defers signature changing refresh until pointer leaves`() async throws {
        self.disableMenuCardsForTesting()
        let settings = self.makeSettings()
        settings.statusChecksEnabled = true
        settings.refreshFrequency = .manual
        settings.mergeIcons = false
        let store = self.makeCodexStore(settings: settings, dashboardAuthorized: false)
        store._cancelPlanUtilizationHistoryLoadForTesting()
        let controller = StatusItemController(
            store: store,
            settings: settings,
            account: UsageFetcher().loadAccountInfo(),
            updater: DisabledUpdaterController(),
            preferencesSelection: PreferencesSelection(),
            statusBar: self.makeStatusBarForTesting())
        defer { controller.releaseStatusItemsForTesting() }
        controller.menuRefreshEnabledOverrideForTesting = true

        let submenu = NSMenu()
        #expect(controller.appendStatusComponentsItem(
            to: submenu,
            provider: .codex,
            width: StatusItemController.menuCardBaseWidth))
        let key = ObjectIdentifier(submenu)
        controller.openMenus[key] = submenu
        defer { controller.menuDidClose(submenu) }
        let originalLink = try #require(submenu.items.last)
        #expect(originalLink.title == L("Open Status Page"))
        #expect(originalLink.view == nil)
        #expect(originalLink.isEnabled)
        controller.menu(submenu, willHighlight: originalLink)

        store.statusComponents[.codex] = [
            ProviderStatusComponent(
                id: "api",
                name: "API",
                indicator: .none,
                status: "operational"),
        ]
        var rebuildCount = 0
        controller._test_openMenuRebuildObserver = { menu in
            if menu === submenu {
                rebuildCount += 1
            }
        }
        defer { controller._test_openMenuRebuildObserver = nil }

        controller.refreshOpenMenusAllowingParentRebuild()
        for _ in 0..<20 where controller.nativeHighlightDeferredMenuRebuilds[key] == nil {
            await Task.yield()
        }

        #expect(rebuildCount == 0)
        #expect(controller.nativeHighlightDeferredMenuRebuilds[key] != nil)
        #expect(submenu.items.count == 1)
        #expect(submenu.items.first === originalLink)

        controller.menu(submenu, willHighlight: nil)
        for _ in 0..<20 where rebuildCount == 0 {
            await Task.yield()
        }

        #expect(rebuildCount == 1)
        #expect(controller.nativeHighlightDeferredMenuRebuilds[key] == nil)
        #expect(submenu.items.count == 3)
        #expect(submenu.items.last !== originalLink)
        #expect(submenu.items.last?.title == L("Open Status Page"))
    }

    @Test
    func `custom highlight does not defer open menu rebuild`() {
        self.disableMenuCardsForTesting()
        let settings = self.makeSettings()
        settings.statusChecksEnabled = false
        settings.refreshFrequency = .manual
        settings.mergeIcons = false
        let store = self.makeCodexStore(settings: settings, dashboardAuthorized: false)
        store._cancelPlanUtilizationHistoryLoadForTesting()
        let controller = StatusItemController(
            store: store,
            settings: settings,
            account: UsageFetcher().loadAccountInfo(),
            updater: DisabledUpdaterController(),
            preferencesSelection: PreferencesSelection(),
            statusBar: self.makeStatusBarForTesting())
        defer { controller.releaseStatusItemsForTesting() }

        let menu = controller.makeMenu()
        controller.menuWillOpen(menu)
        defer { controller.menuDidClose(menu) }
        let key = ObjectIdentifier(menu)
        controller.cancelMenuWork(key)
        controller.openMenus[key] = menu
        let customItem = NSMenuItem()
        customItem.view = HighlightProbeView()
        customItem.isEnabled = true
        menu.addItem(customItem)
        controller.menu(menu, willHighlight: customItem)
        controller.menuSession.invalidate(allowsStaleContent: false, requiresRebuild: true)

        var rebuildCount = 0
        controller._test_openMenuRebuildObserver = { _ in rebuildCount += 1 }
        defer { controller._test_openMenuRebuildObserver = nil }
        controller.rebuildOpenMenuIfStillVisible(menu, provider: .codex)

        #expect(rebuildCount == 1)
        #expect(controller.nativeHighlightDeferredMenuRebuilds[key] == nil)
        #expect(!controller.menuNeedsRefresh(menu))
    }
}
