import Foundation
import Testing
@testable import Kanban

/// A window ref is two things at once — the value `WindowGroup(for:)` keys its windows on, and the
/// argument the host is built from — so it has exactly two promises: it survives the round trip
/// through a scene's archive, and two refs naming one thing are *equal*. Both are pinned here,
/// because a failure of either is invisible until the app opens a second window for something that
/// already has one.

@Suite("Window refs")
struct WindowRefsTests {

    // MARK: Codable

    @Test("Both refs round-trip through JSON unchanged")
    func refsRoundTripThroughCoding() throws {
        let board = BoardWindowRef(path: "/Users/x/Boards/Work.kanban")
        let card = CardWindowRef(boardPath: board.path, cardID: "55555555-5555-4555-8555-555555555555")

        let encoder = JSONEncoder()
        let decoder = JSONDecoder()

        #expect(try decoder.decode(BoardWindowRef.self, from: encoder.encode(board)) == board)

        let decodedCard = try decoder.decode(CardWindowRef.self, from: encoder.encode(card))
        #expect(decodedCard == card)
        #expect(decodedCard.cardID == card.cardID, "the folder's exact spelling survives, not just its value")
        #expect(decodedCard.boardPath == board.path)
    }

    @Test("A board ref and a URL agree in both directions")
    func boardRefAndURLAgree() {
        let url = URL(fileURLWithPath: "/Users/x/Boards/Work.kanban", isDirectory: true)
        let ref = BoardWindowRef(url: url)
        #expect(ref.path == url.path)
        #expect(ref.url.path == url.path)
    }

    // MARK: Identity

    @Test("A card ref's id compares by UUID value, not by spelling")
    func cardRefFoldsTheCaseOfItsCardID() {
        let board = "/Users/x/Boards/Work.kanban"
        let lower = CardWindowRef(boardPath: board, cardID: "55555555-5555-4555-8555-555555555555")
        let upper = CardWindowRef(boardPath: board, cardID: "55555555-5555-4555-8555-555555555555".uppercased())

        // The consequence that matters: `WindowGroup(for:)` keys on this equality, so a card whose
        // folder is spelled in caps focuses the window the lowercase spelling already opened instead
        // of opening a second one for the same card.
        #expect(lower == upper)
        #expect(lower.hashValue == upper.hashValue)
        #expect(Set([lower, upper]).count == 1)

        // And `rawValue` is untouched by any of that — it is what builds URLs.
        #expect(upper.cardID == "55555555-5555-4555-8555-555555555555".uppercased())
        #expect(upper.cardIdentity == lower.cardIdentity)
    }

    @Test("The board half of a card ref is compared verbatim")
    func cardRefsOnDifferentBoardsAreDifferent() {
        let card = "55555555-5555-4555-8555-555555555555"
        let here = CardWindowRef(boardPath: "/Users/x/Boards/Work.kanban", cardID: card)
        let there = CardWindowRef(boardPath: "/Users/x/Boards/Home.kanban", cardID: card)

        // The cross-board move rule in one assertion: the card's UUID travels with it, but the key
        // that named its window does not — so the window dismisses exactly like a delete.
        #expect(here != there)
        #expect(here.board == BoardWindowRef(path: "/Users/x/Boards/Work.kanban"))
        #expect(here.boardURL.path == "/Users/x/Boards/Work.kanban")
    }

    @Test("A card ref built from an ItemID keeps the id's exact spelling")
    func cardRefFromItemIDKeepsRawValue() {
        let board = BoardWindowRef(path: "/Users/x/Boards/Work.kanban")
        let id = ItemID(rawValue: "AAAAAAAA-AAAA-4AAA-8AAA-AAAAAAAAAAAA")
        let ref = CardWindowRef(board: board, cardID: id)

        #expect(ref.cardID == id.rawValue)
        #expect(ref == CardWindowRef(boardPath: board.path, cardID: id.rawValue.lowercased()))
    }
}
