import Foundation

struct DataTypeSnapshotContext: Equatable, Sendable {
    let observationID: Int64
    let observedAt: Date
}

@Observable
final class DataTypesViewModel {
    var filter: DiffFilter = .all
    var comparisonMode: ComparisonMode = .previous
    var observationRows: [CachedArchiveObservationRow] = []
    var hasLoadedObservationRows = false
    var observationRowsError: String?
    var archiveDiffs: [TypeDiff]?
    var archiveDiffError: String?

    var observationContexts: [DataTypeSnapshotContext] {
        observationRows
            .sorted { $0.observedAt > $1.observedAt }
            .map {
                DataTypeSnapshotContext(
                    observationID: $0.observationID,
                    observedAt: $0.observedAt
                )
            }
    }

    func diffs() -> [TypeDiff] {
        guard let archiveDiffs else { return [] }
        return apply(filter: filter, to: archiveDiffs)
    }

    func baseline(for snapshot: DataTypeSnapshotContext, in snapshots: [DataTypeSnapshotContext]) -> DataTypeSnapshotContext? {
        resolveBaseline(for: snapshot, in: snapshots)
    }

    func clearArchiveDiffs() {
        archiveDiffs = nil
        archiveDiffError = nil
    }

    @MainActor
    func loadArchiveRows(limit: Int = 200) async {
        do {
            observationRows = try await SQLiteHealthArchiveStore.shared.observationRows(limit: limit)
            hasLoadedObservationRows = true
            observationRowsError = nil
        } catch {
            observationRows = []
            hasLoadedObservationRows = true
            observationRowsError = error.localizedDescription
        }
    }

    @MainActor
    func loadArchiveDiffs(current: DataTypeSnapshotContext?, snapshots: [DataTypeSnapshotContext]) async {
        guard let current,
              let baseline = resolveBaseline(for: current, in: snapshots) else {
            clearArchiveDiffs()
            return
        }

        do {
            let archiveStore = SQLiteHealthArchiveStore.shared
            let currentSummaries = try await archiveStore.typeSummaries(observationID: current.observationID)
            let baselineSummaries = try await archiveStore.typeSummaries(observationID: baseline.observationID)
            let currentByType = Dictionary(uniqueKeysWithValues: currentSummaries.map { ($0.sampleTypeIdentifier, $0) })
            let baselineByType = Dictionary(uniqueKeysWithValues: baselineSummaries.map { ($0.sampleTypeIdentifier, $0) })
            let allTypeIdentifiers = Set(currentByType.keys).union(baselineByType.keys)
            let canUseMaterializedDiffs = isImmediatePreviousBaseline(
                baseline,
                for: current,
                in: snapshots
            )

            var archiveRows: [TypeDiff] = []
            archiveRows.reserveCapacity(allTypeIdentifiers.count)

            for typeIdentifier in allTypeIdentifiers {
                let summary = currentByType[typeIdentifier]
                let baselineSummary = baselineByType[typeIdentifier]
                let diff: HealthArchiveDiffSummary?
                if canUseMaterializedDiffs {
                    diff = nil
                } else {
                    diff = try await archiveStore.diffSummary(HealthArchiveDiffRequest(
                        fromObservationID: baseline.observationID,
                        toObservationID: current.observationID,
                        sampleTypeIdentifier: typeIdentifier
                    ))
                }
                archiveRows.append(TypeDiff(
                    id: typeIdentifier,
                    typeIdentifier: typeIdentifier,
                    displayName: summary?.displayName ?? baselineSummary?.displayName ?? typeIdentifier,
                    currentCount: summary?.visibleRecordCount ?? 0,
                    previousCount: baselineSummary?.visibleRecordCount ?? 0,
                    previousTracked: baselineSummary != nil,
                    appearedCount: diff?.appearedCount ?? summary?.appearedCount ?? 0,
                    disappearedCount: diff?.disappearedCount ?? summary?.disappearedCount ?? 0,
                    representationChangedCount: diff?.representationChangedCount ?? summary?.representationChangedCount ?? 0
                ))
            }

            archiveDiffs = archiveRows.sorted {
                $0.displayName.localizedCompare($1.displayName) == .orderedAscending
            }
            archiveDiffError = nil
        } catch {
            archiveDiffs = nil
            archiveDiffError = error.localizedDescription
        }
    }

    private func isImmediatePreviousBaseline(
        _ baseline: DataTypeSnapshotContext,
        for snapshot: DataTypeSnapshotContext,
        in snapshots: [DataTypeSnapshotContext]
    ) -> Bool {
        snapshots
            .filter { $0.observedAt < snapshot.observedAt }
            .max { $0.observedAt < $1.observedAt }?
            .observationID == baseline.observationID
    }

    private func resolveBaseline(
        for snapshot: DataTypeSnapshotContext,
        in snapshots: [DataTypeSnapshotContext]
    ) -> DataTypeSnapshotContext? {
        let sorted = snapshots.sorted { $0.observedAt > $1.observedAt }
        switch comparisonMode {
        case .previous:
            return sorted.first { $0.observedAt < snapshot.observedAt }
        case .selected:
            return nil  // DataTypesView uses .previous by default; selection lives in SnapshotsTab
        case .relativeTime(let interval):
            let target = snapshot.observedAt.addingTimeInterval(-interval)
            return snapshots
                .filter { $0.observedAt <= target }
                .max { $0.observedAt < $1.observedAt }
        }
    }

    private func apply(filter: DiffFilter, to diffs: [TypeDiff]) -> [TypeDiff] {
        switch filter {
        case .all:       return diffs
        case .changed:   return diffs.filter { $0.previousTracked && $0.hasChanges }
        case .increased: return diffs.filter { $0.previousTracked && $0.delta > 0 }
        case .decreased: return diffs.filter { $0.previousTracked && $0.delta < 0 }
        }
    }
}
