// Per-axis renderers for the FilterBar — t-paliad-163.
//
// Each axis is a small, self-contained render function that takes the
// current BarState slice and a callback. The bar's mountFilterBar
// composes them in the order declared on the surface.
//
// Reuses existing CSS classes wherever possible:
//   - .agenda-chip / .agenda-chip-active  (chip cluster pattern)
//   - .filter-group                       (label + control wrapping)
//   - .akten-multi-trigger / .multi-anchor / .multi-panel
//
// New classes are scoped under .filter-bar-* so they don't bleed.

import { t, tDyn, type I18nKey } from "../i18n";
import { mountDateRangePicker } from "../date-range-picker";
import {
  ALL_HORIZONS as DRP_ALL_HORIZONS,
  type TimeHorizon as DRPTimeHorizon,
  type TimeSpec as DRPTimeSpec,
} from "../date-range-picker-pure";
import type { BarState, AxisKey, InboxFocus } from "./types";

export interface AxisCtx {
  // Read the current value for this axis.
  get<K extends keyof BarState>(key: K): BarState[K];
  // Patch one or more axis values + trigger re-run.
  patch(delta: Partial<BarState>): void;
}

// RenderAxisOpts — per-surface tuning the bar threads through to axis
// renderers. Currently only time-axis chip presets; future axes can grow
// here without changing every call site.
export interface RenderAxisOpts {
  timePresets?: NonNullable<BarState["time"]>["horizon"][];
}

// renderAxis returns the HTML element for a single axis. The bar's
// mountFilterBar appends the result to its internal toolbar. Returns
// null when the axis is ignored (e.g. surface didn't declare it).
export function renderAxis(axis: AxisKey, ctx: AxisCtx, opts?: RenderAxisOpts): HTMLElement | null {
  switch (axis) {
    case "time":                  return renderTimeAxis(ctx, opts?.timePresets);
    case "project":               return null; // populated lazily — see attachProjectAxis below
    case "personal_only":         return renderPersonalOnlyAxis(ctx);
    case "approval_viewer_role":  return renderApprovalRoleAxis(ctx);
    case "approval_status":       return renderApprovalStatusAxis(ctx);
    case "approval_entity_type":  return renderApprovalEntityTypeAxis(ctx);
    case "deadline_status":       return renderDeadlineStatusAxis(ctx);
    case "appointment_type":      return renderAppointmentTypeAxis(ctx);
    case "project_event_kind":    return renderProjectEventKindAxis(ctx);
    case "timeline_status":       return renderTimelineStatusAxis(ctx);
    case "timeline_track":        return renderTimelineTrackAxis(ctx);
    case "shape":                 return renderShapeAxis(ctx);
    case "density":               return renderDensityAxis(ctx);
    case "sort":                  return renderSortAxis(ctx);
    case "unread_only":           return renderUnreadOnlyAxis(ctx);
    case "inbox_focus":           return renderInboxFocusAxis(ctx);

    // Per-source predicates that need their own widgets and a roundtrip
    // through fetched option lists. Phase 2+ will fill these in by
    // wiring the existing event-types component.
    case "deadline_event_type":
      return null;
  }
}

// ----------------------------------------------------------------------
// time — symmetric date-range picker (t-paliad-248, replaces the t-163
// chip-cluster + disabled Anpassen stub). The picker emits a TimeSpec
// (horizon + optional custom from/to); the bar patches that onto
// BarState.time directly.
// ----------------------------------------------------------------------

type TimeHorizonValue = NonNullable<BarState["time"]>["horizon"];

// Default chip set when the surface doesn't override. Mirrors m's
// 3-column picker spec (t-paliad-278): symmetric 7d/30d/90d/all fan
// per side, plus Heute (next_1d) + Alles (any) in the centre column,
// plus Anpassen. Surfaces with a tighter scope (project history is
// past-only) keep overriding via `timePresets`.
const DEFAULT_TIME_PRESETS: TimeHorizonValue[] = [
  "past_7d", "past_30d", "past_90d", "past_all",
  "next_1d", "any",
  "next_7d", "next_30d", "next_90d", "next_all",
  "custom",
];

function renderTimeAxis(ctx: AxisCtx, presetOverride?: TimeHorizonValue[]): HTMLElement {
  const wrap = group("views.bar.label.time");
  const presetSource = presetOverride && presetOverride.length ? presetOverride : DEFAULT_TIME_PRESETS;
  // The picker's pure module owns the complete chip set; we narrow it
  // here to whatever this surface declares (preserving the surface's
  // chip order so timePresets remains the override knob it always was).
  const presets: DRPTimeHorizon[] = presetSource.flatMap((p) =>
    DRP_ALL_HORIZONS.includes(p as DRPTimeHorizon) ? [p as DRPTimeHorizon] : [],
  );

  const current = ctx.get("time");
  const initialValue: DRPTimeSpec = current
    ? { horizon: current.horizon as DRPTimeHorizon, from: current.from, to: current.to }
    : { horizon: "any" };

  const picker = mountDateRangePicker({
    value: initialValue,
    onChange(next) {
      // The bar treats `any` as "no time overlay" (matches the legacy
      // chip-cluster's behaviour) so the BarState stays minimal when
      // the user lands on the centre ALLES button.
      if (next.horizon === "any") {
        ctx.patch({ time: undefined });
        return;
      }
      ctx.patch({
        time: {
          horizon: next.horizon as TimeHorizonValue,
          from: next.horizon === "custom" ? next.from : undefined,
          to:   next.horizon === "custom" ? next.to   : undefined,
        },
      });
    },
    defaultHorizon: "any",
    presets,
    surface: "filter-bar.time",
    labelPrefix: t("views.bar.label.time"),
  });

  wrap.appendChild(picker.element);
  return wrap;
}

// ----------------------------------------------------------------------
// personal_only — single chip (binary)
// ----------------------------------------------------------------------

function renderPersonalOnlyAxis(ctx: AxisCtx): HTMLElement {
  const wrap = group("views.bar.label.personal");
  const chip = chipBtn(t("views.bar.personal.on"), !!ctx.get("personal_only"));
  chip.addEventListener("click", () => {
    ctx.patch({ personal_only: !ctx.get("personal_only") });
  });
  wrap.appendChild(chip);
  return wrap;
}

// ----------------------------------------------------------------------
// approval_viewer_role — chip cluster (3 mutually exclusive)
// ----------------------------------------------------------------------

const APPROVAL_ROLES: Array<{ value: NonNullable<BarState["approval_viewer_role"]>; key: I18nKey }> = [
  { value: "approver_eligible", key: "views.bar.approval_role.approver_eligible" },
  { value: "self_requested",    key: "views.bar.approval_role.self_requested" },
  { value: "any_visible",       key: "views.bar.approval_role.any_visible" },
];

function renderApprovalRoleAxis(ctx: AxisCtx): HTMLElement {
  const wrap = group("views.bar.label.approval_role");
  const row = chipRow();
  // Default to "any_visible" so the surface lands on a populated view
  // for every user. The InboxSystemView's base spec also defaults here;
  // these two defaults must stay in sync — otherwise the chip and the
  // server narrow disagree on the empty URL.
  const current = ctx.get("approval_viewer_role") ?? "any_visible";
  for (const role of APPROVAL_ROLES) {
    const chip = chipBtn(t(role.key), role.value === current);
    chip.addEventListener("click", () => {
      ctx.patch({ approval_viewer_role: role.value });
    });
    row.appendChild(chip);
  }
  wrap.appendChild(row);
  return wrap;
}

// ----------------------------------------------------------------------
// approval_status — chip cluster (multi-select)
// ----------------------------------------------------------------------

const APPROVAL_STATUSES: Array<{ value: string; key: I18nKey }> = [
  { value: "pending",            key: "views.bar.approval_status.pending" },
  { value: "approved",           key: "views.bar.approval_status.approved" },
  { value: "rejected",           key: "views.bar.approval_status.rejected" },
  { value: "revoked",            key: "views.bar.approval_status.revoked" },
  { value: "changes_requested",  key: "views.bar.approval_status.changes_requested" },
];

function renderApprovalStatusAxis(ctx: AxisCtx): HTMLElement {
  const wrap = group("views.bar.label.approval_status");
  const row = chipRow();
  const all = chipBtn(t("views.bar.common.all"), !ctx.get("approval_status")?.length);
  all.addEventListener("click", () => ctx.patch({ approval_status: undefined }));
  row.appendChild(all);
  const current = new Set(ctx.get("approval_status") ?? []);
  for (const status of APPROVAL_STATUSES) {
    const chip = chipBtn(t(status.key), current.has(status.value));
    chip.addEventListener("click", () => {
      if (current.has(status.value)) current.delete(status.value);
      else current.add(status.value);
      ctx.patch({ approval_status: current.size ? [...current] : undefined });
    });
    row.appendChild(chip);
  }
  wrap.appendChild(row);
  return wrap;
}

// ----------------------------------------------------------------------
// approval_entity_type — chip pair (multi-select; deadline / appointment)
// ----------------------------------------------------------------------

const APPROVAL_ENTITY_TYPES: Array<{ value: string; key: I18nKey }> = [
  { value: "deadline",    key: "views.bar.approval_entity.deadline" },
  { value: "appointment", key: "views.bar.approval_entity.appointment" },
];

function renderApprovalEntityTypeAxis(ctx: AxisCtx): HTMLElement {
  const wrap = group("views.bar.label.approval_entity");
  const row = chipRow();
  const all = chipBtn(t("views.bar.common.all"), !ctx.get("approval_entity_type")?.length);
  all.addEventListener("click", () => ctx.patch({ approval_entity_type: undefined }));
  row.appendChild(all);
  const current = new Set(ctx.get("approval_entity_type") ?? []);
  for (const ent of APPROVAL_ENTITY_TYPES) {
    const chip = chipBtn(t(ent.key), current.has(ent.value));
    chip.addEventListener("click", () => {
      if (current.has(ent.value)) current.delete(ent.value);
      else current.add(ent.value);
      ctx.patch({ approval_entity_type: current.size ? [...current] : undefined });
    });
    row.appendChild(chip);
  }
  wrap.appendChild(row);
  return wrap;
}

// ----------------------------------------------------------------------
// deadline_status — chip cluster (multi-select)
// ----------------------------------------------------------------------

const DEADLINE_STATUSES: Array<{ value: string; key: I18nKey }> = [
  { value: "pending",   key: "views.bar.deadline_status.pending" },
  { value: "completed", key: "views.bar.deadline_status.completed" },
];

function renderDeadlineStatusAxis(ctx: AxisCtx): HTMLElement {
  const wrap = group("views.bar.label.deadline_status");
  const row = chipRow();
  const all = chipBtn(t("views.bar.common.all"), !ctx.get("deadline_status")?.length);
  all.addEventListener("click", () => ctx.patch({ deadline_status: undefined }));
  row.appendChild(all);
  const current = new Set(ctx.get("deadline_status") ?? []);
  for (const s of DEADLINE_STATUSES) {
    const chip = chipBtn(t(s.key), current.has(s.value));
    chip.addEventListener("click", () => {
      if (current.has(s.value)) current.delete(s.value);
      else current.add(s.value);
      ctx.patch({ deadline_status: current.size ? [...current] : undefined });
    });
    row.appendChild(chip);
  }
  wrap.appendChild(row);
  return wrap;
}

// ----------------------------------------------------------------------
// appointment_type — chip cluster (multi-select)
// ----------------------------------------------------------------------

const APPOINTMENT_TYPES: Array<{ value: string; key: I18nKey }> = [
  { value: "hearing",          key: "views.bar.appointment_type.hearing" },
  { value: "meeting",          key: "views.bar.appointment_type.meeting" },
  { value: "consultation",     key: "views.bar.appointment_type.consultation" },
  { value: "deadline_hearing", key: "views.bar.appointment_type.deadline_hearing" },
];

function renderAppointmentTypeAxis(ctx: AxisCtx): HTMLElement {
  const wrap = group("views.bar.label.appointment_type");
  const row = chipRow();
  const all = chipBtn(t("views.bar.common.all"), !ctx.get("appointment_type")?.length);
  all.addEventListener("click", () => ctx.patch({ appointment_type: undefined }));
  row.appendChild(all);
  const current = new Set(ctx.get("appointment_type") ?? []);
  for (const ty of APPOINTMENT_TYPES) {
    const chip = chipBtn(t(ty.key), current.has(ty.value));
    chip.addEventListener("click", () => {
      if (current.has(ty.value)) current.delete(ty.value);
      else current.add(ty.value);
      ctx.patch({ appointment_type: current.size ? [...current] : undefined });
    });
    row.appendChild(chip);
  }
  wrap.appendChild(row);
  return wrap;
}

// ----------------------------------------------------------------------
// project_event_kind — chip cluster (multi-select)
//
// Mirrors KnownProjectEventKinds in internal/services/filter_spec.go.
// Labels reuse the existing `event.title.<kind>` translation table so
// the chip text matches the Verlauf row title for the same event type.
// ----------------------------------------------------------------------

const PROJECT_EVENT_KINDS: string[] = [
  "project_created",
  "project_archived",
  "project_reparented",
  "project_type_changed",
  "status_changed",
  "deadline_created",
  "deadline_completed",
  "deadline_reopened",
  "appointment_created",
  "appointment_updated",
  "appointment_deleted",
  "approval_decided",
  "member_role_changed",
];

function renderProjectEventKindAxis(ctx: AxisCtx): HTMLElement {
  const wrap = group("views.bar.label.project_event_kind");
  const row = chipRow();
  const all = chipBtn(t("views.bar.common.all"), !ctx.get("project_event_kind")?.length);
  all.addEventListener("click", () => ctx.patch({ project_event_kind: undefined }));
  row.appendChild(all);
  const current = new Set(ctx.get("project_event_kind") ?? []);
  for (const kind of PROJECT_EVENT_KINDS) {
    const label = tDyn(`event.title.${kind}`);
    const chip = chipBtn(label, current.has(kind));
    chip.addEventListener("click", () => {
      if (current.has(kind)) current.delete(kind);
      else current.add(kind);
      ctx.patch({ project_event_kind: current.size ? [...current] : undefined });
    });
    row.appendChild(chip);
  }
  wrap.appendChild(row);
  return wrap;
}

// ----------------------------------------------------------------------
// timeline_status — chip cluster (multi-select)
//
// SmartTimeline (t-paliad-173) status vocabulary spans actuals +
// projections. Default: all. Macro chip pair "Zukunft anzeigen" /
// "Nur vergangenes" toggles the [predicted, court_set] subset on
// or off in one click.
// ----------------------------------------------------------------------

const TIMELINE_STATUSES: Array<{ value: string; key: I18nKey }> = [
  { value: "done",              key: "views.bar.timeline_status.done" },
  { value: "open",              key: "views.bar.timeline_status.open" },
  { value: "overdue",           key: "views.bar.timeline_status.overdue" },
  { value: "predicted",         key: "views.bar.timeline_status.predicted" },
  { value: "predicted_overdue", key: "views.bar.timeline_status.predicted_overdue" },
  { value: "court_set",         key: "views.bar.timeline_status.court_set" },
  { value: "off_script",        key: "views.bar.timeline_status.off_script" },
];

function renderTimelineStatusAxis(ctx: AxisCtx): HTMLElement {
  const wrap = group("views.bar.label.timeline_status");
  const row = chipRow();
  const all = chipBtn(t("views.bar.common.all"), !ctx.get("timeline_status")?.length);
  all.addEventListener("click", () => ctx.patch({ timeline_status: undefined }));
  row.appendChild(all);
  const current = new Set(ctx.get("timeline_status") ?? []);
  for (const s of TIMELINE_STATUSES) {
    const chip = chipBtn(t(s.key), current.has(s.value));
    chip.addEventListener("click", () => {
      if (current.has(s.value)) current.delete(s.value);
      else current.add(s.value);
      ctx.patch({ timeline_status: current.size ? [...current] : undefined });
    });
    row.appendChild(chip);
  }
  // Macro chips. "Zukunft anzeigen" = include predicted+court_set; "Nur
  // vergangenes" = strip them. Implemented in terms of timeline_status.
  const future = chipBtn(t("views.bar.timeline_status.macro.future"), false);
  future.classList.add("filter-bar-chip-macro");
  future.addEventListener("click", () => {
    const next = new Set(["done", "open", "overdue", "predicted", "court_set", "predicted_overdue", "off_script"]);
    ctx.patch({ timeline_status: [...next] });
  });
  row.appendChild(future);
  const past = chipBtn(t("views.bar.timeline_status.macro.past"), false);
  past.classList.add("filter-bar-chip-macro");
  past.addEventListener("click", () => {
    ctx.patch({ timeline_status: ["done", "overdue", "off_script"] });
  });
  row.appendChild(past);
  wrap.appendChild(row);
  return wrap;
}

// ----------------------------------------------------------------------
// timeline_track — chip cluster (multi-select)
//
// Slice 2 only renders parent + off_script; counterclaim and child:<id>
// values land with Slice 3's CCR sub-project FK migration. The renderer
// stays ready for those values — chip rendering is dynamic on the
// state set, not hard-coded to the catalogue below.
// ----------------------------------------------------------------------

const TIMELINE_TRACKS: Array<{ value: string; key: I18nKey }> = [
  { value: "parent",       key: "views.bar.timeline_track.parent" },
  { value: "counterclaim", key: "views.bar.timeline_track.counterclaim" },
  { value: "off_script",   key: "views.bar.timeline_track.off_script" },
];

function renderTimelineTrackAxis(ctx: AxisCtx): HTMLElement {
  const wrap = group("views.bar.label.timeline_track");
  const row = chipRow();
  const all = chipBtn(t("views.bar.common.all"), !ctx.get("timeline_track")?.length);
  all.addEventListener("click", () => ctx.patch({ timeline_track: undefined }));
  row.appendChild(all);
  const current = new Set(ctx.get("timeline_track") ?? []);
  for (const tr of TIMELINE_TRACKS) {
    const chip = chipBtn(t(tr.key), current.has(tr.value));
    chip.addEventListener("click", () => {
      if (current.has(tr.value)) current.delete(tr.value);
      else current.add(tr.value);
      ctx.patch({ timeline_track: current.size ? [...current] : undefined });
    });
    row.appendChild(chip);
  }
  wrap.appendChild(row);
  return wrap;
}

// ----------------------------------------------------------------------
// shape — segmented control (list / cards / calendar)
// ----------------------------------------------------------------------

const SHAPES: Array<{ value: NonNullable<BarState["shape"]>; key: I18nKey }> = [
  { value: "list",     key: "views.bar.shape.list" },
  { value: "cards",    key: "views.bar.shape.cards" },
  { value: "calendar", key: "views.bar.shape.calendar" },
];

function renderShapeAxis(ctx: AxisCtx): HTMLElement {
  const wrap = group("views.bar.label.shape");
  const row = chipRow();
  row.classList.add("filter-bar-segment");
  const current = ctx.get("shape");
  for (const sh of SHAPES) {
    const chip = chipBtn(t(sh.key), sh.value === current);
    chip.addEventListener("click", () => ctx.patch({ shape: sh.value }));
    row.appendChild(chip);
  }
  wrap.appendChild(row);
  return wrap;
}

// ----------------------------------------------------------------------
// density — segmented pair (comfortable / compact)
// ----------------------------------------------------------------------

const DENSITIES: Array<{ value: NonNullable<BarState["density"]>; key: I18nKey }> = [
  { value: "comfortable", key: "views.bar.density.comfortable" },
  { value: "compact",     key: "views.bar.density.compact" },
];

function renderDensityAxis(ctx: AxisCtx): HTMLElement {
  const wrap = group("views.bar.label.density");
  const row = chipRow();
  row.classList.add("filter-bar-segment");
  const current = ctx.get("density") ?? "comfortable";
  for (const d of DENSITIES) {
    const chip = chipBtn(t(d.key), d.value === current);
    chip.addEventListener("click", () => ctx.patch({ density: d.value }));
    row.appendChild(chip);
  }
  wrap.appendChild(row);
  return wrap;
}

// ----------------------------------------------------------------------
// sort — small <select>
// ----------------------------------------------------------------------

const SORTS: Array<{ value: NonNullable<BarState["sort"]>; key: I18nKey }> = [
  { value: "date_asc",  key: "views.bar.sort.date_asc" },
  { value: "date_desc", key: "views.bar.sort.date_desc" },
];

function renderSortAxis(ctx: AxisCtx): HTMLElement {
  const wrap = group("views.bar.label.sort");
  const sel = document.createElement("select");
  sel.className = "entity-select filter-bar-select";
  for (const s of SORTS) {
    const opt = document.createElement("option");
    opt.value = s.value;
    opt.textContent = t(s.key);
    sel.appendChild(opt);
  }
  sel.value = ctx.get("sort") ?? "date_asc";
  sel.addEventListener("change", () => ctx.patch({ sort: sel.value as NonNullable<BarState["sort"]> }));
  wrap.appendChild(sel);
  return wrap;
}

// ----------------------------------------------------------------------
// unread_only — single binary chip (t-paliad-249, inbox only)
// ----------------------------------------------------------------------

function renderUnreadOnlyAxis(ctx: AxisCtx): HTMLElement {
  const wrap = group("views.bar.label.unread_only");
  const row = chipRow();
  const isUnread = ctx.get("unread_only") !== false; // default on
  const unreadChip = chipBtn(t("views.bar.unread_only.on"), isUnread);
  unreadChip.addEventListener("click", () => ctx.patch({ unread_only: true }));
  const allChip = chipBtn(t("views.bar.unread_only.off"), !isUnread);
  allChip.addEventListener("click", () => ctx.patch({ unread_only: false }));
  row.appendChild(unreadChip);
  row.appendChild(allChip);
  wrap.appendChild(row);
  return wrap;
}

// ----------------------------------------------------------------------
// inbox_focus — coarse 4-chip cluster (t-paliad-249, inbox only)
//
// Head's UX refinement #2 (2026-05-25): users pick "what to see" in
// human terms, not abstract event-kind names. The overlay translates
// the chip to a (Sources, ProjectEventPredicates.EventTypes,
// ApprovalRequestPredicates.EntityTypes) triple at spec-resolve time
// (see applyInboxFocusOverlay in url-codec.ts).
// ----------------------------------------------------------------------

const INBOX_FOCUS_CHIPS: Array<{ value: InboxFocus; key: I18nKey }> = [
  { value: "alles",         key: "views.bar.inbox_focus.alles" },
  { value: "genehmigungen", key: "views.bar.inbox_focus.genehmigungen" },
  { value: "plus_termine",  key: "views.bar.inbox_focus.plus_termine" },
  { value: "plus_fristen",  key: "views.bar.inbox_focus.plus_fristen" },
];

function renderInboxFocusAxis(ctx: AxisCtx): HTMLElement {
  const wrap = group("views.bar.label.inbox_focus");
  const row = chipRow();
  const current: InboxFocus = ctx.get("inbox_focus") ?? "alles";
  for (const f of INBOX_FOCUS_CHIPS) {
    const chip = chipBtn(t(f.key), f.value === current);
    chip.addEventListener("click", () => {
      ctx.patch({ inbox_focus: f.value === "alles" ? undefined : f.value });
    });
    row.appendChild(chip);
  }
  wrap.appendChild(row);
  return wrap;
}

// ----------------------------------------------------------------------
// shared helpers — group + chip + row
// ----------------------------------------------------------------------

function group(labelKey: I18nKey): HTMLElement {
  const wrap = document.createElement("div");
  wrap.className = "filter-group filter-bar-group";
  const label = document.createElement("span");
  label.className = "filter-bar-label";
  label.textContent = t(labelKey);
  wrap.appendChild(label);
  return wrap;
}

function chipRow(): HTMLElement {
  const row = document.createElement("div");
  row.className = "filter-bar-chip-row";
  return row;
}

function chipBtn(text: string, active: boolean): HTMLButtonElement {
  const btn = document.createElement("button");
  btn.type = "button";
  btn.className = "agenda-chip filter-bar-chip" + (active ? " agenda-chip-active" : "");
  btn.textContent = text;
  return btn;
}
