import Foundation
import Yams

/// A `Sendable` snapshot of a parsed YAML value. Mappings keep document order; this is a
/// read-only view — the engine never serializes through it (see `FrontmatterDocument`).
public enum YAMLValue: Sendable, Equatable {
    case null
    case bool(Bool)
    case int(Int)
    case double(Double)
    case string(String)
    case date(Date)
    case sequence([YAMLValue])
    case mapping([Pair])

    public struct Pair: Sendable, Equatable {
        public let key: YAMLValue
        public let value: YAMLValue

        public init(key: YAMLValue, value: YAMLValue) {
            self.key = key
            self.value = value
        }
    }
}

extension YAMLValue {
    init(_ node: Node, resolver: Resolver = .default) {
        switch node {
        case let .scalar(scalar):
            self = YAMLValue(scalar: scalar, tag: resolver.resolveTag(of: node))
        case let .sequence(sequence):
            self = .sequence(sequence.map { YAMLValue($0, resolver: resolver) })
        case let .mapping(mapping):
            self = .mapping(mapping.map {
                Pair(key: YAMLValue($0.key, resolver: resolver), value: YAMLValue($0.value, resolver: resolver))
            })
        case .alias:
            self = .null
        }
    }

    private init(scalar: Node.Scalar, tag: Tag.Name) {
        switch tag {
        case .null:
            self = .null
        case .bool:
            self = Bool.construct(from: scalar).map(YAMLValue.bool) ?? .string(scalar.string)
        case .int:
            self = Int.construct(from: scalar).map(YAMLValue.int)
                ?? Double.construct(from: scalar).map(YAMLValue.double)
                ?? .string(scalar.string)
        case .float:
            self = Double.construct(from: scalar).map(YAMLValue.double) ?? .string(scalar.string)
        case .timestamp:
            self = Date.construct(from: scalar).map(YAMLValue.date) ?? .string(scalar.string)
        default:
            self = .string(scalar.string)
        }
    }
}

extension YAMLValue: CustomStringConvertible {
    /// Diagnostic rendering only — never used to write files.
    public var description: String {
        switch self {
        case .null: "null"
        case let .bool(value): value ? "true" : "false"
        case let .int(value): String(value)
        case let .double(value): String(value)
        case let .string(value): value
        case let .date(value): value.formatted(.iso8601)
        case let .sequence(values): "[" + values.map(\.description).joined(separator: ", ") + "]"
        case let .mapping(pairs):
            "{" + pairs.map { "\($0.key.description): \($0.value.description)" }.joined(separator: ", ") + "}"
        }
    }
}
