//
//  XPCConvertible.swift
//  kordophone2
//
//  Created by James Magahern on 8/24/25.
//

import Foundation
import XPC

protocol XPCConvertible
{
    static func fromXPC(_ value: xpc_object_t) -> Self?
}

extension String: XPCConvertible
{
    static func fromXPC(_ value: xpc_object_t) -> String? {
        guard xpc_get_type(value) == XPC_TYPE_STRING, let cstr = xpc_string_get_string_ptr(value) else {
            return nil
        }
        
        return String(cString: cstr)
    }
}

extension Int: XPCConvertible
{
    static func fromXPC(_ value: xpc_object_t) -> Int? {
        switch xpc_get_type(value) {
        case XPC_TYPE_INT64:
            return Int(xpc_int64_get_value(value))
        case XPC_TYPE_UINT64:
            return Int(xpc_uint64_get_value(value))
        case XPC_TYPE_STRING:
            if let cstr = xpc_string_get_string_ptr(value) {
                return Int(String(cString: cstr))
            }
        default:
            return nil
        }
        
        return nil
    }
}

extension Date: XPCConvertible
{
    static func fromXPC(_ value: xpc_object_t) -> Date? {
        // Accept seconds since epoch as int/uint or string
        if let seconds: Int = Int.fromXPC(value) {
            return Date(timeIntervalSince1970: TimeInterval(seconds))
        }
        
        return nil
    }
}

extension Array: XPCConvertible where Element: XPCConvertible
{
    static func fromXPC(_ value: xpc_object_t) -> [Element]? {
        guard xpc_get_type(value) == XPC_TYPE_ARRAY else {
            return nil
        }
        
        var result: [Element] = []
        xpc_array_apply(value) { _, item in
            if let element = Element.fromXPC(item) {
                result.append(element)
            }
            
            return true
        }
        
        return result
    }
}

extension Bool: XPCConvertible
{
    static func fromXPC(_ value: xpc_object_t) -> Bool? {
        if xpc_get_type(value) == XPC_TYPE_BOOL {
            return xpc_bool_get_value(value)
        }

        if xpc_get_type(value) == XPC_TYPE_STRING {
            guard let cstr = xpc_string_get_string_ptr(value) else { return nil }
            return strcmp(cstr, "true") == 0
        }

        return nil
    }
}

extension xpc_object_t
{
    func getObject(_ key: String) -> xpc_object_t? {
        var raw: xpc_object_t?
        key.withCString { cKey in
            raw = xpc_dictionary_get_value(self, cKey)
        }

        return raw
    }

    func get<T: XPCConvertible>(_ key: String) -> T? {
        var raw: xpc_object_t?
        key.withCString { cKey in
            raw = xpc_dictionary_get_value(self, cKey)
        }
        
        guard let value = raw else { return nil }
        return T.fromXPC(value)
    }

    subscript<T: XPCConvertible>(key: String) -> T? { return get(key) }

    var isDictionary: Bool { xpc_get_type(self) == XPC_TYPE_DICTIONARY }
    var isArray: Bool { xpc_get_type(self) == XPC_TYPE_ARRAY }
}

// MARK: - Dictionary wrapper

struct XPCDictionary
{
    let raw: xpc_object_t

    init?(_ value: xpc_object_t) {
        guard xpc_get_type(value) == XPC_TYPE_DICTIONARY else { return nil }
        self.raw = value
    }

    func object(_ key: String) -> xpc_object_t? {
        var rawValue: xpc_object_t?
        key.withCString { cKey in
            rawValue = xpc_dictionary_get_value(raw, cKey)
        }
        return rawValue
    }

    func get<T: XPCConvertible>(_ key: String) -> T? {
        guard let value = object(key) else { return nil }
        return T.fromXPC(value)
    }

    subscript<T: XPCConvertible>(_ key: String) -> T? { return get(key) }
}

extension XPCDictionary
{
    static func wrap(_ value: xpc_object_t) -> XPCDictionary? {
        return XPCDictionary(value)
    }
}
