import Foundation

#if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
import FoundationModels
#endif

#if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
@available(macOS 26.0, *)
final class AdapterBox {
    let value: SystemLanguageModel.Adapter

    init(_ value: SystemLanguageModel.Adapter) {
        self.value = value
    }
}

@available(macOS 26.0, *)
func systemModelAvailabilityCode(for model: SystemLanguageModel) -> Int32 {
    switch model.availability {
    case .available:
        return 0
    case .unavailable(let reason):
        switch reason {
        case .deviceNotEligible:
            return 1
        case .appleIntelligenceNotEnabled:
            return 2
        case .modelNotReady:
            return 3
        @unknown default:
            return 4
        }
    @unknown default:
        return 4
    }
}

@available(macOS 26.0, *)
func systemModel(from ptr: UnsafeMutableRawPointer?) -> SystemLanguageModel {
    guard let ptr else {
        return .default
    }
    return Unmanaged<SystemLanguageModel>.fromOpaque(ptr).takeUnretainedValue()
}

@available(macOS 26.0, *)
func adapter(from ptr: UnsafeMutableRawPointer) -> SystemLanguageModel.Adapter {
    Unmanaged<AdapterBox>.fromOpaque(ptr).takeUnretainedValue().value
}

@available(macOS 26.0, *)
func modelUseCase(from rawValue: Int32) -> SystemLanguageModel.UseCase {
    switch rawValue {
    case 1:
        return .contentTagging
    default:
        return .general
    }
}

@available(macOS 26.0, *)
func modelGuardrails(from rawValue: Int32) -> SystemLanguageModel.Guardrails {
    switch rawValue {
    case 1:
        return .permissiveContentTransformations
    default:
        return .default
    }
}

@available(macOS 26.0, *)
func jsonSafeObject(_ value: Any) -> Any {
    switch value {
    case let dict as [String: Any]:
        return dict.mapValues(jsonSafeObject)
    case let array as [Any]:
        return array.map(jsonSafeObject)
    case let number as NSNumber:
        return number
    case let string as String:
        return string
    case let date as Date:
        return ISO8601DateFormatter().string(from: date)
    case _ as NSNull:
        return NSNull()
    default:
        return String(describing: value)
    }
}
#endif

@_cdecl("fm_system_model_create_default")
public func fm_system_model_create_default() -> UnsafeMutableRawPointer? {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        return Unmanaged.passRetained(SystemLanguageModel.default).toOpaque()
    }
    #endif
    return nil
}

@_cdecl("fm_system_model_create")
public func fm_system_model_create(
    _ useCase: Int32,
    _ guardrails: Int32,
    _ errorOut: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> UnsafeMutableRawPointer? {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        let model = SystemLanguageModel(
            useCase: modelUseCase(from: useCase),
            guardrails: modelGuardrails(from: guardrails)
        )
        return Unmanaged.passRetained(model).toOpaque()
    }
    #endif
    writeErrorOut(errorOut, "FoundationModels requires macOS 26.0 or newer")
    return nil
}

@_cdecl("fm_system_model_create_with_adapter")
public func fm_system_model_create_with_adapter(
    _ adapterPtr: UnsafeMutableRawPointer?,
    _ guardrails: Int32,
    _ errorOut: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> UnsafeMutableRawPointer? {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        guard let adapterPtr else {
            writeErrorOut(errorOut, "adapter pointer must not be null")
            return nil
        }
        let model = SystemLanguageModel(
            adapter: adapter(from: adapterPtr),
            guardrails: modelGuardrails(from: guardrails)
        )
        return Unmanaged.passRetained(model).toOpaque()
    }
    #endif
    writeErrorOut(errorOut, "FoundationModels requires macOS 26.0 or newer")
    return nil
}

@_cdecl("fm_system_model_availability_code_for")
public func fm_system_model_availability_code_for(_ modelPtr: UnsafeMutableRawPointer?) -> Int32 {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        return systemModelAvailabilityCode(for: systemModel(from: modelPtr))
    }
    #endif
    return -1
}

@_cdecl("fm_system_model_supported_languages_json")
public func fm_system_model_supported_languages_json(
    _ modelPtr: UnsafeMutableRawPointer?
) -> UnsafeMutablePointer<CChar>? {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        let model = systemModel(from: modelPtr)
        let languages = model.supportedLanguages
            .map(\.maximalIdentifier)
            .sorted()
        return ffiString((try? encodeBridge(languages)) ?? "[]")
    }
    #endif
    return ffiString("[]")
}

@_cdecl("fm_system_model_supports_locale")
public func fm_system_model_supports_locale(
    _ modelPtr: UnsafeMutableRawPointer?,
    _ localeIdentifier: UnsafePointer<CChar>?
) -> Bool {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        let model = systemModel(from: modelPtr)
        let locale = localeIdentifier.map { Locale(identifier: String(cString: $0)) } ?? Locale.current
        return model.supportsLocale(locale)
    }
    #endif
    return false
}

@_cdecl("fm_adapter_create_from_file")
public func fm_adapter_create_from_file(
    _ filePath: UnsafePointer<CChar>,
    _ errorOut: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> UnsafeMutableRawPointer? {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        do {
            let raw = String(cString: filePath)
            let url = raw.hasPrefix("file:") ? URL(string: raw)! : URL(fileURLWithPath: raw)
            let adapter = try SystemLanguageModel.Adapter(fileURL: url)
            return Unmanaged.passRetained(AdapterBox(adapter)).toOpaque()
        } catch {
            writeErrorOut(errorOut, error.localizedDescription)
            return nil
        }
    }
    #endif
    writeErrorOut(errorOut, "FoundationModels requires macOS 26.0 or newer")
    return nil
}

@_cdecl("fm_adapter_create_from_name")
public func fm_adapter_create_from_name(
    _ name: UnsafePointer<CChar>,
    _ errorOut: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> UnsafeMutableRawPointer? {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        do {
            let adapter = try SystemLanguageModel.Adapter(name: String(cString: name))
            return Unmanaged.passRetained(AdapterBox(adapter)).toOpaque()
        } catch {
            writeErrorOut(errorOut, error.localizedDescription)
            return nil
        }
    }
    #endif
    writeErrorOut(errorOut, "FoundationModels requires macOS 26.0 or newer")
    return nil
}

@_cdecl("fm_adapter_compile")
public func fm_adapter_compile(
    _ adapterPtr: UnsafeMutableRawPointer,
    _ context: UnsafeMutableRawPointer?,
    _ callback: @convention(c) (
        UnsafeMutableRawPointer?,
        UnsafeMutablePointer<CChar>?,
        UnsafeMutablePointer<CChar>?,
        Int32
    ) -> Void
) {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        let adapter = adapter(from: adapterPtr)
        Task.detached {
            do {
                try await adapter.compile()
                callback(context, ffiString("ok"), nil, FM_OK)
            } catch {
                let (code, message) = mapError(error)
                callback(context, nil, ffiString(message), code)
            }
        }
        return
    }
    #endif
    callback(context, nil, ffiString("FoundationModels requires macOS 26.0 or newer"), FM_MODEL_UNAVAILABLE)
}

@_cdecl("fm_adapter_compatible_identifiers_json")
public func fm_adapter_compatible_identifiers_json(
    _ name: UnsafePointer<CChar>
) -> UnsafeMutablePointer<CChar>? {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        let identifiers = SystemLanguageModel.Adapter.compatibleAdapterIdentifiers(name: String(cString: name))
        return ffiString((try? encodeBridge(identifiers)) ?? "[]")
    }
    #endif
    return ffiString("[]")
}

@_cdecl("fm_adapter_remove_obsolete")
public func fm_adapter_remove_obsolete(
    _ errorOut: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        do {
            try SystemLanguageModel.Adapter.removeObsoleteAdapters()
            return FM_OK
        } catch {
            let (code, message) = mapError(error)
            writeErrorOut(errorOut, message)
            return code
        }
    }
    #endif
    writeErrorOut(errorOut, "FoundationModels requires macOS 26.0 or newer")
    return FM_MODEL_UNAVAILABLE
}

@_cdecl("fm_adapter_metadata_json")
public func fm_adapter_metadata_json(_ adapterPtr: UnsafeMutableRawPointer) -> UnsafeMutablePointer<CChar>? {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        let metadata = jsonSafeObject(adapter(from: adapterPtr).creatorDefinedMetadata)
        if JSONSerialization.isValidJSONObject(metadata),
           let data = try? JSONSerialization.data(withJSONObject: metadata, options: []),
           let string = String(data: data, encoding: .utf8) {
            return ffiString(string)
        }
        return ffiString("{}")
    }
    #endif
    return ffiString("{}")
}
