// FilterBar URL codec — t-paliad-163. Encodes BarState ↔ URL
// parameters with optional namespace prefix (?<ns>_<key>=).
//
// The bar treats the URL as canonical for everything that affects
// which rows you see. Round-trip discipline: anything written by
// encodeBar must parse back identically via parseBar so deep-links
// and refresh both yield the same effective spec.
//
// Empty / default values are NOT written — the URL stays clean for
// users who don't tweak. The page's base spec is the implicit baseline.

import type { BarState, TimeOverlay, ProjectOverlay } from "./types";

const PERSONAL_PROJECT_SENTINEL = "personal";

// parseBar reads URL params into a BarState. Unknown values are
// dropped silently (forward-compat with future axes).
export function parseBar(params: URLSearchParams, ns?: string): BarState {
  const k = (key: string) => (ns ? `${ns}_${key}` : key);
  const out: BarState = {};

  // time
  const time = params.get(k("time"));
  if (time) {
    const horizon = parseHorizon(time);
    if (horizon) {
      const overlay: TimeOverlay = { horizon };
      if (horizon === "custom") {
        const from = params.get(k("from"));
        const to = params.get(k("to"));
        if (from) overlay.from = from;
        if (to) overlay.to = to;
      }
      out.time = overlay;
    }
  }

  // project
  const project = params.get(k("project"));
  if (project) {
    if (project === PERSONAL_PROJECT_SENTINEL) {
      out.project = { mode: "personal" };
    } else {
      out.project = { mode: "single", id: project };
    }
  }

  // personal_only
  if (params.get(k("personal")) === "1") {
    out.personal_only = true;
  }

  // deadline.status
  const dStatus = params.get(k("d_status"));
  if (dStatus) out.deadline_status = parseCSV(dStatus);

  // deadline.event_types — preserves the legacy /events contract
  // where "none" inside the CSV means include_untyped=true.
  const dEvent = params.get(k("d_event_type"));
  if (dEvent) {
    const tokens = parseCSV(dEvent);
    const ids: string[] = [];
    let untyped = false;
    for (const tok of tokens) {
      if (tok === "none") untyped = true;
      else ids.push(tok);
    }
    out.deadline_event_type = { ids, include_untyped: untyped };
  }

  // appointment.types
  const appType = params.get(k("app_type"));
  if (appType) out.appointment_type = parseCSV(appType);

  // approval_request.viewer_role
  const aRole = params.get(k("a_role"));
  if (aRole === "approver_eligible" || aRole === "self_requested" || aRole === "any_visible") {
    out.approval_viewer_role = aRole;
  }

  // approval_request.status
  const aStatus = params.get(k("a_status"));
  if (aStatus) out.approval_status = parseCSV(aStatus);

  // approval_request.entity_types
  const aEntity = params.get(k("a_entity_type"));
  if (aEntity) out.approval_entity_type = parseCSV(aEntity);

  // project_event.event_types
  const peKind = params.get(k("pe_kind"));
  if (peKind) out.project_event_kind = parseCSV(peKind);

  // SmartTimeline (t-paliad-173) — status + track axes.
  const tlStatus = params.get(k("tl_status"));
  if (tlStatus) out.timeline_status = parseCSV(tlStatus);
  const tlTrack = params.get(k("tl_track"));
  if (tlTrack) out.timeline_track = parseCSV(tlTrack);

  // render.shape
  const shape = params.get(k("shape"));
  if (shape === "list" || shape === "cards" || shape === "calendar") out.shape = shape;

  // render.list.sort / render.cards.sort — the bar treats sort as one axis
  const sort = params.get(k("sort"));
  if (sort === "date_asc" || sort === "date_desc") out.sort = sort;

  // render.list.density
  const density = params.get(k("density"));
  if (density === "comfortable" || density === "compact") out.density = density;

  return out;
}

// encodeBar writes BarState back into URL params, mutating the
// passed-in URLSearchParams. Empty / undefined values are omitted.
// The caller controls how the result is applied (history.replaceState
// with the page pathname unchanged).
export function encodeBar(state: BarState, params: URLSearchParams, ns?: string): void {
  const k = (key: string) => (ns ? `${ns}_${key}` : key);

  // Clear every key the bar owns first, then re-write the non-empty ones.
  for (const key of [
    "time", "from", "to", "project", "personal",
    "d_status", "d_event_type",
    "app_type",
    "a_role", "a_status", "a_entity_type",
    "pe_kind",
    "tl_status", "tl_track",
    "shape", "sort", "density",
  ]) {
    params.delete(k(key));
  }

  if (state.time) {
    params.set(k("time"), state.time.horizon);
    if (state.time.horizon === "custom") {
      if (state.time.from) params.set(k("from"), state.time.from);
      if (state.time.to) params.set(k("to"), state.time.to);
    }
  }

  if (state.project) {
    if (state.project.mode === "personal") {
      params.set(k("project"), PERSONAL_PROJECT_SENTINEL);
    } else if (state.project.id) {
      params.set(k("project"), state.project.id);
    }
  }

  if (state.personal_only) params.set(k("personal"), "1");

  if (state.deadline_status?.length) params.set(k("d_status"), state.deadline_status.join(","));

  if (state.deadline_event_type) {
    const parts = [...state.deadline_event_type.ids];
    if (state.deadline_event_type.include_untyped) parts.push("none");
    if (parts.length) params.set(k("d_event_type"), parts.join(","));
  }

  if (state.appointment_type?.length) params.set(k("app_type"), state.appointment_type.join(","));
  if (state.approval_viewer_role) params.set(k("a_role"), state.approval_viewer_role);
  if (state.approval_status?.length) params.set(k("a_status"), state.approval_status.join(","));
  if (state.approval_entity_type?.length) params.set(k("a_entity_type"), state.approval_entity_type.join(","));
  if (state.project_event_kind?.length) params.set(k("pe_kind"), state.project_event_kind.join(","));
  if (state.timeline_status?.length) params.set(k("tl_status"), state.timeline_status.join(","));
  if (state.timeline_track?.length) params.set(k("tl_track"), state.timeline_track.join(","));

  if (state.shape) params.set(k("shape"), state.shape);
  if (state.sort) params.set(k("sort"), state.sort);
  if (state.density) params.set(k("density"), state.density);
}

function parseHorizon(s: string): TimeOverlay["horizon"] | null {
  switch (s) {
    case "next_7d":
    case "next_30d":
    case "next_90d":
    case "past_7d":
    case "past_30d":
    case "past_90d":
    case "any":
    case "all":
    case "custom":
      return s;
    default:
      return null;
  }
}

function parseCSV(s: string): string[] {
  return s.split(",").map((x) => x.trim()).filter(Boolean);
}

export { PERSONAL_PROJECT_SENTINEL };

// Re-exported so consumers don't need to import ProjectOverlay just
// to construct one in tests.
export type { ProjectOverlay };
