// paliadin-context.ts — structured page-context payload builder for the
// Paliadin inline widget (t-paliad-161).
//
// The standalone /paliadin page submits turns with only `page_origin`
// (single string, the URL pathname). The inline widget submits a richer
// payload: route_name + primary_entity_type + primary_entity_id + the
// user's text selection + UI hints. The Go backend persists this jsonb
// in paliad.paliadin_turns.context (migration 070) AND prepends a
// flattened `[ctx …]` block to the tmux envelope so SKILL.md can branch
// on it before answering.
//
// Design: docs/design-paliadin-inline-2026-05-08.md §4.

export interface PaliadinContext {
  route_name: string;
  page_origin: string;
  primary_entity_type?: "project" | "deadline" | "appointment";
  primary_entity_id?: string;
  user_selection_text?: string;
  view_mode?: "list" | "cards" | "calendar" | "tree";
  filter_summary?: string;
}

const SELECTION_MAX = 1000;

// UUID match — relaxed: any 8-4-4-4-12 hex pattern. Catches /projects/<id>
// and /deadlines/<id> regardless of trailing path segments.
const UUID_RE =
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

/**
 * Compute the Paliadin context for the current page. Reads
 * window.location + window.getSelection() at call time, so callers
 * should invoke this immediately before sending a turn — not at widget
 * boot — to capture the user's selection in the moment they typed.
 *
 * Returns null when the visibility predicate fails (e.g. on /paliadin,
 * /login, /onboarding) — callers SHOULD short-circuit on null instead
 * of sending an empty payload.
 */
export function computePaliadinContext(): PaliadinContext | null {
  const pathname = window.location.pathname || "";
  if (!shouldSendContext(pathname)) {
    return null;
  }
  const search = window.location.search || "";
  const ctx: PaliadinContext = {
    route_name: routeNameFor(pathname),
    page_origin: pathname + search,
  };
  const entity = extractPrimaryEntity(pathname);
  if (entity) {
    ctx.primary_entity_type = entity.type;
    ctx.primary_entity_id = entity.id;
  }
  const selection = readSelection();
  if (selection) {
    ctx.user_selection_text = selection;
  }
  const view = readViewMode();
  if (view) {
    ctx.view_mode = view;
  }
  const filter = readFilterSummary();
  if (filter) {
    ctx.filter_summary = filter;
  }
  return ctx;
}

/**
 * The widget hides itself on routes where Paliadin is either redundant
 * (the standalone /paliadin) or unavailable (auth flows). Mirrored here
 * for the context-payload predicate so a stray send from one of those
 * pages doesn't surface an empty `[ctx]` block.
 */
export function shouldSendContext(pathname: string): boolean {
  if (pathname === "/paliadin" || pathname.startsWith("/paliadin/")) return false;
  if (pathname === "/login" || pathname.startsWith("/login/")) return false;
  if (pathname === "/onboarding") return false;
  return true;
}

/**
 * Map a URL pathname to a stable route key. Stable across query-string
 * + ID variations so the SKILL.md / starter registry can branch on it
 * without fragile URL parsing.
 */
export function routeNameFor(pathname: string): string {
  // Order matters — most-specific first.
  if (/^\/projects\/[^/]+$/.test(pathname)) return "projects.detail";
  if (pathname === "/projects" || pathname === "/projects/") return "projects.list";
  if (/^\/projects\/[^/]+\/settings/.test(pathname)) return "projects.settings";
  if (/^\/deadlines\/[^/]+$/.test(pathname)) return "deadlines.detail";
  if (pathname === "/deadlines/new") return "deadlines.new";
  if (pathname === "/deadlines") return "deadlines.list";
  if (/^\/appointments\/[^/]+$/.test(pathname)) return "appointments.detail";
  if (pathname === "/appointments/new") return "appointments.new";
  if (pathname === "/appointments") return "appointments.list";
  // /deadlines/calendar + /appointments/calendar are 301 redirects to
  // /events?type=…&view=calendar since t-paliad-224 — the client never
  // sees those pathnames any more.
  if (pathname === "/agenda") return "agenda";
  if (pathname === "/inbox") return "inbox";
  if (pathname === "/dashboard" || pathname === "/") return "dashboard";
  if (pathname === "/team") return "team";
  if (pathname === "/courts") return "courts";
  if (pathname === "/glossary") return "glossary";
  if (pathname === "/links") return "links";
  if (pathname === "/downloads") return "downloads";
  if (pathname === "/checklists") return "checklists";
  if (pathname.startsWith("/tools/procedures")) return "tools.procedures";
  if (pathname.startsWith("/tools/kostenrechner")) return "tools.kostenrechner";
  if (pathname.startsWith("/tools/gebuehrentabellen")) return "tools.gebuehrentabellen";
  if (pathname === "/events") return "events";
  if (pathname.startsWith("/views/")) return "views.detail";
  if (pathname === "/views") return "views.list";
  if (pathname.startsWith("/admin/")) return "admin." + pathname.slice("/admin/".length).split("/")[0];
  if (pathname === "/admin") return "admin";
  if (pathname === "/settings") return "settings";
  return "other";
}

/**
 * Pull the primary entity (type + uuid) out of the URL when the route
 * encodes one. Returns null on routes that have no primary entity
 * (dashboard, agenda, lists, tools).
 */
export function extractPrimaryEntity(
  pathname: string,
): { type: "project" | "deadline" | "appointment"; id: string } | null {
  const projectMatch = pathname.match(/^\/projects\/([^/]+)(?:\/|$)/);
  if (projectMatch && UUID_RE.test(projectMatch[1])) {
    return { type: "project", id: projectMatch[1] };
  }
  const deadlineMatch = pathname.match(/^\/deadlines\/([^/]+)$/);
  if (deadlineMatch && UUID_RE.test(deadlineMatch[1])) {
    return { type: "deadline", id: deadlineMatch[1] };
  }
  const apptMatch = pathname.match(/^\/appointments\/([^/]+)$/);
  if (apptMatch && UUID_RE.test(apptMatch[1])) {
    return { type: "appointment", id: apptMatch[1] };
  }
  return null;
}

/**
 * Capture the user's current text selection, capped at SELECTION_MAX.
 * Returns empty string when there's no selection or when the selection
 * is collapsed (caret with no range).
 *
 * Privacy floor (§4.3): respects the widget's "send selection" toggle,
 * stored in localStorage under `paliadin:send-selection`. Default on
 * (m's Q5 lock-in); flip to off → returns empty string regardless of
 * what's selected.
 */
export function readSelection(): string {
  if (localStorage.getItem("paliadin:send-selection") === "off") {
    return "";
  }
  const sel = window.getSelection();
  if (!sel || sel.isCollapsed) return "";
  const text = sel.toString().trim();
  if (!text) return "";
  if (text.length > SELECTION_MAX) {
    return text.slice(0, SELECTION_MAX);
  }
  return text;
}

/**
 * Probe the page for an active "view mode" hint — set by /events,
 * /projects (tree vs list), /deadlines (calendar vs list). The frontend
 * stores these as `data-view-mode` attributes on a known root element
 * or in localStorage; this helper centralises the lookup so future
 * pages adding a new view mode don't have to teach the widget about
 * themselves.
 */
export function readViewMode(): "list" | "cards" | "calendar" | "tree" | "" {
  const root = document.querySelector<HTMLElement>("[data-view-mode]");
  if (!root) return "";
  const v = root.dataset.viewMode || "";
  if (v === "list" || v === "cards" || v === "calendar" || v === "tree") return v;
  return "";
}

/**
 * Pull a short human-readable summary of active list filters from a
 * known DOM hook. Pages that want to participate set
 * `data-filter-summary="status=overdue · project=Acme"` on a root
 * element. Empty = no summary.
 */
export function readFilterSummary(): string {
  const root = document.querySelector<HTMLElement>("[data-filter-summary]");
  if (!root) return "";
  return (root.dataset.filterSummary || "").trim();
}
