import AppIntents
import CodexBarCore
import WidgetKit

/// Provider-specific by design: AppIntents requires compile-time enum cases and display representations;
/// runtime presentation policy still comes from each provider descriptor below this literal WidgetKit surface.
enum BurnProviderChoice: String, AppEnum {
    case codex
    case claude

    static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Provider")

    static let caseDisplayRepresentations: [BurnProviderChoice: DisplayRepresentation] = [
        .codex: DisplayRepresentation(title: "Codex"),
        .claude: DisplayRepresentation(title: "Claude"),
    ]

    var provider: UsageProvider {
        switch self {
        case .codex: .codex
        case .claude: .claude
        }
    }
}

enum BurnWindowChoice: String, AppEnum {
    case session
    case weekly

    static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Usage window")

    static let caseDisplayRepresentations: [BurnWindowChoice: DisplayRepresentation] = [
        .session: DisplayRepresentation(title: "Session (5-hour)"),
        .weekly: DisplayRepresentation(title: "Weekly (7-day)"),
    ]
}

struct BurnDownSelectionIntent: AppIntent, WidgetConfigurationIntent {
    // Provider-specific by design: the original burn-down widget defaults to Codex session usage.
    static let title: LocalizedStringResource = "Burn Down"
    static let description = IntentDescription("Select the provider and usage window to display.")

    @Parameter(title: "Provider", default: .codex)
    var provider: BurnProviderChoice

    @Parameter(title: "Usage window", default: .session)
    var window: BurnWindowChoice

    init() {
        self.provider = .codex
        self.window = .session
    }
}

struct BurnProviderSelectionIntent: AppIntent, WidgetConfigurationIntent {
    // Provider-specific by design: the provider-only burn-down widget also defaults to Codex.
    static let title: LocalizedStringResource = "Burn Down Provider"
    static let description = IntentDescription("Select the provider to display.")

    @Parameter(title: "Provider", default: .codex)
    var provider: BurnProviderChoice

    init() {
        self.provider = .codex
    }
}

struct BurnDownEntry: TimelineEntry {
    let date: Date
    let provider: UsageProvider
    let window: BurnWindowChoice
    let snapshot: WidgetSnapshot
}

struct CombinedBurnDownEntry: TimelineEntry {
    let date: Date
    let provider: UsageProvider
    let snapshot: WidgetSnapshot
}

struct BurnDownState {
    private static let sessionWindowMinutes = 5 * 60
    private static let weeklyWindowMinutes = 7 * 24 * 60

    let entry: WidgetSnapshot.ProviderEntry
    let selection: BurnWindowChoice
    let now: Date

    init?(
        snapshot: WidgetSnapshot,
        provider: UsageProvider,
        selection: BurnWindowChoice,
        now: Date = Date())
    {
        guard let entry = snapshot.entries.first(where: { $0.provider == provider.instanceID }) else { return nil }
        self.entry = entry
        self.selection = selection
        self.now = now
    }

    var secondaryGloballyCapsPrimary: Bool {
        guard let provider = self.entry.provider.firstPartyProvider else { return false }
        return ProviderDescriptorRegistry.descriptor(for: provider).presentation.secondaryGloballyCapsPrimary
    }

    var secondaryExhausted: Bool {
        guard self.secondaryGloballyCapsPrimary, let secondary = self.secondaryWindow else { return false }
        guard secondary.remainingPercent <= 0 else { return false }
        return secondary.resetsAt.map { $0 > self.now } ?? true
    }

    var primaryWindow: RateWindow? {
        guard let primary = self.window(minutes: Self.sessionWindowMinutes) else { return nil }
        guard self.secondaryExhausted, primary.remainingPercent > 0 else { return primary }
        return RateWindow(
            usedPercent: 100,
            windowMinutes: primary.windowMinutes,
            resetsAt: primary.resetsAt,
            resetDescription: primary.resetDescription,
            nextRegenPercent: primary.nextRegenPercent)
    }

    var secondaryWindow: RateWindow? {
        self.window(minutes: Self.weeklyWindowMinutes)
    }

    var selectedWindow: RateWindow? {
        switch self.selection {
        case .session: self.primaryWindow
        case .weekly: self.secondaryWindow
        }
    }

    var blankPrimaryChart: Bool {
        self.selection == .session
            && self.secondaryExhausted
            && self.window(minutes: Self.sessionWindowMinutes) != nil
    }

    var selectedResetOverride: Date? {
        self.blankPrimaryChart ? self.secondaryWindow?.resetsAt : nil
    }

    private func window(minutes: Int) -> RateWindow? {
        [self.entry.primary, self.entry.secondary]
            .compactMap(\.self)
            .first { $0.windowMinutes == minutes }
    }
}

enum BurnDownRefreshSchedule {
    private static let minimumInterval: TimeInterval = 5 * 60
    private static let maximumInterval: TimeInterval = 30 * 60

    static func nextRefresh(
        snapshot: WidgetSnapshot,
        provider: UsageProvider,
        now: Date = Date()) -> Date
    {
        let fallback = now.addingTimeInterval(self.maximumInterval)
        guard let entry = snapshot.entries.first(where: { $0.provider == provider.instanceID }) else { return fallback }
        let nextReset = [entry.primary?.resetsAt, entry.secondary?.resetsAt]
            .compactMap(\.self)
            .filter { $0 > now }
            .min()?
            .addingTimeInterval(1)
        if let nextReset {
            let target = min(fallback, nextReset)
            let minimumDate = now.addingTimeInterval(self.minimumInterval)
            return max(minimumDate, target)
        }
        return fallback
    }
}

struct BurnDownTimelineProvider: AppIntentTimelineProvider {
    func placeholder(in context: Context) -> BurnDownEntry {
        BurnDownEntry(
            date: Date(),
            provider: .codex,
            window: .session,
            snapshot: WidgetPreviewData.snapshot())
    }

    func snapshot(for configuration: BurnDownSelectionIntent, in context: Context) async -> BurnDownEntry {
        BurnDownEntry(
            date: Date(),
            provider: configuration.provider.provider,
            window: configuration.window,
            snapshot: WidgetSnapshotStore.load() ?? WidgetPreviewData.snapshot())
    }

    func timeline(
        for configuration: BurnDownSelectionIntent,
        in context: Context) async -> Timeline<BurnDownEntry>
    {
        let entry = BurnDownEntry(
            date: Date(),
            provider: configuration.provider.provider,
            window: configuration.window,
            snapshot: WidgetSnapshotStore.load() ?? WidgetPreviewData.emptySnapshot())
        let refresh = BurnDownRefreshSchedule.nextRefresh(snapshot: entry.snapshot, provider: entry.provider)
        return Timeline(entries: [entry], policy: .after(refresh))
    }
}

struct CombinedBurnDownTimelineProvider: AppIntentTimelineProvider {
    func placeholder(in context: Context) -> CombinedBurnDownEntry {
        CombinedBurnDownEntry(
            date: Date(),
            provider: .codex,
            snapshot: WidgetPreviewData.snapshot())
    }

    func snapshot(
        for configuration: BurnProviderSelectionIntent,
        in context: Context) async -> CombinedBurnDownEntry
    {
        CombinedBurnDownEntry(
            date: Date(),
            provider: configuration.provider.provider,
            snapshot: WidgetSnapshotStore.load() ?? WidgetPreviewData.snapshot())
    }

    func timeline(
        for configuration: BurnProviderSelectionIntent,
        in context: Context) async -> Timeline<CombinedBurnDownEntry>
    {
        let entry = CombinedBurnDownEntry(
            date: Date(),
            provider: configuration.provider.provider,
            snapshot: WidgetSnapshotStore.load() ?? WidgetPreviewData.emptySnapshot())
        let refresh = BurnDownRefreshSchedule.nextRefresh(snapshot: entry.snapshot, provider: entry.provider)
        return Timeline(entries: [entry], policy: .after(refresh))
    }
}
