-- HousingDecorGuide/tests/test_fingerprint_matcher.lua
---@diagnostic disable: undefined-global
-- ^ Standalone Lua test file — `package` / `require` / `dofile` are valid here even though
-- the project's WoW-context .luarc.json disables them.
package.path = "./?.lua;../Core/?.lua;" .. package.path

local FP = dofile("../Core/HDG_FingerprintMatcher.lua")
local t = require("test_helpers")

t.describe("HDG_FingerprintMatcher.facingToCardinal", function()
    t.it("0 → N", function() t.assert_eq(FP.facingToCardinal(0), "N") end)
    t.it("~2π → N", function() t.assert_eq(FP.facingToCardinal(6.283), "N") end)
    t.it("π/2 → E", function() t.assert_eq(FP.facingToCardinal(math.pi / 2), "E") end)
    t.it("~5π/2 → E (modular wraparound)", function()
        t.assert_eq(FP.facingToCardinal(7.853), "E")
    end)
    t.it("π → S", function() t.assert_eq(FP.facingToCardinal(math.pi), "S") end)
    t.it("3π/2 → W", function() t.assert_eq(FP.facingToCardinal(3 * math.pi / 2), "W") end)
end)

t.describe("HDG_FingerprintMatcher.computeFingerprint", function()
    t.it("Octagon Medium with 4 occupied doors, all cardinals", function()
        local fp = FP.computeFingerprint({
            shape = "octagon_m",
            doors = {
                { facing = 0, occupied = true },              -- N
                { facing = math.pi / 2, occupied = true },    -- E
                { facing = math.pi, occupied = true },        -- S
                { facing = 3 * math.pi / 2, occupied = true } -- W
            },
        })
        t.assert_eq(fp.shape, "octagon_m")
        t.assert_eq(fp.degree, 4)
        t.assert_table_eq(fp.doors, { "E", "N", "S", "W" })          -- sorted alphabetically
        t.assert_table_eq(fp.occupiedDoors, { "E", "N", "S", "W" })
    end)
    t.it("L-Shape with one open door and one occupied", function()
        local fp = FP.computeFingerprint({
            shape = "l_shape",
            doors = {
                { facing = 0, occupied = true },                  -- N occupied
                { facing = 3 * math.pi / 2, occupied = false },   -- W open
            },
        })
        t.assert_eq(fp.degree, 1)
        t.assert_table_eq(fp.doors, { "N", "W" })
        t.assert_table_eq(fp.occupiedDoors, { "N" })
    end)
    t.it("equality: identical inputs produce identical fingerprint hashes", function()
        local a = FP.computeFingerprint({ shape = "closet_xs",
            doors = { { facing = 0, occupied = true }, { facing = math.pi, occupied = true } } })
        local b = FP.computeFingerprint({ shape = "closet_xs",
            doors = { { facing = math.pi, occupied = true }, { facing = 0, occupied = true } } })
        t.assert_eq(FP.fingerprintHash(a), FP.fingerprintHash(b))
    end)
    t.it("inequality: different shapes produce different hashes", function()
        local a = FP.computeFingerprint({ shape = "closet_xs",
            doors = { { facing = 0, occupied = true } } })
        local b = FP.computeFingerprint({ shape = "square_s",
            doors = { { facing = 0, occupied = true } } })
        t.assert_neq(FP.fingerprintHash(a), FP.fingerprintHash(b))
    end)
end)

t.describe("HDG_FingerprintMatcher.matchRooms", function()
    -- Helper to create a captured room
    local function cap(id, shape, doors)
        return { capturedID = id, shape = shape, doors = doors }
    end
    -- Helper to create existing room (already has a fingerprint)
    local function existing(id, fp)
        return { existingID = id, fingerprint = fp }
    end

    t.it("matches a single unambiguous fingerprint", function()
        local cross_fp = FP.computeFingerprint({ shape = "cross_shape",
            doors = { { facing = 0, occupied = true }, { facing = math.pi/2, occupied = true },
                      { facing = math.pi, occupied = true }, { facing = 3*math.pi/2, occupied = true } } })
        local capList = { cap("c1", "cross_shape", {
            { facing = 0, occupied = true }, { facing = math.pi/2, occupied = true },
            { facing = math.pi, occupied = true }, { facing = 3*math.pi/2, occupied = true } }) }
        local exList = { existing("house:alliance:f1:r7a3f", cross_fp) }

        local r = FP.matchRooms(capList, exList)
        t.assert_eq(#r.matched, 1)
        t.assert_eq(r.matched[1].capturedID, "c1")
        t.assert_eq(r.matched[1].existingID, "house:alliance:f1:r7a3f")
        t.assert_eq(#r.newRooms, 0)
        t.assert_eq(#r.orphans, 0)
        t.assert_eq(#r.ambiguous, 0)
    end)

    t.it("flags ambiguous matches when 2 captured rooms share fingerprint with 2 existing", function()
        local lshape_fp = FP.computeFingerprint({ shape = "l_shape",
            doors = { { facing = 0, occupied = true }, { facing = 3*math.pi/2, occupied = false } } })
        local capList = {
            cap("c1", "l_shape", { { facing = 0, occupied = true }, { facing = 3*math.pi/2, occupied = false } }),
            cap("c2", "l_shape", { { facing = 0, occupied = true }, { facing = 3*math.pi/2, occupied = false } }),
        }
        local exList = {
            existing("house:alliance:f1:rAAAA", lshape_fp),
            existing("house:alliance:f1:rBBBB", lshape_fp),
        }

        local r = FP.matchRooms(capList, exList)
        t.assert_eq(#r.ambiguous, 2)
        for _, a in ipairs(r.ambiguous) do
            t.assert_eq(#a.candidates, 2)
        end
    end)

    t.it("identifies new rooms (captured, no existing match)", function()
        local capList = { cap("c1", "closet_xs",
            { { facing = 0, occupied = true }, { facing = math.pi, occupied = true } }) }
        local exList = {}

        local r = FP.matchRooms(capList, exList)
        t.assert_eq(#r.newRooms, 1)
        t.assert_eq(r.newRooms[1].capturedID, "c1")
    end)

    t.it("identifies orphans (existing, no captured match — demolished room)", function()
        local fp = FP.computeFingerprint({ shape = "closet_xs",
            doors = { { facing = 0, occupied = true }, { facing = math.pi, occupied = true } } })
        local capList = {}
        local exList = { existing("house:alliance:f1:rZZZZ", fp) }

        local r = FP.matchRooms(capList, exList)
        t.assert_eq(#r.orphans, 1)
        t.assert_eq(r.orphans[1].existingID, "house:alliance:f1:rZZZZ")
    end)
end)

t.run_all()
