// Copyright © 2022 Olo Inc. All rights reserved.
// This software is made available under the Olo Pay SDK License (See LICENSE.md file)
//
//  DictionaryExtensions.swift
//  OlopaysdkReactNative
//
//  Created by Honz Williams on 5/7/25.
//

import Foundation
import OloPaySDK

extension Dictionary where Key == String {
    func getOrThrow<T>(_ key: String, defaultValue: T) throws -> T {
        do {
            return try getOrThrow(key)
        } catch OloError.MissingKeyError {
            return defaultValue
        } catch OloError.NullValueError {
            return defaultValue
        }
    }

    func getOrThrow<T>(_ key: String) throws -> T {
        guard keyExists(key) else {
            throw OloError.MissingKeyError
        }
        
        guard keyExists(key) else {
            throw OloError.NullValueError
        }
        
        guard let value: T = get(key) else {
            throw OloError.UnexpectedTypeError
        }
        
        return value
    }
    
    func get<T>(_ key: String) -> T? {
        guard let value = self[key] as? T else {
            return nil
        }
        
        return value
    }
    
    func keyExists(_ key: String) -> Bool {
        return self[key] != nil
    }
    
    func getOrReject<T>(
         for key: String,
         withDefault defaultValue: T,
         baseError: String,
         reject: @escaping RCTPromiseRejectBlock
     ) throws -> T {
         do {
             return try self.getOrThrow(key, defaultValue: defaultValue)
         } catch let error {
             reject(
               ErrorCodes.InvalidParameter,
               "\(baseError): Value for '\(key)' is not of type \(String(describing: T.self))",
               nil
             )
                         
             throw error
         }
     }
     
     func getOrReject<T>(
         for key: String,
         baseError: String,
         reject: @escaping RCTPromiseRejectBlock
     ) throws -> T {
         var errorMessage: String = ""
         var errorCode: String = ""
         var oloError: OloError? = nil
         
         do {
             return try self.getOrThrow(key)
         } catch let error as OloError where error == .MissingKeyError {
             errorMessage = "\(baseError): Missing parameter '\(key)'"
             errorCode = ErrorCodes.MissingParameter
             oloError = error
         } catch let error as OloError where error == .NullValueError {
             errorMessage = "\(baseError): Missing parameter '\(key)'"
             errorCode = ErrorCodes.MissingParameter
             oloError = error
         } catch { // OloError.UnexpectedTypeError
             errorMessage = "\(baseError): Value for '\(key)' is not of type \(String(describing: T.self))"
             errorCode = ErrorCodes.InvalidParameter
             oloError = OloError.UnexpectedTypeError
         }
         
         reject(
             errorCode,
             errorMessage,
             nil
         )
         
         throw oloError!
     }
    
    func getStringOrReject(
        for key: String,
        withDefault defaultValue: String,
        baseError: String,
        acceptEmptyValue: Bool,
        reject: @escaping RCTPromiseRejectBlock
    ) throws -> String {
        do {
            var value: String = try self.getOrThrow(key, defaultValue: defaultValue)
            value = value.trim()
            
            if !acceptEmptyValue && value.isEmpty {
                value = defaultValue
            }
            
            return value
        } catch let error {
            reject(
                ErrorCodes.InvalidParameter,
                "\(baseError): Value for '\(key)' is not of type String",
                nil
            )
            
            throw error
        }
    }
    
    func getStringOrReject(
        for key: String,
        baseError: String,
        acceptEmptyValue: Bool,
        reject: @escaping RCTPromiseRejectBlock
    ) throws -> String {
        var value: String = try self.getOrReject(
            for: key,
            baseError: baseError,
            reject: reject
        )
        
        value = value.trim()
        
        if !acceptEmptyValue && value.isEmpty {
            reject(
                ErrorCodes.InvalidParameter,
                "\(baseError): Value for '\(key)' cannot be empty",
                nil
            )
            throw OloError.EmptyValueError
        }
        
        return value
    }
}
