import Foundation
import Testing
@testable import Kanban

/// The write half of the Theme tab's two picture-adjacent gestures: `BoardWriter.writeBoardImage` and
/// `BoardStore.applyGeneratedBackground` for Pattern, `BoardStore.applySolidBackground` for Solid
/// color (03-board-ui.md § Board popover ▸ Theme tab; DESIGN/explorations/board-backgrounds.md).
///
/// Like every other write suite here these drive a real writer or a real store over a real temp
/// board and read the **bytes on disk** back rather than the app's own read path: the claims are
/// about the file — which name the picture landed under, what the frontmatter says afterwards, and
/// what an undo leaves behind. `WriterFixture`, `Ident` and `Item` come from
/// `WriterTestSupport.swift`.

// MARK: - Fixtures

/// A board root carrying whatever background the test needs, plus the unowned baggage every write
/// has to leave alone.
private func boardIndex(background: String? = nil) -> String {
    let line = background.map { "background: \($0)\n" } ?? ""
    return """
    ---
    schema: 1
    title: Work
    \(line)project: lanework       # agent overlay
    created: 2026-01-01T09:00:00Z
    ---
    Board description.

    """
}

@MainActor
private func makeBoard(background: String? = nil) throws -> WriterFixture {
    let fixture = try WriterFixture()
    try fixture.item("", boardIndex(background: background))
    try fixture.item(Ident.lane1, Item.rich(order: "1024", title: "Todo"))
    try fixture.item("\(Ident.lane1)/\(Ident.card1)", Item.rich(order: "1024", title: "First"))
    return fixture
}

@MainActor
private func makeStore(_ fixture: WriterFixture) throws -> (store: BoardStore, history: NativeHistoryProvider) {
    let store = try BoardStore(rootURL: fixture.root)
    let history = NativeHistoryProvider()
    store.history = history
    return (store, history)
}

@MainActor
private func reload(_ store: BoardStore) async {
    store.handleWatcherEvent(.treeChanged(.appMediated))
    await store.awaitQuiescence()
}

private func document(_ fixture: WriterFixture) throws -> FrontmatterDocument {
    try FrontmatterDocument.parse(fixture.indexText(""))
}

/// Bytes that are not an image and do not need to be: nothing in the write path decodes them, which
/// is itself worth pinning — the Writer moves a payload, it does not validate artwork.
private let png = Data([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x01, 0x02, 0x03])
private let otherPNG = Data([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x09, 0x09])

// MARK: - The writer primitive

@MainActor
@Suite("BoardWriter ▸ writeBoardImage")
struct WriteBoardImageTests {

    @Test("The bytes land under the given name, and the name comes back")
    func writesTheBytes() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }

        let name = try BoardWriter.writeBoardImage(
            data: png, named: "facets.png", inRoot: fixture.root, operation: .setBoardBackground
        )

        #expect(name == "facets.png")
        #expect(try fixture.data("facets.png") == png)
    }

    /// The caller decides the name, so the writer's own contract is simply that the same name is
    /// replaced rather than laddered — one board, one generated picture.
    @Test("A second write to the same name replaces it in place")
    func overwritesInPlace() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }

        try BoardWriter.writeBoardImage(
            data: png, named: "facets.png", inRoot: fixture.root, operation: .setBoardBackground
        )
        try BoardWriter.writeBoardImage(
            data: otherPNG, named: "facets.png", inRoot: fixture.root, operation: .setBoardBackground
        )

        #expect(try fixture.data("facets.png") == otherPNG)
        #expect(try fixture.entryNames("") == [Ident.lane1, "facets.png", "index.md"])
    }

    /// **Atomic, and no residue**: the temp file is hidden and in the same folder, so a listing that
    /// sees hidden entries is what proves the rename left nothing behind.
    @Test("No temp file survives the write")
    func leavesNoResidue() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }

        try BoardWriter.writeBoardImage(
            data: png, named: "facets.png", inRoot: fixture.root, operation: .setBoardBackground
        )

        #expect(try !fixture.entryNames("").contains { $0.hasPrefix(".") })
    }

    /// The mirror of the read side's containment rule (`BoardBackdrop.imageURL(named:inBoardRoot:)`):
    /// a background that could be written outside the board folder is not a background.
    @Test("A path, an empty name and the dot names are refused", arguments: ["", "art/x.png", "../x.png", ".", ".."])
    func refusesAnythingThatIsNotABareName(name: String) throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }

        #expect(throws: BoardWriteError.self) {
            try BoardWriter.writeBoardImage(
                data: png, named: name, inRoot: fixture.root, operation: .setBoardBackground
            )
        }
        #expect(try fixture.entryNames("") == [Ident.lane1, "index.md"])
    }

    /// The receipt: without one the churn the write produces classifies as somebody else's, and the
    /// announcer — or any future ledger consumer — would attribute the app's own write to a foreign
    /// editor.
    @Test("The write leaves a content receipt in the ledger")
    func dropsAReceipt() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let ledger = EchoLedger()

        EchoLedger.$current.withValue(ledger) {
            try? BoardWriter.writeBoardImage(
                data: png, named: "facets.png", inRoot: fixture.root, operation: .setBoardBackground
            )
        }

        let receipts = ledger.outstandingEntries()
        #expect(receipts.contains { $0.key.hasSuffix("/facets.png") })
    }

    /// **`.backgrounds/` did not exist before this ruling** (2026-08-09), so the first board on a
    /// Mac to ever generate or paste a background hands this call a folder nothing has made yet — the
    /// call has to make it rather than fail, or every board's very first background write would.
    @Test("The destination folder is created when it does not exist yet")
    func createsTheDestinationFolderWhenMissing() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let backgroundsFolder = fixture.root.appendingPathComponent(
            BoardBackdrop.backgroundsFolderName, isDirectory: true
        )

        let name = try BoardWriter.writeBoardImage(
            data: png, named: "facets.png", inRoot: backgroundsFolder, operation: .setBoardBackground
        )

        #expect(name == "facets.png")
        #expect(try fixture.data("\(BoardBackdrop.backgroundsFolderName)/facets.png") == png)
    }
}

// MARK: - The gesture

@MainActor
@Suite("BoardStore ▸ applyGeneratedBackground")
struct GeneratedBackgroundWriteTests {

    @Test("The picture lands in .backgrounds/ and both subkeys point at it")
    func writesTheFileAndTheFields() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let (store, _) = try makeStore(fixture)

        #expect(store.applyGeneratedBackground(png: png, colorHex: "#E0E5EB"))

        #expect(try fixture.data(".backgrounds/facets.png") == png)
        let after = try document(fixture)
        #expect(after.background == .valid("#E0E5EB"))
        #expect(after.backgroundImage == .valid(".backgrounds/facets.png"))
        #expect(try fixture.indexText("")
            .contains("background: {color: \"#E0E5EB\", image: \".backgrounds/facets.png\"}"))
        #expect(store.banners.oneShots.isEmpty)
    }

    /// One gesture, one bracket — the style batch's rule, which is what makes one reroll one
    /// app-mediated reload, though it writes two files.
    @Test("Two files, one bracket")
    func oneBracketForBothFiles() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let (store, _) = try makeStore(fixture)
        var begins = 0
        var ends = 0
        store.watcherBrackets = (begin: { begins += 1 }, end: { ends += 1 })

        store.applyGeneratedBackground(png: png, colorHex: "#E0E5EB")

        #expect(begins == 1)
        #expect(ends == 1)
    }

    /// A colour the wells wrote is replaced, and everything the app does not own comes through
    /// untouched — the unknown key with its comment, `created`, the body.
    @Test("An existing colour is replaced and the rest of the file survives")
    func replacesAnExistingColour() throws {
        let fixture = try makeBoard(background: "{color: fern}")
        defer { fixture.tearDown() }
        let (store, _) = try makeStore(fixture)

        store.applyGeneratedBackground(png: png, colorHex: "#513D1A")

        let text = try fixture.indexText("")
        #expect(text.contains("background: {color: \"#513D1A\", image: \".backgrounds/facets.png\"}"))
        #expect(text.contains("project: lanework       # agent overlay"))
        #expect(text.contains("created: 2026-01-01T09:00:00Z"))
        #expect(text.contains("Board description."))
    }

    /// **Regenerating overwrites**: the whole reason the name is fixed rather than minted. The reload
    /// between the two rolls is the ordinary case — the snapshot has caught up and names the file.
    @Test("A second generation replaces the same file")
    func secondGenerationOverwrites() async throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let (store, _) = try makeStore(fixture)

        store.applyGeneratedBackground(png: png, colorHex: "#E0E5EB")
        await reload(store)
        store.applyGeneratedBackground(png: otherPNG, colorHex: "#513D1A")

        #expect(try fixture.data(".backgrounds/facets.png") == otherPNG)
        #expect(try document(fixture).backgroundImage == .valid(".backgrounds/facets.png"))
        // No ladder: `.backgrounds/` holds exactly the one picture this store ever wrote to it.
        #expect(try fixture.entryNames(".backgrounds") == ["facets.png"])
    }

    /// **The reroll's echo**: rolling again before the watcher has rounded the first write back must
    /// not ladder onto `facets 2.png`, because a fast reroll is the expected gesture and a folder of
    /// abandoned pictures is what the fixed name exists to prevent.
    @Test("A reroll before the reload lands still overwrites")
    func rerollBeforeTheReloadOverwrites() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let (store, _) = try makeStore(fixture)

        store.applyGeneratedBackground(png: png, colorHex: "#E0E5EB")
        store.applyGeneratedBackground(png: otherPNG, colorHex: "#513D1A")

        #expect(try fixture.data(".backgrounds/facets.png") == otherPNG)
        #expect(try fixture.entryNames("") == [".backgrounds", Ident.lane1, "index.md"])
    }

    /// **Somebody else's `.backgrounds/facets.png` is never written through** — a file the user put in
    /// that folder is theirs, and the Finder ladder is how the app steps aside from a name it does not
    /// own. A same-named file at board root — the legacy location — is a different question entirely
    /// (`aLegacyBareReferenceIsNotOverwrittenInPlace`, below): it no longer sits anywhere this write
    /// ever looks.
    @Test("A foreign file on the name pushes the generation to '.backgrounds/facets 2.png'")
    func stepsAsideFromAForeignFile() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let mine = Data("not the app's".utf8)
        try fixture.file(".backgrounds/facets.png", mine)
        let (store, _) = try makeStore(fixture)

        store.applyGeneratedBackground(png: png, colorHex: "#E0E5EB")

        #expect(try fixture.data(".backgrounds/facets.png") == mine, "the user's file is untouched")
        #expect(try fixture.data(".backgrounds/facets 2.png") == png)
        #expect(try document(fixture).backgroundImage == .valid(".backgrounds/facets 2.png"))
    }

    /// The same ladder when the board already names a *different* image: the hand-written path is
    /// the escape hatch and stays on disk, and the generation lands beside it — inside `.backgrounds/`,
    /// composing with the collision-ladder test above.
    @Test("A board naming another image keeps it and generates alongside")
    func keepsAHandWrittenImage() throws {
        let fixture = try makeBoard(background: "{image: sunset.jpg}")
        defer { fixture.tearDown() }
        try fixture.file("sunset.jpg", Data("photo".utf8))
        try fixture.file(".backgrounds/facets.png", Data("someone else's".utf8))
        let (store, _) = try makeStore(fixture)

        store.applyGeneratedBackground(png: png, colorHex: "#E0E5EB")

        #expect(try fixture.data("sunset.jpg") == Data("photo".utf8))
        #expect(try document(fixture).backgroundImage == .valid(".backgrounds/facets 2.png"))
    }

    /// A board whose generated file was deleted in Finder is a board with a broken backdrop, and
    /// regenerating is exactly the repair — so the name is reused rather than laddered, **when the
    /// reference is already the qualified `.backgrounds/` one this scheme writes**.
    @Test("A missing file under our own qualified name is rewritten, not laddered")
    func rewritesAMissingFile() throws {
        let fixture = try makeBoard(background: "{color: fern, image: .backgrounds/facets.png}")
        defer { fixture.tearDown() }
        let (store, _) = try makeStore(fixture)

        store.applyGeneratedBackground(png: png, colorHex: "#E0E5EB")

        #expect(try fixture.data(".backgrounds/facets.png") == png)
        #expect(try document(fixture).backgroundImage == .valid(".backgrounds/facets.png"))
    }

    /// **Legacy stays where it is — no migration** (ruled 2026-08-09). A board whose `background.image`
    /// still names a bare `facets.png` at board root — the shape every board carried before this
    /// ruling — does not read as "ours to overwrite in place": only the qualified `.backgrounds/`
    /// spelling does (`rewritesAMissingFile`, above). So a regeneration on a legacy board writes a
    /// fresh `.backgrounds/facets.png` rather than touching the root-level file the field used to name
    /// — even though nothing is actually there to protect in this case (the field names a file that
    /// was never created), the point is the *reference's shape* decides, not disk contents.
    @Test("A legacy bare reference is left alone; the regeneration writes a fresh .backgrounds/ file")
    func aLegacyBareReferenceIsNotOverwrittenInPlace() throws {
        let fixture = try makeBoard(background: "{color: fern, image: facets.png}")
        defer { fixture.tearDown() }
        let (store, _) = try makeStore(fixture)

        store.applyGeneratedBackground(png: png, colorHex: "#E0E5EB")

        #expect(try fixture.data(".backgrounds/facets.png") == png)
        #expect(try document(fixture).backgroundImage == .valid(".backgrounds/facets.png"))
        #expect(!fixture.exists("facets.png"), "no legacy file was ever created — nothing to leave behind")
    }

    /// A locked board writes nothing at all — not the picture, not the fields.
    @Test("A read-only board refuses before anything is written")
    func refusesUnderTheLock() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let (store, _) = try makeStore(fixture)
        store.enterVanishedRootLock()

        #expect(store.applyGeneratedBackground(png: png, colorHex: "#E0E5EB") == false)
        #expect(!fixture.exists(".backgrounds/facets.png"))
        #expect(try document(fixture).backgroundImage == .missing)
    }
}

// MARK: - Undo

@MainActor
@Suite("Undo ▸ generated background")
struct GeneratedBackgroundUndoTests {

    /// The first generation's undo is a clean return: the board had no background, and afterwards it
    /// has none again. (The PNG stays in the folder — nothing in the app deletes the user's files —
    /// and nothing points at it.)
    @Test("Undo removes both subkeys and redo puts them back")
    func roundTrip() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let (store, history) = try makeStore(fixture)

        store.applyGeneratedBackground(png: png, colorHex: "#E0E5EB")
        #expect(history.undoActionName == "Restyle Board")

        history.undo()
        let undone = try document(fixture)
        #expect(undone.background == .missing)
        #expect(undone.backgroundImage == .missing)
        #expect(!undone.contains(FrontmatterKeys.background))

        history.redo()
        let redone = try document(fixture)
        #expect(redone.background == .valid("#E0E5EB"))
        #expect(redone.backgroundImage == .valid(".backgrounds/facets.png"))
    }

    /// A prior colour comes back as itself rather than as an absence — the same reading `applyStyle`'s
    /// inverse has.
    @Test("A prior colour and image are restored, not removed")
    func priorValuesComeBack() throws {
        let fixture = try makeBoard(background: "{color: fern, image: sunset.jpg}")
        defer { fixture.tearDown() }
        try fixture.file("sunset.jpg", Data("photo".utf8))
        let (store, history) = try makeStore(fixture)

        store.applyGeneratedBackground(png: png, colorHex: "#E0E5EB")
        history.undo()

        let undone = try document(fixture)
        #expect(undone.background == .valid("fern"))
        #expect(undone.backgroundImage == .valid("sunset.jpg"))
    }

    /// **The undo restores fields, never bytes** — stated as a test so the limit is visible rather
    /// than folklore: regenerating over the app's own output leaves the second picture on disk, and
    /// ⌘Z points the (unchanged) name back at it.
    @Test("Undo does not bring the overwritten pixels back")
    func undoDoesNotRestoreBytes() async throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let (store, history) = try makeStore(fixture)

        store.applyGeneratedBackground(png: png, colorHex: "#E0E5EB")
        await reload(store)
        store.applyGeneratedBackground(png: otherPNG, colorHex: "#513D1A")

        history.undo()
        #expect(try document(fixture).background == .valid("#E0E5EB"))
        #expect(try document(fixture).backgroundImage == .valid(".backgrounds/facets.png"))
        #expect(try fixture.data(".backgrounds/facets.png") == otherPNG, "the first generation's bytes are gone")
    }

    /// A foreign edit to the field the step wrote stales it — the field-level predicate, applied to
    /// the subkey this gesture owns.
    @Test("A foreign edit to the image subkey skips the undo")
    func foreignEditStalesTheStep() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let (store, history) = try makeStore(fixture)

        store.applyGeneratedBackground(png: png, colorHex: "#E0E5EB")

        var foreign = try document(fixture)
        foreign.setBackgroundImage("elsewhere.png")
        try fixture.item("", foreign.serialized())

        history.undo()
        #expect(try document(fixture).backgroundImage == .valid("elsewhere.png"))
        #expect(store.banners.signposts.isEmpty == false, "the skip says so on the strip")
    }
}

// MARK: - Solid background

/// The Theme tab's Solid color half: `BoardStore.applySolidBackground` (03-board-ui.md § Board
/// popover ▸ Theme tab; `BoardThemeTabView.applySolid`). Modeled line-for-line on
/// `applyGeneratedBackground` minus the file write, so these suites mirror the write and undo suites
/// above with the one difference the method itself has: no picture, and `facets.png` — when there is
/// one — is deliberately left on disk rather than deleted.
@MainActor
@Suite("BoardStore ▸ applySolidBackground")
struct SolidBackgroundWriteTests {

    @Test("The colour lands and there is no image subkey to point anywhere")
    func writesTheColourAloneOnAPlainBoard() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let (store, _) = try makeStore(fixture)

        #expect(store.applySolidBackground(colorHex: "#E0E5EB"))

        let after = try document(fixture)
        #expect(after.background == .valid("#E0E5EB"))
        #expect(after.backgroundImage == .missing)
        #expect(try fixture.indexText("").contains("background: {color: \"#E0E5EB\"}"))
        #expect(store.banners.oneShots.isEmpty)
    }

    /// The write's whole point on a board that already carries a generated picture: the colour
    /// changes, the `image` subkey goes, and every other subkey the app does not own rides through
    /// untouched.
    @Test("An existing image subkey is removed and unrelated subkeys survive")
    func removesTheImageSubkeyAndKeepsForeignOnes() throws {
        let fixture = try makeBoard(background: "{blend: multiply, color: fern, image: facets.png, opacity: 0.5}")
        defer { fixture.tearDown() }
        let (store, _) = try makeStore(fixture)

        store.applySolidBackground(colorHex: "#513D1A")

        let text = try fixture.indexText("")
        #expect(text.contains("blend: \"multiply\""))
        #expect(text.contains("color: \"#513D1A\""))
        #expect(text.contains("opacity: 0.5"))
        #expect(!text.contains("image:"))
        let after = try document(fixture)
        #expect(after.background == .valid("#513D1A"))
        #expect(after.backgroundImage == .missing)
    }

    /// **The echo-window case**: back to back with no reload in between, `snapshot` has not caught up
    /// with the generation this solid choice is about to unset, so the trim it would otherwise make
    /// (`SolidBackgroundOrphanTrimTests.trimsTheGeneratedFileOnceSettled`) sees a `nil` prior and does
    /// nothing — the same staleness gate `theTwoProducersStayApart` pins for the paste path.
    @Test(".backgrounds/facets.png survives an unsettled solid choice")
    func leavesTheGeneratedFileOnDiskBeforeTheReloadLands() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let (store, _) = try makeStore(fixture)
        store.applyGeneratedBackground(png: png, colorHex: "#E0E5EB")

        store.applySolidBackground(colorHex: "#513D1A")

        #expect(try fixture.data(".backgrounds/facets.png") == png, "the bytes are untouched")
        #expect(try document(fixture).backgroundImage == .missing, "only the field is gone")
    }

    @Test("One bracket for the one file it touches")
    func oneBracketForTheWrite() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let (store, _) = try makeStore(fixture)
        var begins = 0
        var ends = 0
        store.watcherBrackets = (begin: { begins += 1 }, end: { ends += 1 })

        store.applySolidBackground(colorHex: "#E0E5EB")

        #expect(begins == 1)
        #expect(ends == 1)
    }

    /// A locked board writes nothing at all.
    @Test("A read-only board refuses before anything is written")
    func refusesUnderTheLock() throws {
        let fixture = try makeBoard(background: "{color: fern}")
        defer { fixture.tearDown() }
        let (store, _) = try makeStore(fixture)
        store.enterVanishedRootLock()

        #expect(store.applySolidBackground(colorHex: "#E0E5EB") == false)
        #expect(try document(fixture).background == .valid("fern"))
    }
}

@MainActor
@Suite("Undo ▸ solid background")
struct SolidBackgroundUndoTests {

    /// The first solid colour's undo is a clean return: the board had no background, and afterwards
    /// it has none again.
    @Test("Undo removes the colour and redo puts it back")
    func roundTrip() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let (store, history) = try makeStore(fixture)

        store.applySolidBackground(colorHex: "#E0E5EB")
        #expect(history.undoActionName == "Restyle Board")

        history.undo()
        let undone = try document(fixture)
        #expect(undone.background == .missing)
        #expect(undone.backgroundImage == .missing)
        #expect(!undone.contains(FrontmatterKeys.background))

        history.redo()
        let redone = try document(fixture)
        #expect(redone.background == .valid("#E0E5EB"))
        #expect(redone.backgroundImage == .missing)
    }

    /// **Both prior fields come back** — the colour a board had before, and the generated image the
    /// solid choice pointed away from. `facets.png` here is a **legacy** bare reference at board root
    /// (`aLegacyBareReferenceIsNotOverwrittenInPlace`'s own shape), which the orphan tidy never
    /// touches — "Tidy scopes to `.backgrounds/` ONLY" — so the file surviving on disk is guaranteed
    /// rather than merely likely, and an undo with nothing to point back at would make the surviving
    /// bytes an orphan from the moment they landed.
    @Test("A prior colour and generated image are both restored")
    func restoresAPriorGeneratedImage() throws {
        let fixture = try makeBoard(background: "{color: fern, image: facets.png}")
        defer { fixture.tearDown() }
        try fixture.file("facets.png", png)
        let (store, history) = try makeStore(fixture)

        store.applySolidBackground(colorHex: "#E0E5EB")
        history.undo()

        let undone = try document(fixture)
        #expect(undone.background == .valid("fern"))
        #expect(undone.backgroundImage == .valid("facets.png"))
        #expect(try fixture.data("facets.png") == png, "undo restored the field, not new bytes")
    }

    /// A foreign edit to the field the step wrote stales it — the same field-level predicate
    /// `GeneratedBackgroundUndoTests.foreignEditStalesTheStep` pins for the generated path, applied to
    /// the colour subkey this gesture owns.
    @Test("A foreign edit to the colour subkey skips the undo")
    func foreignEditStalesTheStep() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let (store, history) = try makeStore(fixture)

        store.applySolidBackground(colorHex: "#E0E5EB")

        var foreign = try document(fixture)
        foreign.setStyleValue("obsidian", for: FrontmatterKeys.background)
        try fixture.item("", foreign.serialized())

        history.undo()
        #expect(try document(fixture).background == .valid("obsidian"))
        #expect(store.banners.signposts.isEmpty == false, "the skip says so on the strip")
    }
}

// MARK: - The orphan tidy ▸ replace-in-place (ruled 2026-08-09)

/// The replace-in-place half of the orphan tidy: once a background apply's own writes have landed, a
/// **settled** prior `.backgrounds/` file it repointed or unset away from is trimmed as part of the
/// same bracket (`BoardStore.tidyReplacedBackgroundImage`). "Settled" is the operative word throughout
/// — every unsettled (no-reload-yet) counterpart already lives beside its sibling test:
/// `leavesTheGeneratedFileOnDiskBeforeTheReloadLands` (solid) and
/// `PasteBoardBackgroundTests.theTwoProducersStayApart` (paste after generate).
@MainActor
@Suite("BoardStore ▸ the orphan tidy's replace-in-place trim")
struct BackgroundReplaceInPlaceTrimTests {

    /// The ruling's own headline case: choosing a solid colour over a *settled* generated background
    /// now trims the picture the generator wrote, superseding the earlier "facets.png survives"
    /// contract (`applySolidBackground`'s own doc comment carries the history).
    @Test(".backgrounds/facets.png is trimmed once a solid choice has settled")
    func trimsTheGeneratedFileOnceSettled() async throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let (store, _) = try makeStore(fixture)
        store.applyGeneratedBackground(png: png, colorHex: "#E0E5EB")
        await reload(store)

        store.applySolidBackground(colorHex: "#513D1A")

        #expect(!fixture.exists(".backgrounds/facets.png"), "trimmed as part of the solid write")
        #expect(try document(fixture).backgroundImage == .missing)
        #expect(store.banners.losses.isEmpty, "the in-flow trim is silent — the ruling's temp-file posture")
        #expect(store.banners.oneShots.isEmpty)
    }

    /// Switching producers the other way — a settled generate landing after a settled paste — trims
    /// the pasted file, `PasteBoardBackgroundTests.theTwoProducersStayApart`'s settled counterpart.
    @Test("A settled generate trims a differently-named prior .backgrounds/ file")
    func trimsAPriorFileOfADifferentName() async throws {
        let fixture = try makeBoard(background: "{image: .backgrounds/Pasted Background.png}")
        defer { fixture.tearDown() }
        try fixture.file(".backgrounds/Pasted Background.png", Data("pasted".utf8))
        let (store, _) = try makeStore(fixture)
        await reload(store)

        store.applyGeneratedBackground(png: png, colorHex: "#E0E5EB")

        #expect(!fixture.exists(".backgrounds/Pasted Background.png"), "trimmed by the generate that superseded it")
        #expect(try fixture.data(".backgrounds/facets.png") == png)
        #expect(try document(fixture).backgroundImage == .valid(".backgrounds/facets.png"))
        #expect(store.banners.losses.isEmpty)
    }

    /// **A regeneration over the app's own fixed name never trims anything** — there is no "old" file
    /// distinct from the new one; `writeBoardImage`'s overwrite-in-place already lands the new bytes on
    /// the very name the trim would otherwise have removed.
    @Test("Regenerating the same name trims nothing")
    func regeneratingTheSameNameTrimsNothing() async throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let (store, _) = try makeStore(fixture)
        store.applyGeneratedBackground(png: png, colorHex: "#E0E5EB")
        await reload(store)

        store.applyGeneratedBackground(png: otherPNG, colorHex: "#513D1A")

        #expect(try fixture.entryNames(".backgrounds") == ["facets.png"], "one file, the same name, overwritten")
        #expect(try fixture.data(".backgrounds/facets.png") == otherPNG)
    }

    /// **Legacy stays where it is, settled or not** — "Tidy scopes to `.backgrounds/` ONLY", so a bare
    /// root-level reference this tidy cannot prove is app-written survives every gesture that repoints
    /// or unsets it, whether or not a reload landed first.
    @Test("A legacy root-level prior survives a settled repoint")
    func aLegacyPriorSurvivesASettledRepoint() async throws {
        let fixture = try makeBoard(background: "{image: facets.png}")
        defer { fixture.tearDown() }
        try fixture.file("facets.png", Data("legacy".utf8))
        let (store, _) = try makeStore(fixture)
        await reload(store)

        store.applySolidBackground(colorHex: "#E0E5EB")

        #expect(try fixture.data("facets.png") == Data("legacy".utf8), "never touched — not app-written by construction")
    }

    /// The ruling's "temp-file posture", proven rather than merely swallowed: an old file the app
    /// cannot remove (here, one the filesystem itself refuses to touch) refuses the trim, and the
    /// gesture the user actually asked for — landing the new background — still succeeds with no
    /// banner naming a file the user never saw.
    @Test("A trim that cannot land does not fail or announce the gesture that triggered it")
    func aFailedTrimDoesNotSurface() async throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let (store, _) = try makeStore(fixture)
        store.applyGeneratedBackground(png: png, colorHex: "#E0E5EB")
        await reload(store)
        let oldFile = fixture.root.appendingPathComponent(".backgrounds/facets.png")
        // `uchg` — the immutable flag — makes even the owner unable to rename or remove this one
        // file, while the folder around it stays perfectly writable for the new picture landing in it.
        try FileManager.default.setAttributes([.immutable: true], ofItemAtPath: oldFile.path)
        defer { try? FileManager.default.setAttributes([.immutable: false], ofItemAtPath: oldFile.path) }

        let landed = store.applyPastedBackground(data: otherPNG, fileExtension: "png")

        #expect(landed, "the pasted picture and the field both still land")
        #expect(try fixture.data(".backgrounds/Pasted Background.png") == otherPNG)
        #expect(try document(fixture).backgroundImage == .valid(".backgrounds/Pasted Background.png"))
        #expect(fixture.exists(".backgrounds/facets.png"), "the immutable file could not be trimmed")
        #expect(store.banners.oneShots.isEmpty, "a swallowed trim failure names nothing")
        #expect(store.banners.losses.isEmpty)
    }
}
