import Foundation

/// JSON response from `calcpad_eval_line` — wraps a single `CalcResult`.
struct FFIResponse: Decodable {
    let schemaVersion: String
    let result: FFICalcResult

    enum CodingKeys: String, CodingKey {
        case schemaVersion = "schema_version"
        case result
    }
}

/// JSON response from `calcpad_eval_sheet` — wraps an array of `CalcResult`.
struct FFISheetResponse: Decodable {
    let schemaVersion: String
    let results: [FFICalcResult]

    enum CodingKeys: String, CodingKey {
        case schemaVersion = "schema_version"
        case results
    }
}

/// A complete calculation result returned by the Rust engine.
struct FFICalcResult: Decodable {
    let value: FFICalcValue
    let metadata: FFIResultMetadata
}

/// Metadata attached to every evaluation result.
struct FFIResultMetadata: Decodable {
    let span: FFISpan
    let resultType: String
    let display: String
    let rawValue: Double?

    enum CodingKeys: String, CodingKey {
        case span
        case resultType = "result_type"
        case display
        case rawValue = "raw_value"
    }
}

/// Source span (byte offsets).
struct FFISpan: Decodable {
    let start: Int
    let end: Int
}

/// Tagged union of possible calculation values.
/// Rust serializes with `#[serde(tag = "kind")]`.
enum FFICalcValue: Decodable {
    case number(value: Double)
    case unitValue(value: Double, unit: String)
    case currencyValue(amount: Double, currency: String)
    case dateTime(date: String)
    case timeDelta(days: Int64, description: String)
    case boolean(value: Bool)
    case error(message: String)

    enum CodingKeys: String, CodingKey {
        case kind
        case value, unit, amount, currency, date, days, description, message, span
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let kind = try container.decode(String.self, forKey: .kind)

        switch kind {
        case "Number":
            let value = try container.decode(Double.self, forKey: .value)
            self = .number(value: value)
        case "UnitValue":
            let value = try container.decode(Double.self, forKey: .value)
            let unit = try container.decode(String.self, forKey: .unit)
            self = .unitValue(value: value, unit: unit)
        case "CurrencyValue":
            let amount = try container.decode(Double.self, forKey: .amount)
            let currency = try container.decode(String.self, forKey: .currency)
            self = .currencyValue(amount: amount, currency: currency)
        case "DateTime":
            let date = try container.decode(String.self, forKey: .date)
            self = .dateTime(date: date)
        case "TimeDelta":
            let days = try container.decode(Int64.self, forKey: .days)
            let desc = try container.decode(String.self, forKey: .description)
            self = .timeDelta(days: days, description: desc)
        case "Boolean":
            let value = try container.decode(Bool.self, forKey: .value)
            self = .boolean(value: value)
        case "Error":
            let message = try container.decode(String.self, forKey: .message)
            self = .error(message: message)
        default:
            self = .error(message: "Unknown result kind: \(kind)")
        }
    }

    /// Whether this value represents an error from the Rust engine.
    var isError: Bool {
        if case .error = self { return true }
        return false
    }
}
