// Unit tests for the FilterBar URL codec. Round-trip discipline:
// every BarState shape parseBar produces must encode back to the same
// URL params, and vice versa. Run with `bun test`.

import { test, expect, describe } from "bun:test";
import { parseBar, encodeBar } from "./url-codec";
import type { BarState } from "./types";

function roundTrip(state: BarState, ns?: string): BarState {
  const params = new URLSearchParams();
  encodeBar(state, params, ns);
  return parseBar(params, ns);
}

describe("filter-bar/url-codec", () => {
  test("empty state round-trips to empty", () => {
    expect(roundTrip({})).toEqual({});
  });

  test("time horizon round-trips", () => {
    // Includes the t-paliad-248 symmetric additions (1d / 14d / all on each side).
    for (const h of [
      "next_1d", "next_7d", "next_14d", "next_30d", "next_90d", "next_all",
      "past_1d", "past_7d", "past_14d", "past_30d", "past_90d", "past_all",
      "any", "all",
    ] as const) {
      expect(roundTrip({ time: { horizon: h } })).toEqual({ time: { horizon: h } });
    }
  });

  test("custom time horizon round-trips with from + to", () => {
    const state: BarState = { time: { horizon: "custom", from: "2026-01-01", to: "2026-12-31" } };
    expect(roundTrip(state)).toEqual(state);
  });

  test("project sentinel + uuid round-trip", () => {
    expect(roundTrip({ project: { mode: "personal" } })).toEqual({ project: { mode: "personal" } });
    expect(roundTrip({ project: { mode: "single", id: "11111111-1111-1111-1111-111111111111" } }))
      .toEqual({ project: { mode: "single", id: "11111111-1111-1111-1111-111111111111" } });
  });

  test("personal_only flag round-trips", () => {
    expect(roundTrip({ personal_only: true })).toEqual({ personal_only: true });
    expect(roundTrip({})).toEqual({});
  });

  test("deadline_event_type honours legacy 'none' sentinel", () => {
    const state: BarState = { deadline_event_type: { ids: ["a", "b"], include_untyped: true } };
    expect(roundTrip(state)).toEqual(state);
    const state2: BarState = { deadline_event_type: { ids: [], include_untyped: true } };
    expect(roundTrip(state2)).toEqual(state2);
    const state3: BarState = { deadline_event_type: { ids: ["a"], include_untyped: false } };
    expect(roundTrip(state3)).toEqual(state3);
  });

  test("approval_request triple round-trips together", () => {
    const state: BarState = {
      approval_viewer_role: "approver_eligible",
      approval_status: ["pending", "approved"],
      approval_entity_type: ["deadline"],
    };
    expect(roundTrip(state)).toEqual(state);
  });

  test("namespace prefix isolates two bars on the same page", () => {
    const a: BarState = { time: { horizon: "next_7d" } };
    const b: BarState = { time: { horizon: "next_30d" } };
    const params = new URLSearchParams();
    encodeBar(a, params, "agenda");
    encodeBar(b, params, "activity");
    expect(parseBar(params, "agenda")).toEqual(a);
    expect(parseBar(params, "activity")).toEqual(b);
    // Without namespace neither bar's keys are visible.
    expect(parseBar(params)).toEqual({});
  });

  test("render axes round-trip", () => {
    const state: BarState = { shape: "cards", sort: "date_desc", density: "compact" };
    expect(roundTrip(state)).toEqual(state);
  });

  test("encode is idempotent — re-encoding same state replaces, doesn't accumulate", () => {
    const state: BarState = { time: { horizon: "next_7d" }, deadline_status: ["pending"] };
    const params = new URLSearchParams();
    encodeBar(state, params);
    encodeBar(state, params);
    expect(params.get("d_status")).toBe("pending");
    // Only one entry per key.
    expect(params.getAll("d_status")).toHaveLength(1);
  });

  test("encode replaces stale keys when state shrinks", () => {
    const params = new URLSearchParams();
    encodeBar({ deadline_status: ["pending"], approval_viewer_role: "self_requested" }, params);
    encodeBar({ deadline_status: ["completed"] }, params);
    expect(params.get("d_status")).toBe("completed");
    expect(params.has("a_role")).toBe(false);
  });

  test("parse drops unknown enum values silently (forward-compat)", () => {
    const params = new URLSearchParams();
    params.set("a_role", "future_role_we_dont_know_yet");
    params.set("shape", "kanban");
    params.set("density", "huge");
    expect(parseBar(params)).toEqual({});
  });

  // t-paliad-249 — inbox axes
  test("unread_only round-trips both states", () => {
    expect(roundTrip({ unread_only: true })).toEqual({ unread_only: true });
    expect(roundTrip({ unread_only: false })).toEqual({ unread_only: false });
  });

  test("unread_only undefined stays out of the URL", () => {
    const params = new URLSearchParams();
    encodeBar({}, params);
    expect(params.has("unread")).toBe(false);
  });

  test("inbox_focus round-trips for non-default values", () => {
    for (const f of ["genehmigungen", "plus_termine", "plus_fristen"] as const) {
      expect(roundTrip({ inbox_focus: f })).toEqual({ inbox_focus: f });
    }
  });

  test("inbox_focus alles is omitted (it's the default)", () => {
    const params = new URLSearchParams();
    encodeBar({ inbox_focus: "alles" }, params);
    expect(params.has("focus")).toBe(false);
  });
});
