// Exercise 3: revalidatePath / revalidateTag cache invalidation (harder)
//
// Per theory section 5: a mutation that doesn't call revalidatePath or
// revalidateTag leaves cached routes showing stale data even though the
// underlying data changed. This exercise models Next.js's cache as a
// plain in-memory map (path -> { data, tags, stale }) and implements the
// invalidation + a full "mutate a todo list" action that MUST revalidate
// correctly, all as pure functions for deterministic testing.

interface CacheEntry {
  data: unknown;
  tags: string[];
  stale: boolean;
}

type Cache = Record<string, CacheEntry>;

// TODO 1: implement revalidatePath(cache, path) — return a NEW cache
// object where the entry at `path` (if present) has `stale: true`; every
// other entry is UNCHANGED (same object references for untouched
// entries, matching the structural-sharing spirit from the Redux
// combineReducers exercise). A path not present in the cache is a no-op
// (return the cache unchanged, same reference).
function revalidatePath(cache: Cache, path: string): Cache {
  // TODO: implement per the spec above
  return cache;
}

// TODO 2: implement revalidateTag(cache, tag) — return a NEW cache
// object where EVERY entry whose `tags` array includes `tag` has
// `stale: true`; entries without that tag are UNCHANGED (same
// references). If no entry has that tag, return the cache unchanged
// (same reference).
function revalidateTag(cache: Cache, tag: string): Cache {
  // TODO: implement per the spec above
  return cache;
}

interface Todo {
  id: number;
  text: string;
}

// TODO 3: implement deleteTodoAction(todos, cache, id) — models a full
// Server Action per theory section 5: delete the todo matching `id` from
// `todos` (return a new array without it), AND revalidate the cache by
// calling revalidatePath(cache, "/todos"). Return { todos: newTodos,
// cache: newCache }. If no todo with that id exists, still return new
// (unchanged-content) todos and the cache UNCHANGED (same reference) —
// no mutation happened, so nothing needs revalidating.
function deleteTodoAction(
  todos: Todo[],
  cache: Cache,
  id: number
): { todos: Todo[]; cache: Cache } {
  // TODO: implement per the spec above
  return { todos, cache };
}

export { revalidatePath, revalidateTag, deleteTodoAction };
export type { Cache, CacheEntry, Todo };

// --- self-check: do not modify below this line ---
const checks: Array<[string, unknown, unknown]> = [];

const initialCache: Cache = {
  "/todos": { data: ["buy milk"], tags: ["todos"], stale: false },
  "/dashboard": { data: {}, tags: ["dashboard", "todos"], stale: false },
  "/about": { data: {}, tags: [], stale: false },
};

const afterPathRevalidate = revalidatePath(initialCache, "/todos");
checks.push(["revalidatePath marks only the matching path stale",
  afterPathRevalidate["/todos"].stale, true]);
checks.push(["revalidatePath leaves other entries untouched (SAME reference)",
  afterPathRevalidate["/dashboard"] === initialCache["/dashboard"], true]);
checks.push(["revalidatePath on an unknown path is a no-op (same cache reference)",
  revalidatePath(initialCache, "/nowhere") === initialCache, true]);

const afterTagRevalidate = revalidateTag(initialCache, "todos");
checks.push(["revalidateTag marks EVERY entry carrying that tag as stale",
  afterTagRevalidate["/todos"].stale && afterTagRevalidate["/dashboard"].stale, true]);
checks.push(["revalidateTag leaves entries without that tag untouched (SAME reference)",
  afterTagRevalidate["/about"] === initialCache["/about"], true]);
checks.push(["revalidateTag with an unused tag is a no-op (same cache reference)",
  revalidateTag(initialCache, "nonexistent-tag") === initialCache, true]);

const todos: Todo[] = [{ id: 1, text: "buy milk" }, { id: 2, text: "walk dog" }];

const afterDelete = deleteTodoAction(todos, initialCache, 1);
checks.push(["deleteTodoAction removes the matching todo",
  afterDelete.todos.length, 1]);
checks.push(["deleteTodoAction's cache result has /todos correctly revalidated",
  afterDelete.cache["/todos"].stale, true]);
checks.push(["deleteTodoAction's cache result leaves /about untouched (SAME reference)",
  afterDelete.cache["/about"] === initialCache["/about"], true]);

const noopDelete = deleteTodoAction(todos, initialCache, 999);
checks.push(["deleting a nonexistent id makes NO cache changes (same cache reference)",
  noopDelete.cache === initialCache, true]);
checks.push(["deleting a nonexistent id leaves todos content unchanged",
  JSON.stringify(noopDelete.todos), JSON.stringify(todos)]);

let passed = 0;
for (const [label, actual, expected] of checks) {
  const ok = actual === expected;
  passed += ok ? 1 : 0;
  console.log(`${ok ? "PASS" : "FAIL"}: ${label} (got ${JSON.stringify(actual)}, expected ${JSON.stringify(expected)})`);
}
console.log(passed === checks.length ? "PASS" : "FAIL");
