// Serialize feed filters to/from a compact, human-readable URL query string. Filters are
// NOT kept in the address bar during normal use (localStorage is the single source of
// truth); this serializer powers the explicit "Share view" link and the one-time hydration
// when someone opens such a link. Only non-default values are emitted to keep URLs clean.
import type { FeedFilters } from "./api";

const KEYS = [
  "q",
  "show",
  "sort",
  "scope",
  "source",
  "normal",
  "shorts",
  "live",
  "tags",
  "tagmode",
  "channel",
  "maxage",
  "from",
  "to",
  "mindur",
  "maxdur",
] as const;

export function filtersToParams(f: FeedFilters): URLSearchParams {
  const p = new URLSearchParams();
  if (f.q) p.set("q", f.q);
  if (f.show && f.show !== "unwatched") p.set("show", f.show);
  if (f.sort && f.sort !== "newest") p.set("sort", f.sort);
  if (f.scope === "all") p.set("scope", "all");
  // Library provenance filter (only meaningful in "all" scope); "organic" is the default.
  if (f.scope === "all" && f.librarySource && f.librarySource !== "organic")
    p.set("source", f.librarySource);
  if (!f.includeNormal) p.set("normal", "0");
  if (f.includeShorts) p.set("shorts", "1");
  if (f.includeLive) p.set("live", "1");
  if (f.tags.length) p.set("tags", f.tags.join(","));
  if (f.tagMode === "and") p.set("tagmode", "and");
  if (f.channelId) p.set("channel", f.channelId);
  if (f.maxAgeDays) p.set("maxage", String(f.maxAgeDays));
  if (f.dateFrom) p.set("from", f.dateFrom);
  if (f.dateTo) p.set("to", f.dateTo);
  if (f.minDuration != null) p.set("mindur", String(f.minDuration));
  if (f.maxDuration != null) p.set("maxdur", String(f.maxDuration));
  return p;
}

/** Merge URL params onto a base filter set (typically the defaults). */
export function paramsToFilters(params: URLSearchParams, base: FeedFilters): FeedFilters {
  const f: FeedFilters = { ...base };
  const num = (v: string | null): number | undefined => {
    if (v == null || v === "") return undefined;
    const n = Number(v);
    return Number.isFinite(n) ? n : undefined;
  };

  if (params.has("q")) f.q = params.get("q") ?? "";
  if (params.has("show")) f.show = params.get("show") || "unwatched";
  if (params.has("sort")) f.sort = params.get("sort") || "newest";
  if (params.has("scope")) f.scope = params.get("scope") === "all" ? "all" : "my";
  if (params.has("source")) {
    const s = params.get("source");
    f.librarySource = s === "all" || s === "search" || s === "plex" ? s : "organic";
  }
  if (params.has("normal")) f.includeNormal = params.get("normal") !== "0";
  if (params.has("shorts")) f.includeShorts = params.get("shorts") === "1";
  if (params.has("live")) f.includeLive = params.get("live") === "1";
  if (params.has("tags"))
    f.tags = (params.get("tags") ?? "")
      .split(",")
      .map((s) => Number(s))
      .filter((n) => Number.isInteger(n));
  if (params.has("tagmode")) f.tagMode = params.get("tagmode") === "and" ? "and" : "or";
  f.channelId = params.get("channel") || undefined;
  f.maxAgeDays = num(params.get("maxage"));
  f.dateFrom = params.get("from") || undefined;
  f.dateTo = params.get("to") || undefined;
  f.minDuration = num(params.get("mindur"));
  f.maxDuration = num(params.get("maxdur"));
  return f;
}

/** True if the query string carries any filter param we recognize. */
export function hasFilterParams(params: URLSearchParams): boolean {
  return KEYS.some((k) => params.has(k));
}

// The single source of truth for valid page ids; `Page` and the runtime validator both
// derive from it, so adding a page is a one-line change (no parallel allowlists to keep in
// sync). "feed" is the default/fallback.
const PAGES = [
  "feed",
  "channels",
  "stats",
  "playlists",
  "settings",
  "scheduler",
  "config",
  "users",
  "audit",
  "notifications",
  "messages",
  "downloads",
  "plex",
] as const;

export type Page = (typeof PAGES)[number];

/** Narrow an arbitrary string to a known Page (else null). */
export function isPage(p: string | null | undefined): p is Page {
  return !!p && (PAGES as readonly string[]).includes(p);
}

export function readPage(): Page {
  const p = new URLSearchParams(window.location.search).get("page");
  return isPage(p) ? p : "feed";
}

/** Build a shareable absolute URL that reproduces the current filters (+ page). Used by the
 * opt-in "Share view" action — filters are not otherwise written to the address bar. */
export function shareUrl(f: FeedFilters, page: Page = "feed"): string {
  const params = filtersToParams(f);
  if (page !== "feed") params.set("page", page);
  const qs = params.toString();
  return `${window.location.origin}${window.location.pathname}${qs ? `?${qs}` : ""}`;
}

/** Strip the query string from the address bar (after hydrating from a share/auth link),
 * leaving a clean URL. Preserves the current history.state (e.g. the in-app `sfPage` stamp)
 * so it's safe to call at any time, not just before that stamp is written. */
export function stripUrlParams(): void {
  if (window.location.search) {
    window.history.replaceState(window.history.state, "", window.location.pathname);
  }
}
