//
//  ScreenChangePayloadParser.swift
//  DxaReactNative
//
//  Created by Dawid Prajzner on 11/04/2025.
//  Copyright © 2025 Qualtrics. All rights reserved.
//

import QualtricsDXA

enum ParsingError: Error {
    case invalidArgument(argumentName: String, reason: String)
    case missingField(fieldName: String)
    case invalidField(fieldName: String, reason: String)
    case invalidScreenHierarchyElement(index: Int, reason: String)
}

internal func parseScreenChangePayload(from screenChangePayload: NSDictionary?) -> ([ScreenHierarchyElement], ScreenChangeType, ScreenDetectionType)? {
    do {
        guard let payload = screenChangePayload else {
            throw ParsingError.invalidArgument(argumentName: "screenChangePayload", reason: "Payload cannot be null.")
        }

        let screenHierarchy = try parseScreenHierarchy(from: payload)
        let screenChangeType = try parseChangeType(from: payload)
        let screenDetectionType = try parseDetectionType(from: payload)

        return (screenHierarchy, screenChangeType, screenDetectionType)
    } catch {
        #if DEBUG
            print("Failed to parse screenChangePayload: \(error)")
        #endif
    }
    
    return nil
}

private func parseScreenHierarchy(from screenChangePayload: NSDictionary) throws -> [ScreenHierarchyElement] {
    guard let rawScreenHierarchy = screenChangePayload.value(forKey: "screenHierarchy") as? NSArray else {
        throw ParsingError.missingField(fieldName: "screenHierarchy")
    }

    guard rawScreenHierarchy.count > 0 else {
        throw ParsingError.invalidField(fieldName: "screenHierarchy", reason: "Array cannot be empty.")
    }

    var parsedScreenHierarchy: [ScreenHierarchyElement] = []

    for (i, rawElement) in rawScreenHierarchy.enumerated() {
        guard let element = rawElement as? NSDictionary else {
            throw ParsingError.invalidScreenHierarchyElement(index: i, reason: "Element is null or not an object.")
        }

        guard let screenName = element.getNonEmptyString(forKey: "screenName") else {
            throw ParsingError.invalidScreenHierarchyElement(index: i, reason: "Element is missing field 'screenName' or it's empty.")
        }
    
        let screenType = element.getNonEmptyStringOrDefault(forKey: "screenType", defaultValue: "screen")
     
        parsedScreenHierarchy.append(ScreenHierarchyElement(screenName: screenName, screenType: screenType))
    }

    return parsedScreenHierarchy
}

private func parseChangeType(from screenChangePayload: NSDictionary) throws -> ScreenChangeType {
    let changeType = try screenChangePayload.getNonEmptyStringOrThrow(forKey: "changeType")

    switch changeType {
    case "appear":
        return .appear

    case "disappear":
        return .disappear

    default:
        throw ParsingError.invalidField(fieldName: "changeType", reason: "Expected 'appear' or 'disappear' but received '\(changeType)'.")
    }
}

private func parseDetectionType(from screenChangePayload: NSDictionary) throws -> ScreenDetectionType {
    let detectionType = try screenChangePayload.getNonEmptyStringOrThrow(forKey: "detectionType")

    switch detectionType {
    case "auto":
        return .auto

    case "manual":
        return .manual

    default:
        throw ParsingError.invalidField(fieldName: "detectionType", reason: "Expected 'auto' or 'manual' but received '\(detectionType)'.")
    }
}

private extension NSDictionary {
    func getNonEmptyString(forKey key: String) -> String? {
        guard let value = self.value(forKey: key) as? String, !value.isEmpty else {
            return nil
        }
        return value
    }
  
    func getNonEmptyStringOrDefault(forKey key: String, defaultValue: String) -> String {
        guard let value = self.getNonEmptyString(forKey: key) else {
            return defaultValue
        }
        return value
    }

    func getNonEmptyStringOrThrow(forKey key: String) throws -> String {
        guard let value = self.value(forKey: key) as? String else {
            throw ParsingError.missingField(fieldName: key)
        }
    
        if value.isEmpty {
            throw ParsingError.invalidField(fieldName: key, reason: "Expected non-empty string but received an empty string.")
        }

        return value
    }
}
