import Foundation
import Testing
@testable import Kanban

// MARK: - Fixtures

private enum Fixture {
    static let rich = """
        ---
        # board settings, hand-written
        schema: 1
        title: My Board

        project: lanework       # agent overlay
        sphere: work
        labels: [a, b, c]
        template: {order: 3}
        notes: |
          line one
            indented two
        folded: >
          wrapped
          text
        created: 2026-07-26T16:41:38Z
        modified-by: claude
        tagged: !!str 42
        "quoted key": yes
        # trailing note
        ---
        Body text.

        More body — with *markdown*.
        """

    static let minimal = """
        ---
        schema: 1
        order: 1024
        title: Thing
        ---
        Body
        """

    /// Verbatim copy of a real card written by an agent.
    static let realCard = """
        ---
        schema: 1
        title: Build the frontmatter engine with byte-perfect round-trip
        order: 3072
        created: 2026-07-26T16:41:38Z
        modified: 2026-07-26T19:06:55Z
        modified-by: claude
        source: DESIGN/01-storage-format.md
        labels: [m1-storage-read]
        ---
        Build the Frontmatter component.
        **Design constraints:**
        - index.md is YAML frontmatter between `---` delimiters, then a Markdown body.

        """

    /// A flow mapping whose continuation sits at column 0 — the shape that would fool a naive
    /// "any `key:` at column 0 starts a field" scanner.
    static let unindentedFlow = """
        ---
        flow: {a: 1,
        b: 2}
        last: x
        ---
        body
        """

    /// A hand-edited file that says `title` twice — strict YAML would reject it; the editor reads
    /// the last one and keeps both on disk (01-storage-format.md § Frontmatter).
    static let duplicated = """
        ---
        schema: 1
        title: First
        order: 1024
        title: Second
        ---
        body

        """
}

private func parseError(_ text: String) -> FrontmatterError? {
    do {
        _ = try FrontmatterDocument.parse(text)
        return nil
    } catch {
        return error
    }
}

// MARK: - Round-trip

struct FrontmatterRoundTripTests {
    @Test(arguments: [
        Fixture.rich,
        Fixture.minimal,
        Fixture.realCard,
        Fixture.unindentedFlow,
        // body with a trailing newline
        "---\nschema: 1\n---\nbody\n",
        // body with several trailing newlines
        "---\nschema: 1\n---\nbody\n\n\n",
        // no body at all, closing delimiter unterminated
        "---\nschema: 1\n---",
        // no body, closing delimiter terminated
        "---\nschema: 1\n---\n",
        // empty frontmatter mapping
        "---\n---\nbody\n",
        // frontmatter that is only blank lines
        "---\n\n\n---\nbody\n",
        // frontmatter that is only comments
        "---\n# nothing to see\n---\nbody\n",
        // explicit empty flow mapping
        "---\n{}\n---\nbody\n",
        // top-level flow mapping
        "---\n{schema: 1, order: 5}\n---\nbody\n",
        // odd key order, blank lines between entries
        "---\nzzz: last\n\norder: 5\n\n\nschema: 1\n---\nbody\n",
        // block scalar keep/strip indicators
        "---\nkeep: |+\n  text\n\nstrip: |-\n  text\n---\nbody\n",
        // anchors and aliases
        "---\nbase: &b\n  x: 1\ncopy: *b\n---\nbody\n",
        // quoted values with escapes and embedded delimiters
        "---\ntitle: \"a \\\"quoted\\\" --- thing\"\nother: 'single ---'\n---\nbody\n",
        // a --- inside a block scalar (indented, so not a delimiter)
        "---\nnote: |\n  ---\n  still the value\n---\nbody\n",
        // delimiter line with trailing spaces
        "--- \nschema: 1\n---  \nbody\n",
        // no trailing newline on the body
        "---\nschema: 1\n---\nbody without newline",
        // CRLF throughout
        "---\r\nschema: 1\r\n---\r\nbody\r\n",
        // duplicate top-level keys — every occurrence keeps its own span
        Fixture.duplicated,
        "---\ntitle: a\ntitle: b\ntitle: c\n---\nbody\n",
        // a duplicate whose earlier twin owns a comment and a block scalar
        "---\nnotes: |\n  one\n# about notes\nnotes: two\n---\nbody\n",
    ])
    func serializingAnUntouchedDocumentIsByteIdentical(text: String) throws {
        let document = try FrontmatterDocument.parse(text)
        #expect(document.serialized() == text)
    }

    @Test func bodyIsEverythingAfterTheClosingDelimiter() throws {
        let document = try FrontmatterDocument.parse(Fixture.rich)
        #expect(document.body == "Body text.\n\nMore body — with *markdown*.")
    }

    @Test func emptyBodyIsEmpty() throws {
        #expect(try FrontmatterDocument.parse("---\nschema: 1\n---\n").body == "")
        #expect(try FrontmatterDocument.parse("---\nschema: 1\n---").body == "")
    }

    @Test func bodyTrailingNewlinesAreNotNormalized() throws {
        let document = try FrontmatterDocument.parse("---\nschema: 1\n---\nbody\n\n\n")
        #expect(document.body == "body\n\n\n")
    }
}

// MARK: - Malformed input

struct FrontmatterMalformedTests {
    @Test func missingOpeningDelimiter() {
        #expect(parseError("schema: 1\n---\nbody\n") == .missingOpeningDelimiter)
        #expect(parseError("") == .missingOpeningDelimiter)
        #expect(parseError("\n---\nschema: 1\n---\n") == .missingOpeningDelimiter)
        #expect(parseError("----\nschema: 1\n---\n") == .missingOpeningDelimiter)
        #expect(parseError("--- yaml\nschema: 1\n---\n") == .missingOpeningDelimiter)
    }

    @Test func missingClosingDelimiter() {
        #expect(parseError("---\nschema: 1\nbody\n") == .missingClosingDelimiter)
        #expect(parseError("---\n") == .missingClosingDelimiter)
        #expect(parseError("---") == .missingClosingDelimiter)
    }

    @Test(arguments: [
        "---\nfoo: [1, 2\n---\nbody\n",
        "---\nschema: 1\n\tindented-with-tab: 2\n---\nbody\n",
        "---\nschema: 1\n  bad: indent\n---\nbody\n",
        "---\n\"unterminated: 1\n---\nbody\n",
        "---\nfoo: {a: 1\n---\nbody\n",
        "---\n*undefined-alias\n---\nbody\n",
        // last-wins rescues duplicates at the top level only — nested ones stay a hard failure
        "---\nouter:\n  a: 1\n  a: 2\n---\nbody\n",
        "---\nflow: {a: 1, a: 2}\n---\nbody\n",
        "---\ndupe: 1\ndupe: 2\nouter:\n  a: 1\n  a: 2\n---\nbody\n",
    ])
    func unparseableYAML(text: String) {
        guard case .unparseableYAML = parseError(text) else {
            Issue.record("expected .unparseableYAML for \(text.debugDescription), got \(String(describing: parseError(text)))")
            return
        }
    }

    @Test func unparseableYAMLCarriesAFileRelativeLineNumber() {
        guard case let .unparseableYAML(message, line) = parseError("---\nschema: 1\n  bad: indent\n---\n") else {
            Issue.record("expected .unparseableYAML")
            return
        }
        #expect(line == 3)
        #expect(!message.isEmpty)
    }

    @Test(arguments: [
        "---\n- a\n- b\n---\nbody\n",
        "---\njust a scalar\n---\nbody\n",
        "---\n42\n---\nbody\n",
        "---\n[1, 2]\n---\nbody\n",
    ])
    func frontmatterNotAMapping(text: String) {
        #expect(parseError(text) == .frontmatterNotAMapping)
    }

    @Test(arguments: [
        "---\n---\nbody\n",
        "---\n\n---\nbody\n",
        "---\n# just a comment\n---\nbody\n",
        "---\n{}\n---\nbody\n",
        "---\nnull\n---\nbody\n",
        "---\n~\n---\nbody\n",
    ])
    func emptyFrontmatterIsAnEmptyMapping(text: String) throws {
        let document = try FrontmatterDocument.parse(text)
        #expect(document.keys.isEmpty)
        #expect(document.serialized() == text)
    }

    @Test func wellFormedButUnusualYAMLIsAccepted() throws {
        for text in [Fixture.rich, Fixture.unindentedFlow, "---\nbase: &b\n  x: 1\ncopy: *b\n---\n"] {
            #expect(parseError(text) == nil)
        }
    }

    /// Strict YAML rejects a repeated mapping key; the editor deliberately does not.
    @Test func duplicateTopLevelKeysAreNotAParseError() throws {
        for text in [
            Fixture.duplicated,
            "---\ndupe: 1\ndupe: 2\n---\nbody\n",
            "---\ntitle: a\ntitle: b\ntitle: c\n---\nbody\n",
        ] {
            #expect(parseError(text) == nil)
        }
    }
}

// MARK: - Duplicate keys (last one wins)

/// 01-storage-format.md § Frontmatter: a key written twice reads as its last occurrence, the
/// earlier ones preserved verbatim on disk and invisible; an app write of that key rewrites the
/// winner and removes the twins; a removal removes them all.
struct FrontmatterDuplicateKeyTests {
    @Test func theLastOccurrenceIsTheOneThatReads() throws {
        let document = try FrontmatterDocument.parse(Fixture.duplicated)
        #expect(document.title == .valid("Second"))
        #expect(document.value(for: "title") == .string("Second"))
        #expect(document.rawValue(for: "title") == "Second")
        #expect(document.contains("title"))
    }

    @Test func threeOccurrencesStillReadAsTheLast() throws {
        let document = try FrontmatterDocument.parse("---\ntitle: a\ntitle: b\ntitle: c\n---\nbody\n")
        #expect(document.title == .valid("c"))
        #expect(document.keys == ["title"])
    }

    /// The effective view has one entry per key, sitting where the winner sits — the order the
    /// file itself takes once `set` collapses the twins.
    @Test func iterationPresentsTheEffectiveView() throws {
        let document = try FrontmatterDocument.parse(Fixture.duplicated)
        #expect(document.keys == ["schema", "order", "title"])
        #expect(document.fields.map(\.key) == ["schema", "order", "title"])
        #expect(document.fields.last?.rawValue == "Second")
    }

    @Test func bothOccurrencesSurviveAnUntouchedRoundTrip() throws {
        let document = try FrontmatterDocument.parse(Fixture.duplicated)
        let output = document.serialized()
        #expect(output == Fixture.duplicated)
        #expect(output.contains("title: First"))
        #expect(output.contains("title: Second"))
    }

    @Test func settingRewritesTheWinnerAndDeletesTheTwins() throws {
        var document = try FrontmatterDocument.parse(Fixture.duplicated)
        document.set("title", to: .string("Third"))
        #expect(document.serialized() == "---\nschema: 1\norder: 1024\ntitle: Third\n---\nbody\n")
        #expect(document.title == .valid("Third"))
    }

    @Test func settingCollapsesThreeOccurrencesToOne() throws {
        var document = try FrontmatterDocument.parse("---\ntitle: a\ntitle: b\ntitle: c\n---\nbody\n")
        document.set("title", to: .string("d"))
        #expect(document.serialized() == "---\ntitle: d\n---\nbody\n")
    }

    /// A stale twin left behind would resurrect itself the moment the winner were removed.
    @Test func removingTakesEveryOccurrence() throws {
        var document = try FrontmatterDocument.parse(Fixture.duplicated)
        document.remove("title")
        #expect(document.serialized() == "---\nschema: 1\norder: 1024\n---\nbody\n")
        #expect(document.title == .missing)
        #expect(!document.contains("title"))
        #expect(document.keys == ["schema", "order"])
    }

    @Test func removingTakesAllThreeOccurrences() throws {
        var document = try FrontmatterDocument.parse("---\nschema: 1\ntitle: a\ntitle: b\ntitle: c\n---\nbody\n")
        document.remove("title")
        #expect(document.serialized() == "---\nschema: 1\n---\nbody\n")
    }

    /// Own-line comments outlive the twin they were written above (01-storage-format.md).
    @Test func aCommentAboveTheFirstOccurrenceSurvivesTheCollapse() throws {
        let text = "---\n# the original title\ntitle: First\ntitle: Second\n---\nbody\n"
        var document = try FrontmatterDocument.parse(text)
        #expect(document.serialized() == text)

        document.set("title", to: .string("Third"))
        #expect(document.serialized() == "---\n# the original title\ntitle: Third\n---\nbody\n")
    }

    /// Same rule when the comment trails the earlier twin rather than heading it.
    @Test func aCommentBelowTheFirstOccurrenceSurvivesTheCollapse() throws {
        var document = try FrontmatterDocument.parse("---\ntitle: First\n# still relevant\ntitle: Second\n---\nbody\n")
        document.remove("title")
        #expect(document.serialized() == "---\n# still relevant\n---\nbody\n")
    }

    @Test func twoDifferentKeysMayEachBeDuplicated() throws {
        let text = "---\na: 1\nb: 1\na: 2\nb: 2\n---\nbody\n"
        var document = try FrontmatterDocument.parse(text)
        #expect(document.serialized() == text)
        #expect(document.value(for: "a") == .int(2))
        #expect(document.value(for: "b") == .int(2))
        #expect(document.keys == ["a", "b"])

        document.set("a", to: .int(3))
        #expect(document.serialized() == "---\nb: 1\na: 3\nb: 2\n---\nbody\n")
    }

    /// A duplicated twin whose value is a block scalar still spans its own lines.
    @Test func aDuplicateWithAMultiLineTwinCollapsesCleanly() throws {
        let text = "---\nnotes: |\n  one\n# about notes\nnotes: two\n---\nbody\n"
        var document = try FrontmatterDocument.parse(text)
        #expect(document.serialized() == text)
        #expect(document.value(for: "notes") == .string("two"))

        document.set("notes", to: .string("three"))
        #expect(document.serialized() == "---\n# about notes\nnotes: three\n---\nbody\n")
    }

    /// The twins need not be spelled alike — YAML reads `"title"` and `title` as the same key,
    /// and each occurrence is rewritten (or dropped) in its own spelling.
    @Test func twinsSpelledDifferentlyAreStillTheSameKey() throws {
        let text = "---\n\"title\": First\ntitle: Second\n---\nbody\n"
        var document = try FrontmatterDocument.parse(text)
        #expect(document.serialized() == text)
        #expect(document.title == .valid("Second"))

        document.set("title", to: .string("Third"))
        #expect(document.serialized() == "---\ntitle: Third\n---\nbody\n")
    }

    @Test func duplicatesInACRLFFileRoundTripAndCollapse() throws {
        let text = "---\r\ntitle: First\r\norder: 1\r\ntitle: Second\r\n---\r\nbody\r\n"
        var document = try FrontmatterDocument.parse(text)
        #expect(document.serialized() == text)
        #expect(document.title == .valid("Second"))

        document.set("title", to: .string("Third"))
        #expect(document.serialized() == "---\r\norder: 1\r\ntitle: Third\r\n---\r\nbody\r\n")
    }

    @Test func duplicatesSurviveReparsing() throws {
        var document = try FrontmatterDocument.parse(Fixture.duplicated)
        document.set("order", to: .double(2048))
        let reparsed = try FrontmatterDocument.parse(document.serialized())
        #expect(reparsed.serialized() == document.serialized())
        #expect(reparsed.title == .valid("Second"))
        #expect(reparsed.order == .valid(2048))
    }
}

// MARK: - Generic access

struct FrontmatterAccessTests {
    @Test func keysAreInDocumentOrder() throws {
        let document = try FrontmatterDocument.parse(Fixture.rich)
        #expect(document.keys == [
            "schema", "title", "project", "sphere", "labels", "template",
            "notes", "folded", "created", "modified-by", "tagged", "quoted key",
        ])
    }

    @Test func realCardReadsAsExpected() throws {
        let document = try FrontmatterDocument.parse(Fixture.realCard)
        #expect(document.schema == .valid(1))
        #expect(document.order == .valid(3072))
        #expect(document.title == .valid("Build the frontmatter engine with byte-perfect round-trip"))
        #expect(document.modifiedBy == .valid("claude"))
        #expect(document.created.value != nil)
        #expect(document.unknownFields.map(\.key) == ["source"], "`labels` is schema-owned since 2026-08-09")
        #expect(document.body.hasPrefix("Build the Frontmatter component.\n"))
    }

    @Test func unknownKeysExcludeSchemaOwnedOnes() throws {
        let document = try FrontmatterDocument.parse(Fixture.rich)
        #expect(document.unknownFields.map(\.key) == [
            "project", "sphere", "template", "notes", "folded", "tagged", "quoted key",
        ])
    }

    @Test func modifiedByIsExposedDistinctlyAndIsNotAnUnknownKey() throws {
        let document = try FrontmatterDocument.parse(Fixture.rich)
        #expect(document.modifiedBy == .valid("claude"))
        #expect(!document.unknownFields.contains { $0.key == "modified-by" })
        #expect(document.contains("modified-by"))
    }

    @Test func reservedKeysAreOrdinaryUnknownKeys() throws {
        let text = """
            ---
            schema: 1
            labels: [bug, ui]
            assignees: [ann]
            due: 2026-08-01
            remote: {kind: gitea}
            remote-state: synced
            template: {order: 2}
            ---
            body

            """
        let document = try FrontmatterDocument.parse(text)
        // **`labels` left the reserved family on 2026-08-09** when the owner claimed it for
        // first-party use (`FrontmatterKeys.labels`) — it is schema-owned now, so it is read as a
        // field rather than carried as an unknown key. The other four are untouched, and the
        // byte-identical round trip below is unaffected either way: classification decides how a key
        // is *read*, never how it is written back.
        #expect(document.unknownFields.map(\.key)
            == ["assignees", "due", "remote", "remote-state", "template"])
        #expect(document.labels == .valid(["bug", "ui"]))
        #expect(document.serialized() == text)
    }

    @Test func parsedValuesAreAvailableForUnknownKeys() throws {
        let document = try FrontmatterDocument.parse(Fixture.rich)
        #expect(document.value(for: "labels") == .sequence([.string("a"), .string("b"), .string("c")]))
        #expect(document.value(for: "template")
            == .mapping([YAMLValue.Pair(key: .string("order"), value: .int(3))]))
        #expect(document.value(for: "quoted key") == .bool(true))
        #expect(document.value(for: "tagged") == .string("42"))
        #expect(document.value(for: "nope") == nil)
    }

    @Test func rawValuePreservesBlockScalarsVerbatim() throws {
        let document = try FrontmatterDocument.parse(Fixture.rich)
        #expect(document.rawValue(for: "notes") == "|\n  line one\n    indented two")
        #expect(document.rawValue(for: "labels") == "[a, b, c]")
        #expect(document.rawValue(for: "schema") == "1")
    }

    /// A trailing comment belongs to the line, not to the value — reads stop at it.
    @Test func rawValueStopsAtAnInlineComment() throws {
        let document = try FrontmatterDocument.parse(Fixture.rich)
        #expect(document.rawValue(for: "project") == "lanework")
    }

    /// …but a `#` inside a quoted scalar is value text, and a multi-line value is never scanned.
    @Test func rawValueKeepsAHashThatIsNotAComment() throws {
        let text = """
            ---
            quoted: "hash # inside"
            single: 'hash # inside'
            both: "hash # inside"  # and a real one
            flow: [a, "b # c"]  # note
            block: |
              text  # not a comment out here either
            ---
            body

            """
        let document = try FrontmatterDocument.parse(text)
        #expect(document.rawValue(for: "quoted") == "\"hash # inside\"")
        #expect(document.rawValue(for: "single") == "'hash # inside'")
        #expect(document.rawValue(for: "both") == "\"hash # inside\"")
        #expect(document.rawValue(for: "flow") == "[a, \"b # c\"]")
        #expect(document.rawValue(for: "block") == "|\n  text  # not a comment out here either")
        #expect(document.serialized() == text)
    }
}

// MARK: - Strict fields

struct FrontmatterStrictFieldTests {
    private func document(_ frontmatter: String) throws -> FrontmatterDocument {
        try FrontmatterDocument.parse("---\n\(frontmatter)\n---\nbody\n")
    }

    @Test func schemaValidMissingMalformed() throws {
        #expect(try document("schema: 1").schema == .valid(1))
        #expect(try document("title: x").schema == .missing)
        #expect(try document("schema:").schema == .missing)
        #expect(try document("schema: banana").schema == .malformed(raw: "banana"))
        #expect(try document("schema: 1.5").schema == .malformed(raw: "1.5"))
        #expect(try document("schema: [1]").schema == .malformed(raw: "[1]"))
    }

    @Test func orderValidMissingMalformed() throws {
        #expect(try document("order: 1024").order == .valid(1024))
        #expect(try document("order: 1536.5").order == .valid(1536.5))
        #expect(try document("order: -1024").order == .valid(-1024))
        #expect(try document("schema: 1").order == .missing)
        #expect(try document("order:").order == .missing)
    }

    @Test func nonNumericOrderIsMalformedNotNil() throws {
        let malformed = try document("order: banana").order
        #expect(malformed == .malformed(raw: "banana"))
        #expect(malformed.value == nil)
        #expect(malformed.isMalformed)
        #expect(!malformed.isMissing)
        #expect(malformed.rawText == "banana")

        #expect(try document("order: \"1024\"").order == .malformed(raw: "\"1024\""))
        #expect(try document("order: [1]").order == .malformed(raw: "[1]"))
    }

    /// NaN has no place in the total order the tie-break and midpoint math assume
    /// (01-storage-format.md § Frontmatter, settled): a non-finite reading is `.malformed` exactly
    /// like a non-numeric one, never a silently `.valid(Double.nan)`.
    ///
    /// What that *costs* is the rulebook's, and it changed on 2026-07-31: below the board root both
    /// shapes now read as append-at-end, coerce-tier, rather than failing the load
    /// (`IntegrityRules.resolvedOrder`, `BoardLoaderOptionalOrderTests`). The document's reading is
    /// unchanged, which is the point of the split.
    @Test func nonFiniteOrderIsMalformedNotValid() throws {
        #expect(try document("order: .nan").order == .malformed(raw: ".nan"))
        #expect(try document("order: .inf").order == .malformed(raw: ".inf"))
        #expect(try document("order: +.inf").order == .malformed(raw: "+.inf"))
        #expect(try document("order: -.inf").order == .malformed(raw: "-.inf"))
    }
}

// MARK: - Lenient fields

struct FrontmatterLenientFieldTests {
    private func document(_ frontmatter: String) throws -> FrontmatterDocument {
        try FrontmatterDocument.parse("---\n\(frontmatter)\n---\nbody\n")
    }

    @Test func wellFormedLenientValues() throws {
        #expect(try document("title: My Board").title == .valid("My Board"))
        #expect(try document("background: {color: \"#ff8800\"}").background == .valid("#ff8800"))
        #expect(try document("background: {color: slate}").background == .valid("slate"))
        #expect(try document("icon: tray.full").icon == .valid("tray.full"))
        #expect(try document("iconColor: teal").iconColor == .valid("teal"))
        #expect(try document("width: 3").width == .valid(3))
        #expect(try document("width: 1").width == .valid(1))
    }

    /// A scalar of the wrong YAML type still has a sensible string reading — it coerces to the
    /// source text the author typed (01-storage-format.md § Frontmatter). Only a sequence or
    /// mapping — no scalar to read at all — is malformed.
    @Test func scalarsOfTheWrongTypeCoerceToTheirSourceText() throws {
        #expect(try document("title: 2048").title == .valid("2048"))
        #expect(try document("title: true").title == .valid("true"))
        #expect(try document("background: {color: 42}").background == .valid("42"))
        #expect(try document("iconColor: true").iconColor == .valid("true"))
        #expect(try document("icon: 2026-07-26T16:41:38Z").icon == .valid("2026-07-26T16:41:38Z"))
    }

    /// The comment on a value line belongs to the line, not to the value: the source text a
    /// wrong-typed scalar coerces to stops where the comment starts.
    @Test func aTrailingCommentIsNotPartOfACoercedValue() throws {
        #expect(try document("title: 2048  # note").title == .valid("2048"))
        #expect(try document("title: true # note").title == .valid("true"))
        #expect(try document("background: {color: 42}\t# tabbed").background == .valid("42"))
        #expect(try document("icon: 2026-07-26T16:41:38Z  # when").icon == .valid("2026-07-26T16:41:38Z"))
    }

    /// A `#` inside a quoted scalar is value text — the quotes are what make `#ff8800` writable
    /// at all, so a read must not treat it as a comment.
    @Test func aHashInsideAQuotedValueIsNotTrimmed() throws {
        #expect(try document("title: \"2048 # note\"").title == .valid("2048 # note"))
        #expect(try document("background: {color: \"#ff8800\"}").background == .valid("#ff8800"))
        #expect(try document("background: {color: \"#ff8800\"}  # brand orange").background == .valid("#ff8800"))
    }

    /// A sequence or mapping has no scalar reading at all; the raw text it falls back to stops at
    /// the comment like every other read.
    @Test func malformedRawStopsAtAnInlineComment() throws {
        #expect(try document("background: [red, blue]  # a palette").background == .malformed(raw: "[red, blue]"))
        #expect(try document("title: {a: 1}  # a mapping").title == .malformed(raw: "{a: 1}"))
        #expect(try document("width: wide  # roughly").width == .malformed(raw: "wide"))
        #expect(try document("order: banana  # not a number").order == .malformed(raw: "banana"))
    }

    @Test func quotedStringLenientValuesAreUnaffectedByCoercion() throws {
        #expect(try document("title: \"2048\"").title == .valid("2048"))
        #expect(try document("title: \"true\"").title == .valid("true"))
        #expect(try document("title: My Board").title == .valid("My Board"))
    }

    @Test func malformedLenientValuesArePreservedVerbatimAndDoNotThrow() throws {
        #expect(try document("background: [red, blue]").background == .malformed(raw: "[red, blue]"))
        #expect(try document("icon: {a: 1}").icon == .malformed(raw: "{a: 1}"))
        #expect(try document("title: [a, b]").title == .malformed(raw: "[a, b]"))
    }

    // MARK: `background` is a mapping and only a mapping

    /// **The retired scalar** (01-storage-format.md § Frontmatter, ruled 2026-08-06 before anything
    /// shipped — one shape, no legacy spelling, no migration): `background: green` has no reading at
    /// all. It is `.malformed` like any other unreadable value, which renders as no colour and
    /// leaves the bytes exactly as written.
    @Test func aScalarBackgroundHasNoReading() throws {
        #expect(try document("background: green").background == .malformed(raw: "green"))
        #expect(try document("background: \"#ff8800\"").background == .malformed(raw: "\"#ff8800\""))
        #expect(try document("background: 42").background == .malformed(raw: "42"))
        // The raw stops at the comment like every other read, since a comment is the line's.
        #expect(try document("background: green  # my colour").background == .malformed(raw: "green"))
    }

    /// One unreadable value is reported **once**: the colour reading owns the key's shape, and the
    /// image stays silent about a file that never wrote a mapping to name a picture in.
    @Test func onlyTheColourReportsANonMappingShape() throws {
        #expect(try document("background: green").backgroundImage == .missing)
        #expect(try document("background: [red, blue]").backgroundImage == .missing)
        #expect(try document("background: 42").backgroundImage == .missing)
        #expect(try document("schema: 1").backgroundImage == .missing)
        #expect(try document("background: green").coercedFields
            == [CoercedField(key: "background", raw: "green")])
    }

    /// The mapping form, both halves present — flow and block spellings are one YAML value and
    /// therefore one reading.
    @Test func aMappingBackgroundReadsBothSubkeys() throws {
        let flow = try document("background: {color: \"#112233\", image: sunset.jpg}")
        #expect(flow.background == .valid("#112233"))
        #expect(flow.backgroundImage == .valid("sunset.jpg"))

        let block = try FrontmatterDocument.parse(
            "---\nbackground:\n  color: fern\n  image: art/sunset.jpg\n---\nbody\n"
        )
        #expect(block.background == .valid("fern"))
        #expect(block.backgroundImage == .valid("art/sunset.jpg"))
    }

    /// Either half may be absent, and an absent half is `.missing` — not malformed. An image-only
    /// background is a board with no colour, which is the level's default and not a fallback.
    @Test func eitherSubkeyMayBeAbsent() throws {
        let colorOnly = try document("background: {color: chalk}")
        #expect(colorOnly.background == .valid("chalk"))
        #expect(colorOnly.backgroundImage == .missing)

        let imageOnly = try document("background: {image: sunset.jpg}")
        #expect(imageOnly.background == .missing)
        #expect(imageOnly.backgroundImage == .valid("sunset.jpg"))

        let empty = try document("background: {}")
        #expect(empty.background == .missing)
        #expect(empty.backgroundImage == .missing)
    }

    /// An explicit null subkey reads exactly like an absent one — `FieldValue.missing` already
    /// treats `background: null` that way, and a subkey is no different.
    @Test func nullSubkeysReadAsMissing() throws {
        let nulls = try document("background: {color: null, image: ~}")
        #expect(nulls.background == .missing)
        #expect(nulls.backgroundImage == .missing)
    }

    /// Unknown subkeys are tolerated on the way in exactly as unknown *keys* are — they mean
    /// nothing to either reading and cost it nothing.
    @Test func unknownSubkeysAreTolerated() throws {
        let extra = try document("background: {opacity: 0.5, color: fern, blend: multiply}")
        #expect(extra.background == .valid("fern"))
        #expect(extra.backgroundImage == .missing)
    }

    /// Inside the mapping the subvalues coerce like any other scalar, quoted or not; a subvalue with
    /// no scalar reading at all is malformed, quoting the subvalue rather than the whole span —
    /// a subkey has no source span of its own to quote.
    @Test func subkeyScalarsCoerceAndCollectionsAreMalformed() throws {
        #expect(try document("background: {color: 42}").background == .valid("42"))
        #expect(try document("background: {image: \"sun set.jpg\"}").backgroundImage == .valid("sun set.jpg"))
        #expect(try document("background: {color: [a, b]}").background == .malformed(raw: "[a, b]"))
        #expect(try document("background: {image: {a: 1}}").backgroundImage == .malformed(raw: "{a: 1}"))
        // The other half of a mapping with one bad subkey still reads perfectly well.
        #expect(try document("background: {color: [a, b], image: sunset.jpg}").backgroundImage == .valid("sunset.jpg"))
    }

    /// **The coerce tier's trace covers both readings** (01-storage-format.md § Frontmatter: "every
    /// silent recovery leaves a trace"). Both are filed under the key the schema spells, and are told
    /// apart by the subvalue each quotes — so a mapping whose colour reads fine and whose image does
    /// not is still visible.
    @Test func bothBackgroundReadingsReachTheCoerceRecord() throws {
        #expect(try document("background: {color: fern, image: [a, b]}").coercedFields
            == [CoercedField(key: "background", raw: "[a, b]")])
        #expect(try document("background: {color: [a], image: [b]}").coercedFields
            == [CoercedField(key: "background", raw: "[a]"), CoercedField(key: "background", raw: "[b]")])
        // A sequence is one unreadable value and is reported once — the image reading stays silent
        // about a shape that never claimed to name one.
        #expect(try document("background: [red, blue]").coercedFields
            == [CoercedField(key: "background", raw: "[red, blue]")])
        #expect(try document("background: {image: sunset.jpg}").coercedFields.isEmpty)
    }

    /// The engine reads the shape; it never rewrites it. A mapping background round-trips
    /// byte-identically like every other value the app did not touch.
    @Test func aMappingBackgroundRoundTripsVerbatim() throws {
        let text = "---\nschema: 1\nbackground:\n  color: fern\n  image: art/sunset.jpg\n  blend: multiply\n---\nbody\n"
        let document = try FrontmatterDocument.parse(text)
        #expect(document.serialized() == text)
        #expect(document.background == .valid("fern"))
        #expect(document.backgroundImage == .valid("art/sunset.jpg"))
    }

    /// Only a fractional or non-numeric reading has no sensible width at all — a sequence,
    /// mapping, or scalar with no integer reading whatsoever stays malformed and renders as the
    /// default 1.
    @Test func widthIsMalformedOnlyForFractionalOrNonNumericReadings() throws {
        #expect(try document("width: 1.5").width == .malformed(raw: "1.5"))
        #expect(try document("width: wide").width == .malformed(raw: "wide"))
        #expect(try document("schema: 1").width == .missing)
    }

    /// **Ranges are part of the sensible reading** (01-storage-format.md § Frontmatter, settled):
    /// an exact-integer reading below 1 — from an int, a double, or a numeric string — coerces to
    /// 1 rather than falling back to malformed. The table's "≥ 1" is a validity bound on the
    /// coerced value, not a gate on which readings count as sensible.
    @Test func anIntegerWidthBelowOneCoercesToOne() throws {
        #expect(try document("width: 0").width == .valid(1))
        #expect(try document("width: -3").width == .valid(1))
        #expect(try document("width: \"0\"").width == .valid(1))
        #expect(try document("width: \"-3\"").width == .valid(1))
        #expect(try document("width: -1.0").width == .valid(1))
    }

    /// A string or double with an exact integer reading coerces — below 1 to 1 (see
    /// `anIntegerWidthBelowOneCoercesToOne`), at or above 1 to itself; a fractional reading or
    /// non-numeric text still has none.
    @Test func widthCoercesStringsAndWholeNumberDoubles() throws {
        #expect(try document("width: \"2\"").width == .valid(2))
        #expect(try document("width: 2.0").width == .valid(2))
        #expect(try document("width: 2.7").width == .malformed(raw: "2.7"))
        #expect(try document("width: banana").width == .malformed(raw: "banana"))
    }

    /// **`collapsed` reads like `width`, one type over** (03-board-ui.md § Lane ▸ Collapsed lanes;
    /// 01-storage-format.md § Frontmatter's lane table): a boolean scalar reads as itself — including
    /// YAML 1.1's other spellings, which the parser has already resolved — and a *quoted* one of those
    /// words coerces, the numeric-string precedent applied to a boolean field.
    @Test func collapsedReadsBooleansAndTheirQuotedSpellings() throws {
        #expect(try document("collapsed: true").collapsed == .valid(true))
        #expect(try document("collapsed: false").collapsed == .valid(false))
        // YAML 1.1 resolves these to booleans unquoted, so they arrive here already typed.
        #expect(try document("collapsed: yes").collapsed == .valid(true))
        #expect(try document("collapsed: off").collapsed == .valid(false))
        // Quoted, they are strings — and a string that spells a boolean word has a sensible reading.
        #expect(try document("collapsed: \"true\"").collapsed == .valid(true))
        #expect(try document("collapsed: \"No\"").collapsed == .valid(false))
        #expect(try document("collapsed: \"ON\"").collapsed == .valid(true))
    }

    /// **Everything else has no boolean reading, and therefore renders as expanded** — the default is
    /// the absent key's, so an unreadable value can never fold a lane by accident.
    @Test func collapsedIsMalformedForEveryNonBooleanReading() throws {
        #expect(try document("schema: 1").collapsed == .missing)
        #expect(try document("collapsed: null").collapsed == .missing)
        #expect(try document("collapsed: maybe").collapsed == .malformed(raw: "maybe"))
        #expect(try document("collapsed: 1").collapsed == .malformed(raw: "1"))
        #expect(try document("collapsed: [true]").collapsed == .malformed(raw: "[true]"))
        #expect(try document("collapsed: {a: 1}").collapsed == .malformed(raw: "{a: 1}"))
    }

    /// A lenient field with no reading files a coerce-tier trace and leaves the bytes exactly as
    /// written — the family's posture, `collapsed` included.
    @Test func anUnreadableCollapsedFilesATraceAndRoundTrips() throws {
        let text = "---\nschema: 1\ncollapsed: maybe\nwidth: 2\n---\nbody\n"
        let parsed = try FrontmatterDocument.parse(text)
        #expect(parsed.serialized() == text)
        #expect(parsed.coercedFields == [CoercedField(key: "collapsed", raw: "maybe")])
        // A readable value is an absence of trace, not a trace of a value.
        #expect(try document("collapsed: true").coercedFields.isEmpty)
        #expect(try document("collapsed: false").coercedFields.isEmpty)
    }

    /// The key is the schema's, so the card window's Details section does not list it beside a user's
    /// own overlay keys (`FrontmatterKeys.schemaOwned`).
    @Test func collapsedIsSchemaOwnedRatherThanAnUnknownKey() throws {
        #expect(FrontmatterKeys.schemaOwned.contains(FrontmatterKeys.collapsed))
        #expect(try document("collapsed: true").unknownFields.isEmpty)
    }

    /// **`hero` reads like `title` with a gate after it** (03-board-ui.md § Card face ▸ Hero image;
    /// 01-storage-format.md § Frontmatter's card table): the string family's scalar coercion, so a
    /// quoted name reads as its own text and an unquoted one as its source span — a filename that
    /// looks like a number included.
    @Test func heroReadsAnyScalarThatCouldNameAFile() throws {
        #expect(try document("hero: sketch.png").hero == .valid("sketch.png"))
        #expect(try document("hero: \"a picture.jpg\"").hero == .valid("a picture.jpg"))
        // A scalar YAML types as something else still reads as the text the author typed — the same
        // coercion `title: 2048` gets, and `2048.png` is a perfectly good filename.
        #expect(try document("hero: 2048").hero == .valid("2048"))
        // Nothing about the *name* is policed beyond the gate below: a leading dot, a tilde and a
        // space are all a filename's business, and one that names nothing simply resolves to nothing.
        #expect(try document("hero: \".hidden.png\"").hero == .valid(".hidden.png"))
        #expect(try document("hero: \"~shot.png\"").hero == .valid("~shot.png"))
    }

    /// **A path is not a hero image spelled awkwardly — it is a value with no reading** (the bare
    /// filename grammar, ruled 2026-08-09). Every separator position refuses: a subfolder, a climb
    /// out, an absolute path, and a trailing slash. So do the two directory entries every folder
    /// carries, and the empty string.
    @Test func heroIsMalformedForEveryValueThatIsNotABareFilename() throws {
        #expect(try document("hero: art/sketch.png").hero == .malformed(raw: "art/sketch.png"))
        #expect(try document("hero: ../sketch.png").hero == .malformed(raw: "../sketch.png"))
        #expect(try document("hero: /etc/passwd").hero == .malformed(raw: "/etc/passwd"))
        // The mistake the key most invites: the folder is implied, so naming it is naming a path.
        #expect(try document("hero: attachments/sketch.png").hero == .malformed(raw: "attachments/sketch.png"))
        #expect(try document("hero: sketch.png/").hero == .malformed(raw: "sketch.png/"))
        // The two entries every folder carries, which name a directory rather than a file.
        #expect(try document("hero: .").hero == .malformed(raw: "."))
        #expect(try document("hero: ..").hero == .malformed(raw: ".."))
        // Empty can only be written quoted — unquoted is null, which is an absence (below). The raw
        // is the span as written, quotes included, like every other malformed value's.
        #expect(try document("hero: \"\"").hero == .malformed(raw: "\"\""))
        // And the string family's own floor: a sequence or mapping has no scalar reading at all.
        #expect(try document("hero: [a.png]").hero == .malformed(raw: "[a.png]"))
        #expect(try document("hero: {name: a.png}").hero == .malformed(raw: "{name: a.png}"))
    }

    /// **No key is no banner, and that is an absence rather than a failure** — nothing to report and
    /// nothing to render, which is the card every board is made of.
    @Test func heroIsMissingWhenTheKeyIsAbsentOrNull() throws {
        #expect(try document("schema: 1").hero == .missing)
        #expect(try document("hero: null").hero == .missing)
        #expect(try document("schema: 1").coercedFields.isEmpty)
    }

    /// A lenient field with no reading files a coerce-tier trace and leaves the bytes exactly as
    /// written — the family's posture, `hero` included.
    @Test func anUnreadableHeroFilesATraceAndRoundTrips() throws {
        let text = "---\nschema: 1\nkind: card\nhero: art/sketch.png\n---\nbody\n"
        let parsed = try FrontmatterDocument.parse(text)
        #expect(parsed.serialized() == text)
        #expect(parsed.coercedFields == [CoercedField(key: "hero", raw: "art/sketch.png")])
        // A readable name is an absence of trace, not a trace of a value.
        #expect(try document("hero: sketch.png").coercedFields.isEmpty)
    }

    /// The key is the schema's, so the card window's Details section does not list it beside a user's
    /// own overlay keys — `iconColor`'s posture exactly: schema yes, control no.
    @Test func heroIsSchemaOwnedRatherThanAnUnknownKey() throws {
        #expect(FrontmatterKeys.schemaOwned.contains(FrontmatterKeys.hero))
        #expect(try document("hero: sketch.png").unknownFields.isEmpty)
    }

    @Test func malformedLenientValuesStillRoundTrip() throws {
        let text = "---\nschema: 1\nbackground: [red, blue]\nwidth: 1.5\nicon: {a: 1}\n---\nbody\n"
        let document = try FrontmatterDocument.parse(text)
        #expect(document.serialized() == text)
        #expect(document.background.isMalformed)
        #expect(document.width.isMalformed)
        #expect(document.icon.isMalformed)
    }

    @Test func timestampsReadPlainAndQuoted() throws {
        let expected = Date(timeIntervalSince1970: 1_785_084_098)  // 2026-07-26T16:41:38Z
        #expect(try document("created: 2026-07-26T16:41:38Z").created == .valid(expected))
        #expect(try document("modified: \"2026-07-26T16:41:38Z\"").modified == .valid(expected))
        #expect(try document("deleted: 2026-07-26T16:41:38Z").deleted == .valid(expected))
        #expect(try document("schema: 1").deleted == .missing)
        #expect(try document("created: never").created == .malformed(raw: "never"))
    }

    /// A quoted ISO-8601 string is not YAML's implicit timestamp type — it parses as `.string`
    /// — but still coerces to a valid date (01-storage-format.md § Frontmatter).
    @Test func quotedISO8601StringCoercesToAValidDate() throws {
        let expected = Date(timeIntervalSince1970: 1_767_323_045)  // 2026-01-02T03:04:05Z
        #expect(try document("created: \"2026-01-02T03:04:05Z\"").created == .valid(expected))
    }
}

// MARK: - Surgical edits

struct FrontmatterEditTests {
    @Test func settingOneKeyLeavesEveryOtherByteIdentical() throws {
        var document = try FrontmatterDocument.parse(Fixture.rich)
        document.set("title", to: .string("Renamed"))
        let output = document.serialized()

        let expected = Fixture.rich.replacingOccurrences(of: "title: My Board", with: "title: Renamed")
        #expect(output == expected)

        // Prefix and suffix around the edited span are untouched.
        let marker = "title: "
        let originalPrefix = Fixture.rich.prefix(while: { _ in true }).components(separatedBy: marker)[0]
        #expect(output.hasPrefix(originalPrefix))
        #expect(output.hasSuffix("\nMore body — with *markdown*."))
    }

    @Test func settingAKeyPreservesItsTrailingCommentsAndBlankLines() throws {
        let text = "---\nschema: 1\nproject: lanework       # agent overlay\n\n# a note\norder: 5\n---\nbody\n"
        var document = try FrontmatterDocument.parse(text)
        document.set("project", to: .string("kanban"))
        #expect(document.serialized()
            == "---\nschema: 1\nproject: kanban       # agent overlay\n\n# a note\norder: 5\n---\nbody\n")
    }

    @Test func appendingANewKeyGoesBeforeTheClosingDelimiter() throws {
        var document = try FrontmatterDocument.parse(Fixture.minimal)
        document.set("modified", to: .date(Date(timeIntervalSince1970: 1_785_084_098)))
        #expect(document.serialized() == """
            ---
            schema: 1
            order: 1024
            title: Thing
            modified: 2026-07-26T16:41:38Z
            ---
            Body
            """)
        #expect(document.keys == ["schema", "order", "title", "modified"])
    }

    @Test func appendingToEmptyFrontmatter() throws {
        var document = try FrontmatterDocument.parse("---\n---\nbody\n")
        document.set("schema", to: .int(1))
        #expect(document.serialized() == "---\nschema: 1\n---\nbody\n")
        #expect(document.schema == .valid(1))
    }

    @Test func removingAKeyDeletesOnlyItsLines() throws {
        var document = try FrontmatterDocument.parse(Fixture.minimal)
        document.remove("order")
        #expect(document.serialized() == "---\nschema: 1\ntitle: Thing\n---\nBody")
        #expect(document.order == .missing)
        #expect(document.keys == ["schema", "title"])
    }

    @Test func removingAKeyKeepsTrailingComments() throws {
        var document = try FrontmatterDocument.parse("---\na: 1\n# note about b\nb: 2\n---\nbody\n")
        document.remove("a")
        #expect(document.serialized() == "---\n# note about b\nb: 2\n---\nbody\n")
    }

    @Test func removingAnAbsentKeyIsANoOp() throws {
        var document = try FrontmatterDocument.parse(Fixture.minimal)
        document.remove("nope")
        #expect(document.serialized() == Fixture.minimal)
    }

    @Test func editingAKeyWhoseValueWasABlockScalar() throws {
        var document = try FrontmatterDocument.parse(Fixture.rich)
        document.set("notes", to: .string("flattened"))
        #expect(document.serialized().contains("notes: flattened\nfolded: >\n"))
        #expect(!document.serialized().contains("line one"))
        #expect(document.value(for: "notes") == .string("flattened"))
        // Everything after the edited span is untouched.
        #expect(document.serialized().hasSuffix("More body — with *markdown*."))
    }

    @Test func editingAKeyAfterAnUnindentedFlowContinuation() throws {
        var document = try FrontmatterDocument.parse(Fixture.unindentedFlow)
        document.set("last", to: .string("y"))
        #expect(document.serialized() == "---\nflow: {a: 1,\nb: 2}\nlast: y\n---\nbody")
    }

    @Test func editingAQuotedKeyKeepsItsOriginalSpelling() throws {
        var document = try FrontmatterDocument.parse("---\n\"quoted key\": one\n---\nbody\n")
        document.set("quoted key", to: .string("two"))
        #expect(document.serialized() == "---\n\"quoted key\": two\n---\nbody\n")
    }

    @Test func buildingADocumentFromScratch() throws {
        var document = FrontmatterDocument(body: "The card body.\n")
        document.set("schema", to: .int(1))
        document.set("order", to: .double(1024))
        document.set("title", to: .string("New card"))
        let text = document.serialized()
        #expect(text == "---\nschema: 1\norder: 1024\ntitle: New card\n---\nThe card body.\n")
        #expect(try FrontmatterDocument.parse(text).serialized() == text)
    }

    @Test func settingTheBodyReplacesOnlyTheBody() throws {
        var document = try FrontmatterDocument.parse(Fixture.rich)
        document.body = "Brand new body.\n"
        let output = document.serialized()
        #expect(output.hasSuffix("---\nBrand new body.\n"))
        #expect(output.hasPrefix("---\n# board settings, hand-written\nschema: 1\n"))
        #expect(!output.contains("More body"))
    }

    @Test func editsSurviveReparsing() throws {
        var document = try FrontmatterDocument.parse(Fixture.rich)
        document.set("order", to: .double(2048))
        document.remove("modified-by")
        document.set("title", to: .string("Renamed"))

        let reparsed = try FrontmatterDocument.parse(document.serialized())
        #expect(reparsed.order == .valid(2048))
        #expect(reparsed.modifiedBy == .missing)
        #expect(reparsed.title == .valid("Renamed"))
        #expect(reparsed.unknownFields.map(\.key)
            == ["project", "sphere", "template", "notes", "folded", "tagged", "quoted key"])
        #expect(reparsed.serialized() == document.serialized())
    }
}

// MARK: - Inline comments on rewritten lines

/// 01-storage-format.md § Frontmatter, "App rewrites preserve comments": an inline comment on a
/// rewritten value line is re-spliced after the new value — best-effort, guaranteed for plain
/// single-line scalars, allowed to drop in pathological shapes.
struct FrontmatterInlineCommentTests {
    private func edited(_ frontmatter: String, _ key: String, _ value: FrontmatterValue) throws -> String {
        var document = try FrontmatterDocument.parse("---\n\(frontmatter)\n---\nbody\n")
        document.set(key, to: value)
        return String(document.serialized().dropFirst("---\n".count).dropLast("\n---\nbody\n".count))
    }

    @Test func aPlainScalarKeepsItsCommentAndItsSpacing() throws {
        #expect(try edited("order: 3072  # keep at top", "order", .double(4096))
            == "order: 4096  # keep at top")
        #expect(try edited("order: 3072 # tight", "order", .double(4096)) == "order: 4096 # tight")
        #expect(try edited("order: 3072\t# tabbed", "order", .double(4096)) == "order: 4096\t# tabbed")
    }

    @Test func aValueLineWithoutACommentIsRewrittenAsBefore() throws {
        #expect(try edited("order: 3072", "order", .double(4096)) == "order: 4096")
        #expect(try edited("title: My Board", "title", .string("Renamed")) == "title: Renamed")
    }

    /// A `#` inside a quoted scalar is value text, not a comment — it must not be re-spliced.
    @Test func aHashInsideAQuotedScalarIsNotAComment() throws {
        #expect(try edited("title: \"hash # inside\"", "title", .string("Renamed")) == "title: Renamed")
        #expect(try edited("title: 'hash # inside'", "title", .string("Renamed")) == "title: Renamed")
        #expect(try edited("background: \"#ff8800\"", "background", .string("slate")) == "background: slate")
    }

    /// …but a real comment *after* such a value still is one.
    @Test func aCommentAfterAQuotedScalarIsStillSpliced() throws {
        #expect(try edited("title: \"hash # inside\"  # real note", "title", .string("Renamed"))
            == "title: Renamed  # real note")
        #expect(try edited("title: 'it''s # fine'  # real note", "title", .string("Renamed"))
            == "title: Renamed  # real note")
    }

    /// An apostrophe mid-scalar does not open a quoted scalar, so the comment after it is found.
    @Test func aPlainScalarWithAnApostropheKeepsItsComment() throws {
        #expect(try edited("title: it's fine  # note", "title", .string("Renamed"))
            == "title: Renamed  # note")
        #expect(try edited("title: say \"hi\"  # note", "title", .string("Renamed"))
            == "title: Renamed  # note")
    }

    @Test func aSingleLineFlowCollectionKeepsItsComment() throws {
        #expect(try edited("labels: [a, \"b # c\"]  # note", "labels", .raw("[x]"))
            == "labels: [x]  # note")
        #expect(try edited("template: {order: 3}  # picker slot", "template", .raw("{order: 7}"))
            == "template: {order: 7}  # picker slot")
    }

    @Test func aCommentOnAnEmptyValueIsKept() throws {
        #expect(try edited("title:  # to be filled in", "title", .string("Named"))
            == "title: Named  # to be filled in")
    }

    @Test func theCommentSurvivesSuccessiveSets() throws {
        var document = try FrontmatterDocument.parse("---\norder: 3072  # keep at top\n---\nbody\n")
        document.set("order", to: .double(4096))
        document.set("order", to: .double(8192))
        #expect(document.serialized() == "---\norder: 8192  # keep at top\n---\nbody\n")
    }

    /// Reading a value and writing it straight back must leave one comment, not two — the read
    /// stops at the comment, the write re-splices it.
    @Test func aReadThenWriteDoesNotCompoundTheComment() throws {
        var document = try FrontmatterDocument.parse("---\ntitle: 2048  # note\n---\nbody\n")
        #expect(document.title == .valid("2048"))

        document.set("title", to: .string(document.title.value ?? ""))
        #expect(document.serialized() == "---\ntitle: \"2048\"  # note\n---\nbody\n")

        document.set("title", to: .string(document.title.value ?? ""))
        #expect(document.serialized() == "---\ntitle: \"2048\"  # note\n---\nbody\n")
        #expect(try FrontmatterDocument.parse(document.serialized()).title == .valid("2048"))
    }

    @Test func theCommentSurvivesAReparse() throws {
        var document = try FrontmatterDocument.parse("---\norder: 3072  # keep at top\n---\nbody\n")
        document.set("order", to: .double(4096))
        let reparsed = try FrontmatterDocument.parse(document.serialized())
        #expect(reparsed.order == .valid(4096))
        #expect(reparsed.serialized() == document.serialized())
    }

    /// The winning occurrence's own comment is what carries over; a collapsed twin's inline
    /// comment goes with the twin.
    @Test func theWinningOccurrencesCommentIsTheOneKept() throws {
        var document = try FrontmatterDocument.parse("---\norder: 1  # old\norder: 2  # current\n---\nbody\n")
        document.set("order", to: .double(3))
        #expect(document.serialized() == "---\norder: 3  # current\n---\nbody\n")
    }

    /// Pathological shapes are allowed to lose the comment rather than have the editor guess.
    /// Pinning the chosen behavior: a value spanning several lines is not scanned at all.
    @Test func aMultiLineValueDropsItsHeaderComment() throws {
        var document = try FrontmatterDocument.parse("---\nnotes: |  # about the notes\n  line one\n---\nbody\n")
        document.set("notes", to: .string("flattened"))
        #expect(document.serialized() == "---\nnotes: flattened\n---\nbody\n")

        var flow = try FrontmatterDocument.parse("---\nflow: {a: 1,\nb: 2}  # spread out\nlast: x\n---\nbody\n")
        flow.set("flow", to: .raw("{a: 9}"))
        #expect(flow.serialized() == "---\nflow: {a: 9}\nlast: x\n---\nbody\n")
    }

    /// Comments on their own lines were always preserved and still are — including the one that
    /// heads the frontmatter and the one that trails it.
    @Test func ownLineCommentsAreUntouchedByARewrite() throws {
        var document = try FrontmatterDocument.parse(Fixture.rich)
        document.set("title", to: .string("Renamed"))
        let output = document.serialized()
        #expect(output.hasPrefix("---\n# board settings, hand-written\nschema: 1\n"))
        #expect(output.contains("\n# trailing note\n---\n"))
        #expect(output.contains("project: lanework       # agent overlay\n"))
    }
}

// MARK: - Scalar emission

struct FrontmatterEmissionTests {
    private func emittedTitle(_ value: FrontmatterValue) throws -> String {
        var document = try FrontmatterDocument.parse("---\n---\n")
        document.set("title", to: value)
        return String(document.serialized().dropFirst("---\ntitle: ".count).dropLast("\n---\n".count))
    }

    @Test func numbersAndTimestampsAreEmittedPlain() throws {
        #expect(try emittedTitle(.int(42)) == "42")
        #expect(try emittedTitle(.double(1024)) == "1024")
        #expect(try emittedTitle(.double(1536.5)) == "1536.5")
        #expect(try emittedTitle(.bool(true)) == "true")
        #expect(try emittedTitle(.date(Date(timeIntervalSince1970: 1_785_084_098))) == "2026-07-26T16:41:38Z")
    }

    @Test func ordinaryStringsAreEmittedPlain() throws {
        #expect(try emittedTitle(.string("My Board")) == "My Board")
        #expect(try emittedTitle(.string("Fix login — round 2")) == "Fix login — round 2")
        #expect(try emittedTitle(.string("a/b?c")) == "a/b?c")
    }

    @Test(arguments: [
        "",
        " padded ",
        "true",
        "yes",
        "null",
        "~",
        "42",
        "1.5",
        "2026-07-26T16:41:38Z",
        "has: a colon",
        "trailing colon:",
        "hash # comment",
        "#ff8800",
        "- leading dash",
        "[bracketed]",
        "{braced}",
        "line\nbreak",
        "tab\there",
        "quote\"inside",
        "back\\slash",
        "*anchor",
        "&anchor",
        "!tagged",
        "%directive",
        "@at",
        "|pipe",
        ">gt",
        "'single'",
    ])
    func stringsNeedingQuotingSurviveAReparse(value: String) throws {
        var document = try FrontmatterDocument.parse("---\n---\n")
        document.set("title", to: .string(value))
        let reparsed = try FrontmatterDocument.parse(document.serialized())
        #expect(reparsed.title == .valid(value))
    }

    @Test func quotingIsUsedOnlyWhenNeeded() throws {
        #expect(try emittedTitle(.string("true")) == "\"true\"")
        #expect(try emittedTitle(.string("#ff8800")) == "\"#ff8800\"")
        #expect(try emittedTitle(.string("")) == "\"\"")
        #expect(try emittedTitle(.string("a\nb")) == "\"a\\nb\"")
        // An interior quote is legal in a plain scalar; a leading one is not.
        #expect(try emittedTitle(.string("say \"hi\"")) == "say \"hi\"")
        #expect(try emittedTitle(.string("\"quoted\"")) == "\"\\\"quoted\\\"\"")
        #expect(try emittedTitle(.string("back\\slash")) == "back\\slash")
    }

    @Test func rawValuesAreWrittenVerbatim() throws {
        var document = try FrontmatterDocument.parse("---\nschema: 1\n---\nbody\n")
        document.set("template", to: .raw("{order: 7}"))
        #expect(document.serialized() == "---\nschema: 1\ntemplate: {order: 7}\n---\nbody\n")
        #expect(document.value(for: "template")
            == .mapping([YAMLValue.Pair(key: .string("order"), value: .int(7))]))
    }
}

// MARK: - Appended lines and empty-but-not-blank blocks

struct FrontmatterAppendedLineTests {
    @Test func appendedKeyAdoptsCRLFInACRLFFile() throws {
        var document = try FrontmatterDocument.parse("---\r\nschema: 1\r\n---\r\nbody\r\n")
        document.set("title", to: .string("x"))
        #expect(document.serialized() == "---\r\nschema: 1\r\ntitle: x\r\n---\r\nbody\r\n")
    }

    @Test func appendedKeyStaysLFInAnLFFile() throws {
        var document = try FrontmatterDocument.parse("---\nschema: 1\n---\nbody\n")
        document.set("title", to: .string("x"))
        #expect(document.serialized() == "---\nschema: 1\ntitle: x\n---\nbody\n")
    }

    /// `{}`, `null`, `~` resolve to an empty mapping yet leave text no key owns — appending
    /// after it would be unparseable YAML, so the shape refuses edits (readable-but-uneditable).
    @Test(arguments: ["---\n{}\n---\nbody\n", "---\nnull\n---\nbody\n", "---\n~\n---\nbody\n"])
    func emptyButNotBlankFrontmatterIsUneditable(text: String) throws {
        #expect(try FrontmatterDocument.parse(text).uneditableShape == .keyWithoutOwnLine)
    }

    /// Truly blank or comment-only frontmatter stays editable — appending after it is exactly
    /// what `set` is for, and the result must reparse.
    @Test(arguments: ["---\n---\nbody\n", "---\n\n---\nbody\n", "---\n# just a comment\n---\nbody\n"])
    func blankOrCommentOnlyFrontmatterStaysEditable(text: String) throws {
        var document = try FrontmatterDocument.parse(text)
        #expect(document.uneditableShape == nil)
        document.set("schema", to: .int(1))
        let reparsed = try FrontmatterDocument.parse(document.serialized())
        #expect(reparsed.schema == .valid(1))
    }
}
