import AppKit
import CodexBarCore
import QuartzCore
import SwiftUI

enum HostedSubviewContentFingerprint: Equatable {
    case text(String)
    case costHistory(CostHistoryChartMenuView.RenderFingerprint)
}

struct HostedSubviewRenderSignature: Equatable {
    let chartID: String
    let providerRawValue: String?
    let widthBitPattern: UInt64
    let content: HostedSubviewContentFingerprint
}

final class HostedSubviewRenderSignatureBox: NSObject {
    let signature: HostedSubviewRenderSignature

    init(_ signature: HostedSubviewRenderSignature) {
        self.signature = signature
    }
}

extension StatusItemController {
    private struct HostedSubviewIdentity {
        let chartID: String
        let provider: UsageProvider?
        let providerRawValue: String?
    }

    func refreshHostedSubviewHeights(in menu: NSMenu) {
        let width = self.renderedMenuWidth(for: menu)

        for item in menu.items {
            guard let view = item.view else { continue }
            let height = self.hostedSubviewFittingHeight(for: view, width: width)
            view.frame = NSRect(origin: .zero, size: NSSize(width: width, height: height))
        }
    }

    /// Measures the natural height of a hosted submenu view at the given width using the live
    /// view that will actually be displayed. Hosted chart items used to spin up a second,
    /// throwaway `NSHostingController` purely to size the chart even though every build path
    /// immediately re-measures the live view via `fittingSize`; that extra SwiftUI hierarchy was
    /// pure overhead on a popup-menu hot path, so callers now size the displayed view directly.
    func hostedSubviewFittingHeight(for view: NSView, width: CGFloat) -> CGFloat {
        view.frame = NSRect(origin: .zero, size: NSSize(width: width, height: 1))
        view.layoutSubtreeIfNeeded()
        return view.fittingSize.height
    }

    func isHostedSubviewMenu(_ menu: NSMenu) -> Bool {
        let ids: Set = [
            Self.usageBreakdownChartID,
            Self.creditsHistoryChartID,
            Self.costHistoryChartID,
            Self.usageHistoryChartID,
            Self.storageBreakdownID,
            Self.statusComponentsID,
        ]
        return menu.items.contains { item in
            guard let id = item.representedObject as? String else { return false }
            return ids.contains(id)
        }
    }

    func makeHostedSubviewPlaceholderMenu(
        chartID: String,
        provider: UsageProvider? = nil,
        width: CGFloat? = nil) -> NSMenu
    {
        let submenu = NSMenu()
        submenu.autoenablesItems = false
        if let width {
            submenu.minimumWidth = width
        }
        submenu.delegate = self
        let chartItem = NSMenuItem()
        chartItem.isEnabled = true
        chartItem.representedObject = chartID
        chartItem.toolTip = provider?.rawValue
        submenu.addItem(chartItem)
        return submenu
    }

    @discardableResult
    func hydrateHostedSubviewMenuIfNeeded(_ menu: NSMenu, width requestedWidth: CGFloat? = nil) -> Bool {
        guard let placeholder = menu.items.first,
              menu.items.count == 1,
              placeholder.view == nil,
              let chartID = placeholder.representedObject as? String
        else {
            return false
        }

        let width = requestedWidth ?? self.renderedMenuWidth(for: menu.supermenu ?? menu)
        let identity = HostedSubviewIdentity(
            chartID: chartID,
            provider: placeholder.toolTip.flatMap(UsageProvider.init(rawValue:)),
            providerRawValue: placeholder.toolTip)
        menu.removeAllItems()

        let t0 = CACurrentMediaTime()
        MainThreadActivityBreadcrumb.push("hydrateChart:\(chartID)")
        defer { MainThreadActivityBreadcrumb.pop() }
        let didHydrate: Bool = switch chartID {
        case Self.usageBreakdownChartID:
            self.appendUsageBreakdownChartItem(to: menu, width: width)
        case Self.creditsHistoryChartID:
            self.appendCreditsHistoryChartItem(to: menu, width: width)
        case Self.costHistoryChartID:
            if let providerRawValue = placeholder.toolTip,
               let provider = UsageProvider(rawValue: providerRawValue)
            {
                self.appendCostHistoryChartItem(to: menu, provider: provider, width: width)
            } else {
                false
            }
        case Self.usageHistoryChartID:
            if let providerRawValue = placeholder.toolTip,
               let provider = UsageProvider(rawValue: providerRawValue)
            {
                self.appendUsageHistoryChartItem(to: menu, provider: provider, width: width)
            } else {
                false
            }
        case Self.storageBreakdownID:
            if let providerRawValue = placeholder.toolTip,
               let provider = UsageProvider(rawValue: providerRawValue)
            {
                self.appendStorageBreakdownItem(to: menu, provider: provider, width: width)
            } else {
                false
            }
        case Self.statusComponentsID:
            if let providerRawValue = self.hostedSubviewProviderRawValue(for: placeholder),
               let provider = UsageProvider(rawValue: providerRawValue)
            {
                self.appendStatusComponentsItem(to: menu, provider: provider, width: width)
            } else {
                false
            }
        default:
            false
        }
        self.logChartRenderDurationIfSlow("hydrateHostedSubview:\(chartID)", startedAt: t0)

        if !didHydrate {
            self.appendHostedSubviewUnavailableItem(
                to: menu,
                chartID: chartID,
                providerRawValue: placeholder.toolTip)
        }
        self.recordHostedSubviewRenderSignature(for: menu, identity: identity, width: width)
        return true
    }

    func refreshHostedSubviewMenu(_ menu: NSMenu) {
        let width = self.renderedMenuWidth(for: menu)
        guard let identity = self.hostedSubviewIdentity(for: menu) else {
            self.refreshHostedSubviewHeights(in: menu)
            return
        }
        let signature = self.hostedSubviewRenderSignature(identity: identity, width: width)
        if self.hostedSubviewRenderSignatures.object(forKey: menu)?.signature == signature {
            return
        }

        menu.removeAllItems()
        let t0 = CACurrentMediaTime()
        MainThreadActivityBreadcrumb.push("refreshChart:\(identity.chartID)")
        defer { MainThreadActivityBreadcrumb.pop() }
        let didHydrate: Bool = switch identity.chartID {
        case Self.usageBreakdownChartID:
            self.appendUsageBreakdownChartItem(to: menu, width: width)
        case Self.creditsHistoryChartID:
            self.appendCreditsHistoryChartItem(to: menu, width: width)
        case Self.costHistoryChartID:
            if let provider = identity.provider {
                self.appendCostHistoryChartItem(to: menu, provider: provider, width: width)
            } else {
                false
            }
        case Self.usageHistoryChartID:
            if let provider = identity.provider {
                self.appendUsageHistoryChartItem(to: menu, provider: provider, width: width)
            } else {
                false
            }
        case Self.storageBreakdownID:
            if let provider = identity.provider {
                self.appendStorageBreakdownItem(to: menu, provider: provider, width: width)
            } else {
                false
            }
        case Self.statusComponentsID:
            if let provider = identity.provider {
                self.appendStatusComponentsItem(to: menu, provider: provider, width: width)
            } else {
                false
            }
        default:
            false
        }
        self.logChartRenderDurationIfSlow("refreshHostedSubview:\(identity.chartID)", startedAt: t0)

        if !didHydrate {
            self.appendHostedSubviewUnavailableItem(
                to: menu,
                chartID: identity.chartID,
                providerRawValue: identity.provider?.rawValue ?? identity.providerRawValue)
        }
        self.hostedSubviewRenderSignatures.setObject(
            HostedSubviewRenderSignatureBox(signature),
            forKey: menu)
    }

    private func hostedSubviewIdentity(for menu: NSMenu)
    -> HostedSubviewIdentity? {
        for item in menu.items {
            guard let chartID = item.representedObject as? String else { continue }
            let providerRawValue = self.hostedSubviewProviderRawValue(for: item)
            return HostedSubviewIdentity(
                chartID: chartID,
                provider: providerRawValue.flatMap(UsageProvider.init(rawValue:)),
                providerRawValue: providerRawValue)
        }
        return nil
    }

    private func hostedSubviewProviderRawValue(for item: NSMenuItem) -> String? {
        if let providerRawValue = item.toolTip {
            return providerRawValue
        }
        guard item.representedObject as? String == Self.statusComponentsID else { return nil }
        return item.identifier?.rawValue
    }

    private func recordHostedSubviewRenderSignature(
        for menu: NSMenu,
        identity: HostedSubviewIdentity,
        width: CGFloat)
    {
        let signature = self.hostedSubviewRenderSignature(identity: identity, width: width)
        self.hostedSubviewRenderSignatures.setObject(
            HostedSubviewRenderSignatureBox(signature),
            forKey: menu)
    }

    private func hostedSubviewRenderSignature(
        identity: HostedSubviewIdentity,
        width: CGFloat) -> HostedSubviewRenderSignature
    {
        let contentSignature: HostedSubviewContentFingerprint = switch identity.chartID {
        case Self.usageBreakdownChartID:
            .text(Self.dashboardBreakdownReadinessSignature(
                OpenAIDashboardDailyBreakdown.removingSkillUsageServices(
                    from: self.store.openAIDashboard?.usageBreakdown ?? [])))
        case Self.creditsHistoryChartID:
            .text(Self.dashboardBreakdownReadinessSignature(self.store.openAIDashboard?.dailyBreakdown ?? []))
        case Self.costHistoryChartID:
            if let provider = identity.provider {
                self.costHistoryRenderFingerprint(for: provider)
            } else {
                .text("missing-provider")
            }
        case Self.usageHistoryChartID:
            .text(identity.provider.map(self.usageHistoryRenderSignature(for:)) ?? "missing-provider")
        case Self.storageBreakdownID:
            .text(identity.provider.map(self.storageBreakdownRenderSignature(for:)) ?? "missing-provider")
        case Self.statusComponentsID:
            .text(identity.provider.map(self.statusComponentsRenderSignature(for:)) ?? "missing-provider")
        default:
            .text("unknown")
        }
        return HostedSubviewRenderSignature(
            chartID: identity.chartID,
            providerRawValue: identity.providerRawValue,
            widthBitPattern: Double(width).bitPattern,
            content: contentSignature)
    }

    private func costHistoryRenderFingerprint(for provider: UsageProvider) -> HostedSubviewContentFingerprint {
        guard let snapshot = self.tokenSnapshotForCostHistorySubmenu(provider: provider) else {
            return .text("none")
        }
        let displayConversion = self.costHistoryDisplayConversion(for: snapshot)
        return .costHistory(CostHistoryChartMenuView.renderFingerprint(
            from: snapshot,
            provider: provider,
            displayCurrencyCode: displayConversion.currencyCode,
            displayCostMultiplier: displayConversion.multiplier))
    }

    /// Resolves the user's preferred display currency for cost-history values, falling back to
    /// the snapshot's native currency when no exchange rate is available.
    private func costHistoryDisplayConversion(
        for snapshot: CostUsageTokenSnapshot) -> (currencyCode: String, multiplier: Double)
    {
        let converted = UsageFormatter.convertedCost(
            1,
            preferredCurrency: self.settings.preferredCurrencyCode,
            providerCurrency: snapshot.currencyCode)
        return (converted.currencyCode, converted.value)
    }

    private func usageHistoryRenderSignature(for provider: UsageProvider) -> String {
        let snapshot = self.store.snapshot(for: provider.instanceID)
        let selection = self.store.planUtilizationHistorySelection(for: provider)
        return [
            "\(self.store.planUtilizationHistoryRevision)",
            "\(Int(Date().timeIntervalSince1970 / 60))",
            selection.accountKey ?? "unscoped",
            snapshot?.primary == nil ? "0" : "1",
            snapshot?.secondary == nil ? "0" : "1",
            snapshot?.tertiary == nil ? "0" : "1",
        ].joined(separator: "|")
    }

    func statusComponentsRenderSignature(for provider: UsageProvider) -> String {
        let components = self.store.statusComponents(for: provider)
        guard !components.isEmpty else { return "none" }
        func signature(_ component: ProviderStatusComponent) -> String {
            let childSig = component.children.map(signature).joined(separator: ",")
            return "\(component.id)=\(component.indicator.rawValue)[\(childSig)]"
        }
        return components.map(signature).joined(separator: ";")
    }

    private func storageBreakdownRenderSignature(for provider: UsageProvider) -> String {
        guard let footprint = self.store.storageFootprint(for: provider) else { return "none" }
        let components = footprint.components
            .map { "\($0.path)=\($0.totalBytes)" }
            .joined(separator: ";")
        return [
            "\(footprint.totalBytes)",
            footprint.paths.joined(separator: ";"),
            footprint.missingPaths.joined(separator: ";"),
            footprint.unreadablePaths.joined(separator: ";"),
            components,
            String(Double(self.storageBreakdownMenuMaxHeight()).bitPattern, radix: 16),
        ].joined(separator: "|")
    }

    private func appendHostedSubviewUnavailableItem(
        to menu: NSMenu,
        chartID: String,
        providerRawValue: String?)
    {
        let unavailableItem = NSMenuItem(title: L("No data available"), action: nil, keyEquivalent: "")
        unavailableItem.isEnabled = false
        unavailableItem.representedObject = chartID
        unavailableItem.toolTip = providerRawValue
        menu.addItem(unavailableItem)
    }

    @discardableResult
    func appendUsageBreakdownChartItem(to submenu: NSMenu, width: CGFloat) -> Bool {
        let breakdown = OpenAIDashboardDailyBreakdown.removingSkillUsageServices(
            from: self.store.openAIDashboard?.usageBreakdown ?? [])
        guard !breakdown.isEmpty else { return false }

        if !self.menuCardRenderingEnabledForController {
            let chartItem = NSMenuItem()
            chartItem.isEnabled = true
            chartItem.representedObject = Self.usageBreakdownChartID
            submenu.addItem(chartItem)
            return true
        }

        let chartView = UsageBreakdownChartMenuView(breakdown: breakdown, width: width)
        let hosting = MenuHostingView(rootView: chartView)
        hosting.frame = NSRect(
            origin: .zero,
            size: NSSize(width: width, height: self.hostedSubviewFittingHeight(for: hosting, width: width)))

        let chartItem = NSMenuItem()
        chartItem.view = hosting
        chartItem.isEnabled = true
        chartItem.representedObject = Self.usageBreakdownChartID
        submenu.addItem(chartItem)
        return true
    }

    @discardableResult
    func appendCreditsHistoryChartItem(to submenu: NSMenu, width: CGFloat) -> Bool {
        let breakdown = self.store.openAIDashboard?.dailyBreakdown ?? []
        guard !breakdown.isEmpty else { return false }

        if !self.menuCardRenderingEnabledForController {
            let chartItem = NSMenuItem()
            chartItem.isEnabled = true
            chartItem.representedObject = Self.creditsHistoryChartID
            submenu.addItem(chartItem)
            return true
        }

        let chartView = CreditsHistoryChartMenuView(breakdown: breakdown, width: width)
        let hosting = MenuHostingView(rootView: chartView)
        hosting.frame = NSRect(
            origin: .zero,
            size: NSSize(width: width, height: self.hostedSubviewFittingHeight(for: hosting, width: width)))

        let chartItem = NSMenuItem()
        chartItem.view = hosting
        chartItem.isEnabled = true
        chartItem.representedObject = Self.creditsHistoryChartID
        submenu.addItem(chartItem)
        return true
    }

    @discardableResult
    func appendCostHistoryChartItem(
        to submenu: NSMenu,
        provider: UsageProvider,
        width: CGFloat) -> Bool
    {
        guard let tokenSnapshot = self.tokenSnapshotForCostHistorySubmenu(provider: provider) else { return false }
        guard !tokenSnapshot.daily.isEmpty else { return false }

        if !self.menuCardRenderingEnabledForController {
            let chartItem = NSMenuItem()
            chartItem.isEnabled = true
            chartItem.representedObject = Self.costHistoryChartID
            chartItem.toolTip = provider.rawValue
            submenu.addItem(chartItem)
            return true
        }

        let displayConversion = self.costHistoryDisplayConversion(for: tokenSnapshot)
        let chartView = CostHistoryChartMenuView(
            provider: provider,
            daily: tokenSnapshot.daily,
            totalCostUSD: tokenSnapshot.last30DaysCostUSD,
            currencyCode: displayConversion.currencyCode,
            costMultiplier: displayConversion.multiplier,
            historyDays: tokenSnapshot.historyDays,
            historyCoverageIsEstablished: tokenSnapshot.historyCoverageIsEstablished,
            windowLabel: tokenSnapshot.historyLabel,
            projects: provider == .codex ? tokenSnapshot.projects : [],
            sessions: provider == .codex ? tokenSnapshot.sessions : [],
            width: width)
        let hosting = MenuHostingView(rootView: chartView)
        hosting.applyMeasuredHeight(
            width: width,
            height: self.hostedSubviewFittingHeight(for: hosting, width: width))

        let chartItem = NSMenuItem()
        chartItem.view = hosting
        chartItem.isEnabled = true
        chartItem.representedObject = Self.costHistoryChartID
        chartItem.toolTip = provider.rawValue
        submenu.addItem(chartItem)
        return true
    }

    @discardableResult
    func appendStorageBreakdownItem(
        to submenu: NSMenu,
        provider: UsageProvider,
        width: CGFloat)
        -> Bool
    {
        guard let footprint = self.store.storageFootprint(for: provider),
              !footprint.components.isEmpty
        else { return false }

        if !self.menuCardRenderingEnabledForController {
            let item = NSMenuItem()
            item.isEnabled = true
            item.representedObject = Self.storageBreakdownID
            item.toolTip = provider.rawValue
            submenu.addItem(item)
            return true
        }

        let maxHeight = self.storageBreakdownMenuMaxHeight()
        final class HostingRelay {
            weak var hosting: MenuHostingView<StorageBreakdownMenuView>?
            var collapsedHeight: CGFloat = 1
        }
        let relay = HostingRelay()
        let view = StorageBreakdownMenuView(
            footprint: footprint,
            width: width,
            maxHeight: maxHeight,
            onExpansionHeightChange: { additionalHeight in
                relay.hosting?.applyMeasuredHeight(
                    width: width,
                    height: min(maxHeight, relay.collapsedHeight + additionalHeight))
            })
        let hosting = MenuHostingView(rootView: view)
        relay.hosting = hosting
        relay.collapsedHeight = self.hostedSubviewFittingHeight(for: hosting, width: width)
        hosting.applyMeasuredHeight(width: width, height: relay.collapsedHeight)

        let item = NSMenuItem()
        item.view = hosting
        item.isEnabled = true
        item.representedObject = Self.storageBreakdownID
        item.toolTip = provider.rawValue
        submenu.addItem(item)
        return true
    }

    @discardableResult
    func appendStatusComponentsItem(
        to submenu: NSMenu,
        provider: UsageProvider,
        width: CGFloat) -> Bool
    {
        // The list of component rows is shown only once the provider's status has been fetched.
        // Before the first fetch lands the submenu still renders (just the website link below), so
        // every provider with a status feed gets the native submenu rather than a bare link; it
        // re-hydrates with the live component list once data arrives (see makeStatusComponentsSubmenu).
        let components = Self.filterStatusComponents(self.store.statusComponents(for: provider), for: provider)
        if !components.isEmpty {
            if self.menuCardRenderingEnabledForController {
                final class HostingRelay {
                    weak var hosting: MenuHostingView<StatusComponentsMenuView>?
                }
                let relay = HostingRelay()
                let listView = StatusComponentsMenuView(
                    components: components,
                    width: width,
                    onToggle: {
                        // Re-measure the live content after SwiftUI applies the expand/collapse so the
                        // row grows/shrinks to fit exactly (no leftover blank space).
                        DispatchQueue.main.async {
                            guard let hosting = relay.hosting else { return }
                            hosting.applyMeasuredHeight(
                                width: width,
                                height: hosting.measuredFittingHeight(width: width))
                        }
                    })
                let hosting = MenuHostingView(rootView: listView)
                relay.hosting = hosting
                hosting.applyMeasuredHeight(width: width, height: hosting.measuredFittingHeight(width: width))

                let listItem = NSMenuItem()
                listItem.view = hosting
                listItem.isEnabled = false
                listItem.representedObject = Self.statusComponentsID
                listItem.toolTip = provider.rawValue
                submenu.addItem(listItem)
            } else {
                let placeholder = NSMenuItem()
                placeholder.isEnabled = false
                placeholder.representedObject = Self.statusComponentsID
                placeholder.toolTip = provider.rawValue
                submenu.addItem(placeholder)
            }

            submenu.addItem(.separator())
        }

        let linkItem = NSMenuItem(
            title: L("Open Status Page"),
            action: #selector(self.openStatusPageFromMenuItem(_:)),
            keyEquivalent: "")
        linkItem.target = self
        // Tag the link with the chart identity so the menu is still recognized as a status
        // submenu (and re-hydrates) when the component list hasn't loaded yet and the link is the
        // only row. The identifier also scopes the action to this submenu's provider so a later
        // menu selection change cannot open another provider's status page.
        linkItem.representedObject = Self.statusComponentsID
        linkItem.identifier = NSUserInterfaceItemIdentifier(provider.rawValue)
        if let image = NSImage(systemSymbolName: "arrow.up.right.square", accessibilityDescription: nil) {
            image.isTemplate = true
            image.size = NSSize(width: 16, height: 16)
            linkItem.image = image
        }
        submenu.addItem(linkItem)
        return true
    }

    private func storageBreakdownMenuMaxHeight() -> CGFloat {
        let visibleHeight = NSScreen.main?.visibleFrame.height ?? 900
        return min(620, max(360, floor(visibleHeight * 0.72)))
    }
}

#if DEBUG
extension StatusItemController {
    func _hostedSubviewRenderSignatureForTesting(menu: NSMenu, width: CGFloat) -> HostedSubviewRenderSignature? {
        guard let identity = self.hostedSubviewIdentity(for: menu) else { return nil }
        return self.hostedSubviewRenderSignature(identity: identity, width: width)
    }

    func _storedHostedSubviewRenderSignatureForTesting(menu: NSMenu) -> HostedSubviewRenderSignature? {
        self.hostedSubviewRenderSignatures.object(forKey: menu)?.signature
    }
}
#endif
