//
//  ContainerStatus.swift
//  Reference code — copy into your app, adjust the type/file names to taste.
//
//  Adapted from the IndieDiag package (`iCloudContainerDiagnostic.swift`,
//  commit bc6f475). The original resolved account status via
//  `CKContainer.accountStatus`, which pulls in CloudKit for a fact
//  `FileManager` already knows: `ubiquityIdentityToken` is non-nil exactly
//  when an iCloud account is signed in, checked synchronously with no
//  network round trip and no CloudKit entitlement required. That swap is
//  the whole reason this file has no `import CloudKit`.
//
//  Trade-off: `ubiquityIdentityToken` only distinguishes "signed in" from
//  "not signed in." It cannot tell you *why* an account is unavailable
//  (restricted, temporarily unavailable, etc.) the way CloudKit's richer
//  `CKAccountStatus` can — that granularity isn't worth reintroducing
//  CloudKit for a documents-only app. If you need it, it means you've
//  reintroduced CloudKit somewhere else too.
//

import Foundation

/// Availability and configuration of the app's iCloud ubiquity container.
public struct ICloudContainerStatus: Sendable {
    public let isAvailable: Bool
    public let containerURL: URL?
    public let containerIdentifier: String?
    public let hasSignedInAccount: Bool
    public let lastChecked: Date

    public init(
        isAvailable: Bool,
        containerURL: URL?,
        containerIdentifier: String?,
        hasSignedInAccount: Bool,
        lastChecked: Date = Date()
    ) {
        self.isAvailable = isAvailable
        self.containerURL = containerURL
        self.containerIdentifier = containerIdentifier
        self.hasSignedInAccount = hasSignedInAccount
        self.lastChecked = lastChecked
    }
}

/// Point-in-time checks for the iCloud ubiquity container: is an account
/// signed in, and can the container's URL be resolved. An `actor` because
/// `url(forUbiquityContainerIdentifier:)` is a blocking FileManager call
/// that can stall indefinitely when the account is in a bad state (password
/// reverification pending is the classic case per the icloud-sync-engine
/// skill) — actor isolation keeps that stall off whichever thread awaits it,
/// in practice the main thread.
///
/// This does point-in-time checks, not continuous monitoring. Call it when
/// a diagnostics screen appears or the user taps "Refresh" — see
/// SKILL.md's anti-patterns for why this shouldn't run on a timer.
public actor ContainerDiagnostic {
    private let containerIdentifier: String?

    /// - Parameter containerIdentifier: iCloud container identifier. Pass
    ///   `nil` to use the app's default (single) ubiquity container.
    public init(containerIdentifier: String? = nil) {
        self.containerIdentifier = containerIdentifier
    }

    /// Full status snapshot: signed-in check + container URL resolution.
    public func checkStatus() async -> ICloudContainerStatus {
        let hasAccount = FileManager.default.ubiquityIdentityToken != nil
        let containerURL = FileManager.default.url(forUbiquityContainerIdentifier: containerIdentifier)

        return ICloudContainerStatus(
            isAvailable: hasAccount && containerURL != nil,
            containerURL: containerURL,
            containerIdentifier: containerIdentifier,
            hasSignedInAccount: hasAccount
        )
    }

    /// Convenience for a plain yes/no check.
    public func isAvailable() async -> Bool {
        await checkStatus().isAvailable
    }

    /// The `Documents/` subdirectory inside the container, creating it if
    /// needed. Returns `nil` when the container can't be resolved — treat
    /// that as "iCloud required," not as a cue to fall back to local
    /// storage (see the icloud-sync-engine skill).
    public func documentsURL() async -> URL? {
        guard let containerURL = FileManager.default.url(forUbiquityContainerIdentifier: containerIdentifier) else {
            return nil
        }

        let documentsURL = containerURL.appendingPathComponent("Documents", isDirectory: true)
        try? FileManager.default.createDirectory(at: documentsURL, withIntermediateDirectories: true)
        return documentsURL
    }

    /// File count and total size under `Documents/`, for a diagnostics
    /// screen's "N documents, X MB" line. Walks the whole tree — fine for
    /// typical per-user datasets, but it's an O(files) directory walk, not
    /// something to call in a loop.
    public func containerStats() async -> (fileCount: Int, totalBytes: Int64)? {
        guard let documentsURL = await documentsURL() else { return nil }
        return Self.walk(documentsURL)
    }

    /// Synchronous, non-`async` on purpose: `FileManager.DirectoryEnumerator`
    /// iteration goes through `NSEnumerator.nextObject()`, which Foundation
    /// marks unavailable directly inside an `async` function body. Keeping
    /// the `for case let ... in enumerator` loop in a plain sync function
    /// and calling it from the `async` method above sidesteps that —
    /// the blocking work still happens off the caller's thread because this
    /// whole type is an actor.
    private nonisolated static func walk(_ documentsURL: URL) -> (fileCount: Int, totalBytes: Int64) {
        // No `.skipsHiddenFiles`: an evicted document exists on disk only as a
        // hidden ".<name>.icloud" placeholder — skipping hidden files makes
        // evicted documents vanish from the count (a fully-evicted container
        // would report "0 documents"). Placeholders count as documents; their
        // on-disk placeholder size stands in for the real size, so totalBytes
        // reflects local footprint, not logical size. Other hidden files
        // (.DS_Store etc.) are still skipped by name.
        guard let enumerator = FileManager.default.enumerator(
            at: documentsURL,
            includingPropertiesForKeys: [.fileSizeKey, .isDirectoryKey],
            options: []
        ) else { return (0, 0) }

        var fileCount = 0
        var totalBytes: Int64 = 0

        for case let fileURL as URL in enumerator {
            guard let values = try? fileURL.resourceValues(forKeys: [.isDirectoryKey, .fileSizeKey]),
                  values.isDirectory == false else { continue }
            if fileURL.lastPathComponent.hasPrefix("."), fileURL.pathExtension != "icloud" { continue }
            fileCount += 1
            totalBytes += Int64(values.fileSize ?? 0)
        }

        return (fileCount, totalBytes)
    }
}
