-- Paths smoke: state-path notation + matching.
-- Covers single-path Matches, set-level MatchesAny, Join (with nil/empty
-- segment skipping), Parse, and Union (with "*" sentinel collapse).

dofile("wow_mock.lua")
dofile("../Core/HDGR_Paths.lua")

local Paths = HDG.Paths

local function assertEq(actual, expected, label)
    if actual ~= expected then
        error(label .. ": expected " .. tostring(expected) .. ", got " .. tostring(actual), 2)
    end
end
local function assertTrue(v, label) if not v then error(label, 2) end end
local function assertFalse(v, label) if v then error(label .. " (expected false, got truthy)", 2) end end

-- ===== Matches: equality + parent + child =====
assertTrue(Paths.Matches("a", "a"), "exact equality")
assertTrue(Paths.Matches("a.b", "a.b"), "exact equality multi-segment")
assertTrue(Paths.Matches("a", "a.b"), "parent of child")
assertTrue(Paths.Matches("a.b", "a"), "child of parent")
assertTrue(Paths.Matches("a.b", "a.b.c.d"), "deep child")
assertTrue(Paths.Matches("account.collection", "account.collection.decorCatalog"),
    "real-world cold-state parent match")
assertTrue(Paths.Matches("session.ui.decor.searchQuery",
    "session.ui.decor"), "hot-state child match")

-- ===== Matches: sibling segments must NOT match =====
assertFalse(Paths.Matches("a", "b"), "different top-level")
assertFalse(Paths.Matches("a.b", "a.c"), "different sub-segments")
assertFalse(Paths.Matches("account.col", "account.collection"),
    "segment-aware -- 'col' is NOT prefix of 'collection' as a segment")
assertFalse(Paths.Matches("account.collection", "account.collections"),
    "segment-aware -- 'collection' NOT prefix of 'collections'")

-- ===== Matches: nil + wildcard =====
assertFalse(Paths.Matches(nil, "a"), "nil never matches")
assertFalse(Paths.Matches("a", nil), "nil never matches reverse")
assertTrue(Paths.Matches("*", "anything.at.all"), "wildcard a matches anything")
assertTrue(Paths.Matches("a.b.c", "*"), "wildcard b matches anything")
assertTrue(Paths.Matches("*", "*"), "wildcard vs wildcard")

-- ===== MatchesAny: cross-product =====
assertTrue(Paths.MatchesAny({"a.b"}, {"a"}), "single-element lists, match")
assertTrue(Paths.MatchesAny({"x", "a.b"}, {"q", "a"}), "match on second pair")
assertFalse(Paths.MatchesAny({"x", "y"}, {"q", "r"}), "no intersection")
assertFalse(Paths.MatchesAny({}, {"a"}), "empty reads matches nothing")
assertFalse(Paths.MatchesAny({"a"}, {}), "empty invalidation matches nothing")

-- ===== MatchesAny: wildcard sentinel =====
assertTrue(Paths.MatchesAny("*", {"anything"}), "wildcard reads")
assertTrue(Paths.MatchesAny({"anything"}, "*"), "wildcard invalidation")
assertTrue(Paths.MatchesAny("*", "*"), "both wildcards")

-- ===== Join: composition =====
assertEq(Paths.Join("a", "b", "c"), "a.b.c", "basic join")
assertEq(Paths.Join("session.ui", "decor", "searchQuery"),
    "session.ui.decor.searchQuery", "payload-style join")
assertEq(Paths.Join("a"), "a", "single segment")
assertEq(Paths.Join(), "", "no segments")
-- nil/empty skip behavior: enables Paths.Join("session.ui", view, key)
-- when view or key might be nil. Result truncates instead of yielding
-- "session.ui.." with trailing dot.
assertEq(Paths.Join("a", nil, "c"), "a.c", "nil segment skipped")
assertEq(Paths.Join("a", "", "c"), "a.c", "empty segment skipped")
assertEq(Paths.Join("a", nil), "a", "trailing nil skipped")
assertEq(Paths.Join(nil, "b"), "b", "leading nil skipped")
-- Numeric segments coerced to string
assertEq(Paths.Join("session.combat", 42), "session.combat.42", "numeric coerced")

-- ===== Parse: decomposition =====
local function arraysEqual(a, b)
    if #a ~= #b then return false end
    for i = 1, #a do if a[i] ~= b[i] then return false end end
    return true
end
assertTrue(arraysEqual(Paths.Parse("a.b.c"), {"a", "b", "c"}), "basic parse")
assertTrue(arraysEqual(Paths.Parse("single"), {"single"}), "single segment")
assertTrue(arraysEqual(Paths.Parse(""), {}), "empty string")
assertTrue(arraysEqual(Paths.Parse("account.collection.decorCatalog"),
    {"account", "collection", "decorCatalog"}), "real path")

-- ===== Union: merge reads-lists =====
local function listEq(actual, expected)
    if actual == "*" then return expected == "*" end
    if expected == "*" then return false end
    if #actual ~= #expected then return false end
    local seen = {}
    for _, v in ipairs(expected) do seen[v] = true end
    for _, v in ipairs(actual) do if not seen[v] then return false end end
    return true
end

assertTrue(listEq(Paths.Union({"a", "b"}, {"b", "c"}), {"a", "b", "c"}),
    "union deduplicates")
assertTrue(listEq(Paths.Union({"a"}, {}, {"b"}), {"a", "b"}),
    "union skips empty lists")
assertEq(Paths.Union({"a"}, "*", {"b"}), "*",
    "wildcard collapses entire union")
assertEq(Paths.Union("*"), "*", "single wildcard pass-through")
assertTrue(listEq(Paths.Union(), {}), "no args -> empty")
assertTrue(listEq(Paths.Union({"a"}), {"a"}), "single list pass-through")

print("test_paths_smoke.lua passed")
