#!/usr/bin/env swift

// import-existing-names.swift
//
// Walk the local CNContactStore, extract (handle, givenName) pairs for every
// contact that has a phone number or email, and POST them to quinn.api so the
// server can seed `clients.display_name` for handles it already tracks.
//
// This runs ONCE before the first real contact-render write — without it, the
// renderer emits `[LAX] {firstname}` for contacts whose human name Quinn had
// already saved manually, which would effectively erase those names on first
// write.
//
// Usage:
//   export QUINN_API_URL="http://black.local:3100"
//   export QUINN_API_SERVICE_TOKEN="..."
//   swift scripts/import-existing-names.swift

import Contacts
import Foundation

struct SeedEntry: Codable {
    let handle: String
    let channel: String
    let displayName: String
}

struct SeedBody: Codable {
    let items: [SeedEntry]
}

enum SeedError: Error {
    case accessDenied
    case missingEnv(String)
    case http(Int, String)
    case fetch(Error)
}

@MainActor
func main() async throws {
    guard let base = ProcessInfo.processInfo.environment["QUINN_API_URL"],
          let url = URL(string: base) else {
        throw SeedError.missingEnv("QUINN_API_URL")
    }
    guard let token = ProcessInfo.processInfo.environment["QUINN_API_SERVICE_TOKEN"], !token.isEmpty else {
        throw SeedError.missingEnv("QUINN_API_SERVICE_TOKEN")
    }

    let store = CNContactStore()
    let granted: Bool = try await withCheckedThrowingContinuation { cont in
        store.requestAccess(for: .contacts) { ok, err in
            if let err = err { cont.resume(throwing: err); return }
            cont.resume(returning: ok)
        }
    }
    guard granted else { throw SeedError.accessDenied }

    let keys: [CNKeyDescriptor] = [
        CNContactGivenNameKey as CNKeyDescriptor,
        CNContactPhoneNumbersKey as CNKeyDescriptor,
        CNContactEmailAddressesKey as CNKeyDescriptor,
    ]
    let request = CNContactFetchRequest(keysToFetch: keys)
    request.unifyResults = true

    var items: [SeedEntry] = []
    do {
        try store.enumerateContacts(with: request) { contact, _ in
            let given = contact.givenName.trimmingCharacters(in: .whitespaces)
            guard !given.isEmpty else { return }
            // Skip our own managed contacts.
            if given.hasPrefix("[") { return }

            for phone in contact.phoneNumbers {
                let raw = phone.value.stringValue.filter { "+0123456789".contains($0) }
                if raw.isEmpty { continue }
                items.append(SeedEntry(handle: raw, channel: "imessage", displayName: given))
            }
            for email in contact.emailAddresses {
                let raw = (email.value as String).trimmingCharacters(in: .whitespaces)
                if raw.isEmpty { continue }
                items.append(SeedEntry(handle: raw, channel: "email", displayName: given))
            }
        }
    } catch {
        throw SeedError.fetch(error)
    }

    print("import-existing-names: collected \(items.count) handle→name pairs")

    let body = SeedBody(items: items)
    let endpoint = url.appendingPathComponent("m/contact-render/seed-names")
    var req = URLRequest(url: endpoint)
    req.httpMethod = "POST"
    req.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
    req.setValue("application/json", forHTTPHeaderField: "Content-Type")
    req.httpBody = try JSONEncoder().encode(body)

    let (data, response) = try await URLSession.shared.data(for: req)
    guard let http = response as? HTTPURLResponse else {
        throw SeedError.http(0, "no http response")
    }
    guard (200..<300).contains(http.statusCode) else {
        throw SeedError.http(http.statusCode, String(data: data, encoding: .utf8) ?? "")
    }
    print("import-existing-names: seeded \(items.count) names — server \(http.statusCode)")
}

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