import Foundation
import ComposeApp
import Combine

/// SwiftUI-compatible wrapper for Kotlin DataManager.
/// Observes all DataManager StateFlows and publishes changes via @Published properties.
///
/// Usage in SwiftUI views:
/// ```swift
/// @StateObject private var dataManager = DataManagerObservable.shared
/// // or
/// @EnvironmentObject var dataManager: DataManagerObservable
/// ```
///
/// This is the Swift-side Single Source of Truth that mirrors Kotlin's DataManager.
/// All screens should observe this instead of making duplicate API calls.
@MainActor
class DataManagerObservable: ObservableObject {

    // MARK: - Singleton

    static let shared = DataManagerObservable()

    // MARK: - Authentication

    @Published var authToken: String?
    @Published var currentUser: User?
    @Published var isAuthenticated: Bool = false

    // MARK: - App Preferences

    @Published var themeId: String = "default"

    // MARK: - Residences

    @Published var residences: [ResidenceResponse] = []
    @Published var myResidences: MyResidencesResponse?
    @Published var totalSummary: TotalSummary?
    @Published var residenceSummaries: [Int32: ResidenceSummaryResponse] = [:]

    // MARK: - Tasks

    @Published var allTasks: TaskColumnsResponse?
    @Published var tasksByResidence: [Int32: TaskColumnsResponse] = [:]

    // MARK: - Documents

    @Published var documents: [Document] = []
    @Published var documentsByResidence: [Int32: [Document]] = [:]

    // MARK: - Contractors

    @Published var contractors: [ContractorSummary] = []

    // MARK: - Subscription

    @Published var subscription: SubscriptionStatus?
    @Published var upgradeTriggers: [String: UpgradeTriggerData] = [:]
    @Published var featureBenefits: [FeatureBenefit] = []
    @Published var promotions: [Promotion] = []

    // MARK: - Lookups (Reference Data)

    @Published var residenceTypes: [ResidenceType] = []
    @Published var taskFrequencies: [TaskFrequency] = []
    @Published var taskPriorities: [TaskPriority] = []
    @Published var taskCategories: [TaskCategory] = []
    @Published var contractorSpecialties: [ContractorSpecialty] = []

    // MARK: - Task Templates

    @Published var taskTemplates: [TaskTemplate] = []
    @Published var taskTemplatesGrouped: TaskTemplatesGroupedResponse?

    // MARK: - State Metadata

    @Published var isInitialized: Bool = false
    @Published var lookupsInitialized: Bool = false
    @Published var lastSyncTime: Int64 = 0

    // MARK: - Private Properties

    private var observationTasks: [Task<Void, Never>] = []
    private var widgetSaveWorkItem: DispatchWorkItem?

    // MARK: - Initialization

    private init() {
        startObserving()
    }

    /// Test-only initializer that skips observing the Kotlin
    /// `DataManager.shared` singleton, so callers can assign the
    /// `@Published` properties directly from fixture data. Used by the
    /// parity-gallery SnapshotGalleryTests to produce populated-state
    /// snapshots. Production code must never call this — views always
    /// resolve `DataManagerObservable.shared`.
    ///
    /// - Parameter observeSharedDataManager: When `false`, no Kotlin
    ///   StateFlow observation tasks are spun up. Callers are expected
    ///   to seed `@Published` properties manually.
    init(observeSharedDataManager: Bool) {
        if observeSharedDataManager {
            startObserving()
        }
    }

    /// Test-only initializer that seeds every `@Published` property from
    /// the current value of the matching Kotlin StateFlow on the given
    /// `IDataManager`. This is a synchronous snapshot — later changes to
    /// the Kotlin fixture are NOT observed. Used by the parity-gallery
    /// SnapshotGalleryTests to produce populated-state renders from
    /// `FixtureDataManager.populated()`.
    ///
    /// Production code must never call this — views always resolve
    /// `DataManagerObservable.shared`.
    convenience init(kotlin fixture: IDataManager) {
        self.init(observeSharedDataManager: false)

        // Auth
        self.currentUser = fixture.currentUser.value
        self.isAuthenticated = fixture.currentUser.value != nil

        // Residences
        self.residences = fixture.residences.value
        self.myResidences = fixture.myResidences.value
        self.totalSummary = fixture.totalSummary.value
        self.residenceSummaries = Self.convertIntMapSync(fixture.residenceSummaries.value)

        // Tasks
        self.allTasks = fixture.allTasks.value
        self.tasksByResidence = Self.convertIntMapSync(fixture.tasksByResidence.value)

        // Documents
        self.documents = fixture.documents.value
        self.documentsByResidence = Self.convertIntArrayMapSync(fixture.documentsByResidence.value)

        // Contractors
        self.contractors = fixture.contractors.value

        // Subscription
        self.subscription = fixture.subscription.value
        self.upgradeTriggers = Self.convertStringMapSync(fixture.upgradeTriggers.value)
        self.featureBenefits = fixture.featureBenefits.value
        self.promotions = fixture.promotions.value

        // Lookups
        self.residenceTypes = fixture.residenceTypes.value
        self.taskFrequencies = fixture.taskFrequencies.value
        self.taskPriorities = fixture.taskPriorities.value
        self.taskCategories = fixture.taskCategories.value
        self.contractorSpecialties = fixture.contractorSpecialties.value

        // Task Templates
        self.taskTemplates = fixture.taskTemplates.value
        self.taskTemplatesGrouped = fixture.taskTemplatesGrouped.value

        // Lookups are fully populated by both FixtureDataManager.empty()
        // and FixtureDataManager.populated(), so surfaces that gate UI on
        // `lookupsInitialized` render their non-empty form.
        self.lookupsInitialized = !fixture.residenceTypes.value.isEmpty ||
            !fixture.taskPriorities.value.isEmpty ||
            !fixture.taskCategories.value.isEmpty
        self.isInitialized = self.lookupsInitialized
    }

    // MARK: - Synchronous Map Converters (fixture seeding)

    private static func convertIntMapSync<V>(_ kotlinMap: Any?) -> [Int32: V] {
        guard let nsDict = kotlinMap as? NSDictionary else { return [:] }
        var result: [Int32: V] = [:]
        for key in nsDict.allKeys {
            guard let value = nsDict[key], let typedValue = value as? V else { continue }
            if let kotlinKey = key as? KotlinInt {
                result[kotlinKey.int32Value] = typedValue
            } else if let nsNumberKey = key as? NSNumber {
                result[nsNumberKey.int32Value] = typedValue
            }
        }
        return result
    }

    private static func convertIntArrayMapSync<V>(_ kotlinMap: Any?) -> [Int32: [V]] {
        guard let nsDict = kotlinMap as? NSDictionary else { return [:] }
        var result: [Int32: [V]] = [:]
        for key in nsDict.allKeys {
            guard let value = nsDict[key] else { continue }
            let typed: [V]
            if let arr = value as? [V] {
                typed = arr
            } else if let nsArr = value as? NSArray {
                typed = nsArr.compactMap { $0 as? V }
            } else {
                continue
            }
            if let kotlinKey = key as? KotlinInt {
                result[kotlinKey.int32Value] = typed
            } else if let nsNumberKey = key as? NSNumber {
                result[nsNumberKey.int32Value] = typed
            }
        }
        return result
    }

    private static func convertStringMapSync<V>(_ kotlinMap: Any?) -> [String: V] {
        if let direct = kotlinMap as? [String: V] { return direct }
        guard let nsDict = kotlinMap as? NSDictionary else { return [:] }
        var result: [String: V] = [:]
        for key in nsDict.allKeys {
            guard let sKey = key as? String, let value = nsDict[key] as? V else { continue }
            result[sKey] = value
        }
        return result
    }

    // MARK: - Observation Setup

    /// Start observing all DataManager StateFlows
    private func startObserving() {
        // Authentication - authToken
        let authTokenTask = Task { [weak self] in
            for await token in DataManager.shared.authToken {
                guard let self else { return }
                let previousToken = self.authToken
                let wasAuthenticated = previousToken != nil
                self.authToken = token
                self.isAuthenticated = token != nil

                // Token rotated/account switched without explicit logout.
                if let previousToken, let token, previousToken != token {
                    PushNotificationManager.shared.clearRegistrationCache()
                }

                // Keep widget auth in sync with token lifecycle.
                if let token {
                    WidgetDataManager.shared.saveAuthToken(token)
                    WidgetDataManager.shared.saveAPIBaseURL(ApiClient.shared.getBaseUrl())
                }

                // Clear widget cache on logout
                if token == nil && wasAuthenticated {
                    WidgetDataManager.shared.clearCache()
                    WidgetDataManager.shared.clearAuthToken()
                    PushNotificationManager.shared.clearRegistrationCache()
                }
            }
        }
        observationTasks.append(authTokenTask)

        // Authentication - currentUser
        let currentUserTask = Task { [weak self] in
            for await user in DataManager.shared.currentUser {
                guard let self else { return }
                self.currentUser = user
            }
        }
        observationTasks.append(currentUserTask)

        // Theme
        let themeIdTask = Task { [weak self] in
            for await id in DataManager.shared.themeId {
                guard let self else { return }
                self.themeId = id
            }
        }
        observationTasks.append(themeIdTask)

        // Residences
        let residencesTask = Task { [weak self] in
            for await list in DataManager.shared.residences {
                guard let self else { return }
                self.residences = list
            }
        }
        observationTasks.append(residencesTask)

        // MyResidences
        let myResidencesTask = Task { [weak self] in
            for await response in DataManager.shared.myResidences {
                guard let self else { return }
                self.myResidences = response
            }
        }
        observationTasks.append(myResidencesTask)

        // TotalSummary
        let totalSummaryTask = Task { [weak self] in
            for await summary in DataManager.shared.totalSummary {
                guard let self else { return }
                self.totalSummary = summary
            }
        }
        observationTasks.append(totalSummaryTask)

        // ResidenceSummaries
        let residenceSummariesTask = Task { [weak self] in
            for await summaries in DataManager.shared.residenceSummaries {
                guard let self else { return }
                self.residenceSummaries = self.convertIntMap(summaries)
            }
        }
        observationTasks.append(residenceSummariesTask)

        // AllTasks
        let allTasksTask = Task { [weak self] in
            for await tasks in DataManager.shared.allTasks {
                guard let self else { return }
                self.allTasks = tasks
                self.recomputeActiveTasks()
                // Save to widget shared container (debounced)
                if let tasks = tasks {
                    self.debouncedWidgetSave(tasks: tasks)
                }
            }
        }
        observationTasks.append(allTasksTask)

        // TasksByResidence
        let tasksByResidenceTask = Task { [weak self] in
            for await tasks in DataManager.shared.tasksByResidence {
                guard let self else { return }
                self.tasksByResidence = self.convertIntMap(tasks)
            }
        }
        observationTasks.append(tasksByResidenceTask)

        // Documents
        let documentsTask = Task { [weak self] in
            for await docs in DataManager.shared.documents {
                guard let self else { return }
                self.documents = docs
            }
        }
        observationTasks.append(documentsTask)

        // DocumentsByResidence
        let documentsByResidenceTask = Task { [weak self] in
            for await docs in DataManager.shared.documentsByResidence {
                guard let self else { return }
                self.documentsByResidence = self.convertIntArrayMap(docs)
            }
        }
        observationTasks.append(documentsByResidenceTask)

        // Contractors
        let contractorsTask = Task { [weak self] in
            for await list in DataManager.shared.contractors {
                guard let self else { return }
                self.contractors = list
            }
        }
        observationTasks.append(contractorsTask)

        // Subscription
        let subscriptionTask = Task { [weak self] in
            for await sub in DataManager.shared.subscription {
                guard let self else { return }
                self.subscription = sub
            }
        }
        observationTasks.append(subscriptionTask)

        // UpgradeTriggers
        let upgradeTriggersTask = Task { [weak self] in
            for await triggers in DataManager.shared.upgradeTriggers {
                guard let self else { return }
                self.upgradeTriggers = self.convertStringMap(triggers)
            }
        }
        observationTasks.append(upgradeTriggersTask)

        // FeatureBenefits
        let featureBenefitsTask = Task { [weak self] in
            for await benefits in DataManager.shared.featureBenefits {
                guard let self else { return }
                self.featureBenefits = benefits
            }
        }
        observationTasks.append(featureBenefitsTask)

        // Promotions
        let promotionsTask = Task { [weak self] in
            for await promos in DataManager.shared.promotions {
                guard let self else { return }
                self.promotions = promos
            }
        }
        observationTasks.append(promotionsTask)

        // Lookups - ResidenceTypes
        let residenceTypesTask = Task { [weak self] in
            for await types in DataManager.shared.residenceTypes {
                guard let self else { return }
                self.residenceTypes = types
            }
        }
        observationTasks.append(residenceTypesTask)

        // Lookups - TaskFrequencies
        let taskFrequenciesTask = Task { [weak self] in
            for await items in DataManager.shared.taskFrequencies {
                guard let self else { return }
                self.taskFrequencies = items
            }
        }
        observationTasks.append(taskFrequenciesTask)

        // Lookups - TaskPriorities
        let taskPrioritiesTask = Task { [weak self] in
            for await items in DataManager.shared.taskPriorities {
                guard let self else { return }
                self.taskPriorities = items
            }
        }
        observationTasks.append(taskPrioritiesTask)

        // Lookups - TaskCategories
        let taskCategoriesTask = Task { [weak self] in
            for await items in DataManager.shared.taskCategories {
                guard let self else { return }
                self.taskCategories = items
            }
        }
        observationTasks.append(taskCategoriesTask)

        // Lookups - ContractorSpecialties
        let contractorSpecialtiesTask = Task { [weak self] in
            for await items in DataManager.shared.contractorSpecialties {
                guard let self else { return }
                self.contractorSpecialties = items
            }
        }
        observationTasks.append(contractorSpecialtiesTask)

        // Task Templates
        let taskTemplatesTask = Task { [weak self] in
            for await items in DataManager.shared.taskTemplates {
                guard let self else { return }
                self.taskTemplates = items
            }
        }
        observationTasks.append(taskTemplatesTask)

        // Task Templates Grouped
        let taskTemplatesGroupedTask = Task { [weak self] in
            for await response in DataManager.shared.taskTemplatesGrouped {
                guard let self else { return }
                self.taskTemplatesGrouped = response
            }
        }
        observationTasks.append(taskTemplatesGroupedTask)

        // Metadata - isInitialized
        let isInitializedTask = Task { [weak self] in
            for await initialized in DataManager.shared.isInitialized {
                guard let self else { return }
                self.isInitialized = initialized.boolValue
            }
        }
        observationTasks.append(isInitializedTask)

        // Metadata - lookupsInitialized
        let lookupsInitializedTask = Task { [weak self] in
            for await initialized in DataManager.shared.lookupsInitialized {
                guard let self else { return }
                self.lookupsInitialized = initialized.boolValue
            }
        }
        observationTasks.append(lookupsInitializedTask)

        // Metadata - lastSyncTime
        let lastSyncTimeTask = Task { [weak self] in
            for await time in DataManager.shared.lastSyncTime {
                guard let self else { return }
                self.lastSyncTime = time.int64Value
            }
        }
        observationTasks.append(lastSyncTimeTask)
    }

    // MARK: - Widget Save Debounce

    /// Debounce widget saves to avoid excessive disk I/O on rapid task updates.
    /// Cancels any pending save and schedules a new one after 2 seconds.
    private func debouncedWidgetSave(tasks: TaskColumnsResponse) {
        widgetSaveWorkItem?.cancel()
        let workItem = DispatchWorkItem { [weak self] in
            guard self != nil else { return }
            WidgetDataManager.shared.saveTasks(from: tasks)
        }
        widgetSaveWorkItem = workItem
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0, execute: workItem)
    }

    /// Stop all observations
    func stopObserving() {
        observationTasks.forEach { $0.cancel() }
        observationTasks.removeAll()
    }

    // MARK: - Map Conversion Helpers

    /// Convert Kotlin Map<Int, V> to Swift [Int32: V]
    /// Uses ObjectIdentifier-based iteration to avoid Swift bridging issues with KotlinInt keys
    private func convertIntMap<V>(_ kotlinMap: Any?) -> [Int32: V] {
        guard let kotlinMap = kotlinMap else {
            return [:]
        }

        guard let nsDict = kotlinMap as? NSDictionary else {
            print("DataManagerObservable: Failed to bridge Int-keyed map to NSDictionary")
            return [:]
        }

        var result: [Int32: V] = [:]
        var failedKeys = 0
        let totalKeys = nsDict.allKeys.count

        for key in nsDict.allKeys {
            guard let value = nsDict[key], let typedValue = value as? V else { continue }

            // Extract the int value from whatever key type we have
            if let kotlinKey = key as? KotlinInt {
                result[kotlinKey.int32Value] = typedValue
            } else if let nsNumberKey = key as? NSNumber {
                result[nsNumberKey.int32Value] = typedValue
            } else {
                failedKeys += 1
                print("DataManagerObservable: convertIntMap failed to convert key of type \(type(of: key)): \(key)")
            }
        }

        if failedKeys > 0 {
            print("DataManagerObservable: Warning: \(failedKeys) of \(totalKeys) keys failed to convert in convertIntMap")
        }

        return result
    }

    /// Convert Kotlin Map<Int, List<V>> to Swift [Int32: [V]]
    private func convertIntArrayMap<V>(_ kotlinMap: Any?) -> [Int32: [V]] {
        guard let kotlinMap = kotlinMap else {
            return [:]
        }

        guard let nsDict = kotlinMap as? NSDictionary else {
            print("DataManagerObservable: Failed to bridge Int->Array map to NSDictionary")
            return [:]
        }

        var result: [Int32: [V]] = [:]
        var failedKeys = 0
        let totalKeys = nsDict.allKeys.count

        for key in nsDict.allKeys {
            guard let value = nsDict[key] else { continue }

            let typedValue: [V]
            if let swiftArray = value as? [V] {
                typedValue = swiftArray
            } else if let nsArray = value as? NSArray {
                let converted = nsArray.compactMap { $0 as? V }
                guard converted.count == nsArray.count else { continue }
                typedValue = converted
            } else {
                continue
            }

            if let kotlinKey = key as? KotlinInt {
                result[kotlinKey.int32Value] = typedValue
            } else if let nsNumberKey = key as? NSNumber {
                result[nsNumberKey.int32Value] = typedValue
            } else {
                failedKeys += 1
                print("DataManagerObservable: convertIntArrayMap failed to convert key of type \(type(of: key)): \(key)")
            }
        }

        if failedKeys > 0 {
            print("DataManagerObservable: Warning: \(failedKeys) of \(totalKeys) keys failed to convert in convertIntArrayMap")
        }

        return result
    }

    /// Convert Kotlin Map<String, V> to Swift [String: V]
    private func convertStringMap<V>(_ kotlinMap: Any?) -> [String: V] {
        guard let kotlinMap = kotlinMap else {
            return [:]
        }

        if let map = kotlinMap as? [String: V] {
            return map
        }

        guard let nsDict = kotlinMap as? NSDictionary else {
            return [:]
        }

        var result: [String: V] = [:]
        for key in nsDict.allKeys {
            guard let stringKey = key as? String,
                  let value = nsDict[key] as? V else { continue }
            result[stringKey] = value
        }
        return result
    }

    // MARK: - Convenience Lookup Methods
    // Note: These use O(n) linear search which is acceptable for small lookup arrays
    // (typically <20 items each). Dictionary-based lookups would add complexity
    // for negligible performance gain at this scale.

    /// Get residence type by ID
    func getResidenceType(id: Int32?) -> ResidenceType? {
        guard let id = id else { return nil }
        return residenceTypes.first { $0.id == id }
    }

    /// Get task frequency by ID
    func getTaskFrequency(id: Int32?) -> TaskFrequency? {
        guard let id = id else { return nil }
        return taskFrequencies.first { $0.id == id }
    }

    /// Get task priority by ID
    func getTaskPriority(id: Int32?) -> TaskPriority? {
        guard let id = id else { return nil }
        return taskPriorities.first { $0.id == id }
    }

    /// Get task category by ID
    func getTaskCategory(id: Int32?) -> TaskCategory? {
        guard let id = id else { return nil }
        return taskCategories.first { $0.id == id }
    }

    /// Get contractor specialty by ID
    func getContractorSpecialty(id: Int32?) -> ContractorSpecialty? {
        guard let id = id else { return nil }
        return contractorSpecialties.first { $0.id == id }
    }

    // MARK: - Task Helpers

    /// Get tasks for a specific residence
    func tasks(for residenceId: Int32) -> TaskColumnsResponse? {
        return tasksByResidence[residenceId]
    }

    /// Get documents for a specific residence
    func documents(for residenceId: Int32) -> [Document] {
        return documentsByResidence[residenceId] ?? []
    }

    /// Get residence summary
    func summary(for residenceId: Int32) -> ResidenceSummaryResponse? {
        return residenceSummaries[residenceId]
    }

    /// Total task count across all columns
    var totalTaskCount: Int {
        guard let response = allTasks else { return 0 }
        return response.columns.reduce(0) { $0 + Int($1.count) }
    }

    /// Check if there are no tasks
    var hasNoTasks: Bool {
        guard let response = allTasks else { return true }
        return response.columns.allSatisfy { $0.tasks.isEmpty }
    }

    // MARK: - Task Stats (Single Source of Truth)
    // Uses API column names + shared calculateMetrics function

    /// Active tasks (excludes completed and cancelled).
    /// Computed once when `allTasks` changes and cached to avoid
    /// redundant iteration in `totalTaskMetrics`, `taskMetrics(for:)`, etc.
    private(set) var activeTasks: [TaskResponse] = []

    /// Recompute cached activeTasks from allTasks
    private func recomputeActiveTasks() {
        guard let response = allTasks else {
            activeTasks = []
            return
        }
        var tasks: [TaskResponse] = []
        for column in response.columns {
            let columnName = column.name.lowercased()
            if columnName == "completed_tasks" || columnName == "cancelled_tasks" {
                continue
            }
            tasks.append(contentsOf: column.tasks)
        }
        activeTasks = tasks
    }

    /// Get tasks from a specific column by name
    private func tasksInColumn(_ columnName: String) -> [TaskResponse] {
        guard let response = allTasks else { return [] }
        return response.columns.first { $0.name.lowercased() == columnName.lowercased() }?.tasks ?? []
    }

    /// Convert TaskResponse array to WidgetTask array for shared calculator
    /// isDueWithin7Days and isDue8To30Days are calculated from actual due dates,
    /// NOT from API column membership (which uses different thresholds)
    private func toWidgetTasks(_ tasks: [TaskResponse], overdueIds: Set<Int32>, dueWithin7DaysIds: Set<Int32>, due8To30DaysIds: Set<Int32>) -> [WidgetDataManager.WidgetTask] {
        let today = Calendar.current.startOfDay(for: Date())

        return tasks.map { task in
            // Calculate actual days until due based on effectiveDueDate
            var isDueWithin7Days = false
            var isDue8To30Days = false

            if let dueDate = DateUtils.parseDate(task.effectiveDueDate) {
                let dueDay = Calendar.current.startOfDay(for: dueDate)
                let daysUntilDue = Calendar.current.dateComponents([.day], from: today, to: dueDay).day ?? 0

                // Only count future tasks (not overdue)
                if daysUntilDue >= 0 && daysUntilDue <= 7 {
                    isDueWithin7Days = true
                } else if daysUntilDue > 7 && daysUntilDue <= 30 {
                    isDue8To30Days = true
                }
            }

            return WidgetDataManager.WidgetTask(
                id: Int(task.id),
                title: task.title,
                description: task.description_,
                priority: task.priorityName,
                inProgress: task.inProgress,
                dueDate: task.effectiveDueDate,
                category: task.categoryName,
                residenceName: nil,
                isOverdue: overdueIds.contains(task.id),
                isDueWithin7Days: isDueWithin7Days,
                isDue8To30Days: isDue8To30Days
            )
        }
    }

    /// Get column ID sets (used by both stats and widget saving)
    /// Uses column name constants from WidgetDataManager (single source of truth)
    private var columnIdSets: (overdue: Set<Int32>, dueWithin7Days: Set<Int32>, due8To30Days: Set<Int32>) {
        (
            overdue: Set(tasksInColumn(WidgetDataManager.overdueColumn).map { $0.id }),
            dueWithin7Days: Set(tasksInColumn(WidgetDataManager.dueWithin7DaysColumn).map { $0.id }),
            due8To30Days: Set(tasksInColumn(WidgetDataManager.due8To30DaysColumn).map { $0.id })
        )
    }

    /// Calculate task metrics for a given set of tasks
    /// Returns WidgetDataManager.TaskMetrics - the single source of truth for all metrics
    private func calculateMetrics(for tasks: [TaskResponse]) -> WidgetDataManager.TaskMetrics {
        let ids = columnIdSets
        let widgetTasks = toWidgetTasks(tasks, overdueIds: ids.overdue, dueWithin7DaysIds: ids.dueWithin7Days, due8To30DaysIds: ids.due8To30Days)
        return WidgetDataManager.calculateMetrics(from: widgetTasks)
    }

    /// Total task metrics across all residences - USE THIS FOR DASHBOARD SUMMARY
    var totalTaskMetrics: WidgetDataManager.TaskMetrics {
        calculateMetrics(for: activeTasks)
    }

    /// Task metrics for a specific residence - USE THIS FOR RESIDENCE CARDS
    func taskMetrics(for residenceId: Int32) -> WidgetDataManager.TaskMetrics {
        calculateMetrics(for: activeTasks.filter { $0.residenceId == residenceId })
    }

    /// Active task count for a specific residence
    func activeTaskCount(for residenceId: Int32) -> Int {
        return activeTasks.filter { $0.residenceId == residenceId }.count
    }

    // MARK: - Task Template Helpers

    /// Search task templates by query string
    func searchTaskTemplates(query: String) -> [TaskTemplate] {
        return DataManager.shared.searchTaskTemplates(query: query)
    }

    /// Get total task template count
    var taskTemplateCount: Int {
        return taskTemplates.count
    }
}
