#!/usr/bin/env swift

// restore-contacts.swift
//
// Restore the CNContactStore from a full .vcf backup produced by ContactsBackup.swift.
//
// Usage: swift scripts/restore-contacts.swift <path-to-full-*.vcf>
//
// The script asks for Contacts access on first invocation — grant it through the
// prompt, or pre-grant Terminal (or your shell host) under Privacy & Security →
// Contacts. The store is NOT wiped prior to import; new identifiers are issued
// for all restored contacts. To do a clean restore after a bad run, first delete
// contacts whose familyName matches the lilith-sync emoji-only regex via Contacts.app
// before invoking this, or use the per-row rollback JSONL instead.

import Contacts
import Foundation

enum RestoreError: Error {
    case missingArgument
    case readFailed(Error)
    case deserializationFailed(Error)
    case accessDenied
    case saveFailed(Error)
}

@MainActor
func main() async throws {
    let args = CommandLine.arguments
    guard args.count >= 2 else {
        FileHandle.standardError.write(Data("usage: restore-contacts.swift <path-to-full-*.vcf>\n".utf8))
        throw RestoreError.missingArgument
    }

    let path = args[1]
    let url = URL(fileURLWithPath: path)

    let data: Data
    do {
        data = try Data(contentsOf: url)
    } catch {
        throw RestoreError.readFailed(error)
    }

    let contacts: [CNContact]
    do {
        let parsed = try CNContactVCardSerialization.contacts(with: data)
        contacts = parsed
    } catch {
        throw RestoreError.deserializationFailed(error)
    }

    print("restore-contacts: parsed \(contacts.count) contacts from \(url.lastPathComponent)")

    let store = CNContactStore()
    let granted: Bool
    do {
        granted = try await store.requestAccess(for: .contacts)
    } catch {
        throw RestoreError.accessDenied
    }
    guard granted else { throw RestoreError.accessDenied }

    let save = CNSaveRequest()
    for contact in contacts {
        guard let mutable = contact.mutableCopy() as? CNMutableContact else { continue }
        save.add(mutable, toContainerWithIdentifier: nil)
    }

    do {
        try store.execute(save)
    } catch {
        throw RestoreError.saveFailed(error)
    }

    print("restore-contacts: added \(contacts.count) contacts")
}

private extension CNContactStore {
    func requestAccess(for entity: CNEntityType) async throws -> Bool {
        try await withCheckedThrowingContinuation { cont in
            self.requestAccess(for: entity) { granted, err in
                if let err = err { cont.resume(throwing: err); return }
                cont.resume(returning: granted)
            }
        }
    }
}

do {
    try await main()
} catch {
    FileHandle.standardError.write(Data("restore-contacts: \(error)\n".utf8))
    exit(1)
}
