import Foundation
import Yams

/// A value being written into frontmatter. The engine emits these as minimal YAML scalars —
/// it never round-trips a whole document through a YAML emitter.
public enum FrontmatterValue: Sendable, Equatable {
    case string(String)
    case int(Int)
    case double(Double)
    case bool(Bool)
    case date(Date)
    /// Verbatim YAML supplied by the caller (flow collections, block scalars). Not validated.
    case raw(String)

    /// The scalar text placed after `key: `.
    public var yamlText: String {
        switch self {
        case let .string(value): Self.emitScalar(value)
        case let .int(value): String(value)
        case let .double(value): Self.emitDouble(value)
        case let .bool(value): value ? "true" : "false"
        case let .date(value): value.formatted(.iso8601)
        case let .raw(value): value
        }
    }

    var parsedValue: YAMLValue {
        switch self {
        case let .string(value): .string(value)
        case let .int(value): .int(value)
        case let .double(value): .double(value)
        case let .bool(value): .bool(value)
        case let .date(value): .date(value)
        case let .raw(value):
            if let node = try? Yams.compose(yaml: value) { YAMLValue(node) } else { .string(value) }
        }
    }
}

extension FrontmatterValue {
    private static func emitDouble(_ value: Double) -> String {
        if value.isNaN { return ".nan" }
        if value.isInfinite { return value < 0 ? "-.inf" : ".inf" }
        if value == value.rounded(), abs(value) < 1e15 { return String(Int64(value)) }
        return String(value)
    }

    /// Plain when YAML round-trips it back to the same string, double-quoted otherwise.
    /// The round-trip check is the authority: it catches leading/trailing space, comment
    /// introducers, and anything that would resolve to a bool/int/float/null/timestamp.
    static func emitScalar(_ value: String) -> String {
        guard !value.isEmpty, !value.unicodeScalars.contains(where: { $0 == "\n" || $0 == "\r" }) else {
            return quoted(value)
        }
        guard let node = try? Yams.compose(yaml: "v: \(value)"),
              let mapping = node.mapping,
              let scalar = mapping["v"],
              Resolver.default.resolveTag(of: scalar) == .str,
              scalar.scalar?.string == value
        else { return quoted(value) }
        return value
    }

    /// The double-quoted form unconditionally — what a value inside a **flow collection** takes
    /// (`FrontmatterDocument.setStyleValue`), where `emitScalar`'s block-context round trip is not
    /// the right question: `,`, `]`, `}` and `: ` end a plain scalar in flow context and not on a
    /// line of its own.
    static func emitQuoted(_ value: String) -> String { quoted(value) }

    private static func quoted(_ value: String) -> String {
        var out = "\""
        for scalar in value.unicodeScalars {
            switch scalar {
            case "\\": out += "\\\\"
            case "\"": out += "\\\""
            case "\n": out += "\\n"
            case "\r": out += "\\r"
            case "\t": out += "\\t"
            default:
                if scalar.value < 0x20 || scalar.value == 0x7F {
                    out += String(format: "\\x%02x", scalar.value)
                } else {
                    out.unicodeScalars.append(scalar)
                }
            }
        }
        return out + "\""
    }
}
