import { t, tDyn, type I18nKey, getLang } from "../i18n";
import type { RenderSpec, ViewRow } from "./types";

// shape-calendar: month / week / day views. The view switcher is rendered
// inline above the grid; the active view persists in the URL via
// ?cal_view= so /views/<slug>?cal_view=day&cal_date=2026-05-18 is a
// shareable deep-link. Each view buckets the same flat ViewRow[] by
// ISO-date — only the rendering differs.

type CalView = "month" | "week" | "day";

const VIEW_PARAM = "cal_view";
const DATE_PARAM = "cal_date";
const MAX_PILLS_PER_MONTH_CELL = 3;

export function renderCalendarShape(host: HTMLElement, rows: ViewRow[], render: RenderSpec): void {
  host.innerHTML = "";
  const cfg = render.calendar ?? {};

  // Mobile fallback: viewport <600px collapses to cards (cleaner on narrow
  // screens). Documented in design §9 trade-off 8.
  if (window.innerWidth < 600) {
    const notice = document.createElement("p");
    notice.className = "views-calendar-mobile-notice";
    notice.textContent = t("views.calendar.mobile_fallback");
    host.appendChild(notice);
  }

  const initialView = readView(cfg.default_view);
  const anchor = readAnchor(rows);
  paint(host, rows, anchor, initialView);
}

// paint redraws the calendar in the supplied view + anchor. Called from
// the view switcher and from the day/week navigation buttons. Each paint
// clears the host so we don't leak prior DOM.
function paint(host: HTMLElement, rows: ViewRow[], anchor: Date, view: CalView): void {
  // Keep the mobile-notice (first child) if present; everything else is
  // re-rendered each time.
  const notice = host.querySelector<HTMLElement>(".views-calendar-mobile-notice");
  host.innerHTML = "";
  if (notice) host.appendChild(notice);

  const wrap = document.createElement("div");
  wrap.className = `views-calendar views-calendar--${view}`;
  wrap.appendChild(renderToolbar(view, anchor, (nextView, nextAnchor) => {
    writeURL(nextView, nextAnchor);
    paint(host, rows, nextAnchor, nextView);
  }));

  if (view === "month") {
    wrap.appendChild(renderMonth(anchor, rows, (clickedDate) => {
      writeURL("day", clickedDate);
      paint(host, rows, clickedDate, "day");
    }));
  } else if (view === "week") {
    wrap.appendChild(renderWeek(anchor, rows));
  } else {
    wrap.appendChild(renderDay(anchor, rows));
  }

  host.appendChild(wrap);
}

// --- Toolbar -------------------------------------------------------------

function renderToolbar(
  view: CalView,
  anchor: Date,
  onNav: (view: CalView, anchor: Date) => void,
): HTMLElement {
  const bar = document.createElement("div");
  bar.className = "views-calendar-toolbar";

  // View switcher: month / week / day chips.
  const switcher = document.createElement("div");
  switcher.className = "views-calendar-view-switcher agenda-chip-row";
  switcher.setAttribute("role", "tablist");
  for (const v of ["month", "week", "day"] as CalView[]) {
    const chip = document.createElement("button");
    chip.type = "button";
    chip.className = "agenda-chip views-calendar-view-chip" + (v === view ? " agenda-chip-active" : "");
    chip.dataset.calView = v;
    chip.setAttribute("role", "tab");
    chip.setAttribute("aria-selected", v === view ? "true" : "false");
    chip.textContent = t(`cal.view.${v}` as I18nKey);
    chip.addEventListener("click", () => {
      if (v === view) return;
      onNav(v, anchor);
    });
    switcher.appendChild(chip);
  }
  bar.appendChild(switcher);

  // Prev / current-label / next. Step size depends on the view.
  const nav = document.createElement("div");
  nav.className = "views-calendar-nav";

  const prev = document.createElement("button");
  prev.type = "button";
  prev.className = "btn-secondary btn-small views-calendar-nav-btn";
  prev.setAttribute("aria-label", t(navLabelKey(view, "prev")));
  prev.textContent = "‹";
  prev.addEventListener("click", () => onNav(view, shift(anchor, view, -1)));
  nav.appendChild(prev);

  const label = document.createElement("span");
  label.className = "views-calendar-nav-label";
  label.textContent = formatRangeLabel(view, anchor);
  nav.appendChild(label);

  const next = document.createElement("button");
  next.type = "button";
  next.className = "btn-secondary btn-small views-calendar-nav-btn";
  next.setAttribute("aria-label", t(navLabelKey(view, "next")));
  next.textContent = "›";
  next.addEventListener("click", () => onNav(view, shift(anchor, view, 1)));
  nav.appendChild(next);

  // Day/week view: provide a "Zurück zum Monat" link so users can climb
  // back without hunting for the switcher chip.
  if (view !== "month") {
    const backToMonth = document.createElement("button");
    backToMonth.type = "button";
    backToMonth.className = "btn-link views-calendar-back-to-month";
    backToMonth.textContent = t("cal.day.back_to_month");
    backToMonth.addEventListener("click", () => onNav("month", anchor));
    nav.appendChild(backToMonth);
  }

  bar.appendChild(nav);
  return bar;
}

function navLabelKey(view: CalView, dir: "prev" | "next"): I18nKey {
  if (view === "month") return dir === "prev" ? "cal.month.prev" : "cal.month.next";
  if (view === "week")  return dir === "prev" ? "cal.week.prev"  : "cal.week.next";
  return dir === "prev" ? "cal.day.prev" : "cal.day.next";
}

// --- Month view ----------------------------------------------------------

function renderMonth(anchor: Date, rows: ViewRow[], onDayDrill: (d: Date) => void): HTMLElement {
  const lang = getLang() === "de" ? "de-DE" : "en-GB";
  const wrap = document.createElement("div");
  wrap.className = "views-calendar-month";

  const header = document.createElement("h2");
  header.className = "views-calendar-month-label";
  header.textContent = anchor.toLocaleDateString(lang, { month: "long", year: "numeric" });
  wrap.appendChild(header);

  // Single grid with one column-template that the weekday row and the day
  // cells share. The header row is added with `grid-column: span 7` so
  // it spans the full width above the day grid (laid out below).
  const grid = document.createElement("div");
  grid.className = "views-calendar-grid";

  const weekdayKeys: I18nKey[] = [
    "cal.day.mon", "cal.day.tue", "cal.day.wed", "cal.day.thu",
    "cal.day.fri", "cal.day.sat", "cal.day.sun",
  ];
  for (const k of weekdayKeys) {
    const cell = document.createElement("div");
    cell.className = "views-calendar-weekday";
    cell.textContent = t(k);
    grid.appendChild(cell);
  }

  const monthStart = new Date(anchor.getFullYear(), anchor.getMonth(), 1);
  const startWeekday = (monthStart.getDay() + 6) % 7; // Mon=0
  const daysInMonth = new Date(anchor.getFullYear(), anchor.getMonth() + 1, 0).getDate();

  // Pad start with prev-month spillover.
  for (let i = 0; i < startWeekday; i++) {
    const cell = document.createElement("div");
    cell.className = "views-calendar-cell views-calendar-cell--out";
    grid.appendChild(cell);
  }

  // Bucket rows by ISO date (yyyy-mm-dd) within the visible month.
  const byDate = bucketByDate(rows, (d) =>
    d.getMonth() === anchor.getMonth() && d.getFullYear() === anchor.getFullYear(),
  );

  for (let day = 1; day <= daysInMonth; day++) {
    const dayDate = new Date(anchor.getFullYear(), anchor.getMonth(), day);
    const dateKey = isoDate(dayDate);
    const dayRows = byDate.get(dateKey) ?? [];
    const cell = renderMonthCell(dayDate, day, dayRows, onDayDrill);
    grid.appendChild(cell);
  }

  wrap.appendChild(grid);
  return wrap;
}

function renderMonthCell(
  dayDate: Date,
  dayNum: number,
  dayRows: ViewRow[],
  onDayDrill: (d: Date) => void,
): HTMLElement {
  const cell = document.createElement("div");
  cell.className = "views-calendar-cell";
  if (isToday(dayDate)) cell.classList.add("views-calendar-cell--today");
  if (dayRows.length > 0) cell.classList.add("views-calendar-cell--has");

  // Day-number is a click-target that switches to the day view. We render
  // it as a button to keep keyboard semantics; the surrounding cell stays
  // a div so it doesn't compete with the inner row anchors.
  const dayLabel = document.createElement("button");
  dayLabel.type = "button";
  dayLabel.className = "views-calendar-cell-day";
  dayLabel.textContent = String(dayNum);
  dayLabel.setAttribute("aria-label", t("cal.day.open_day"));
  dayLabel.addEventListener("click", (e) => {
    e.stopPropagation();
    onDayDrill(dayDate);
  });
  cell.appendChild(dayLabel);

  if (dayRows.length > 0) {
    const ul = document.createElement("ul");
    ul.className = "views-calendar-pills";
    const visible = dayRows.slice(0, MAX_PILLS_PER_MONTH_CELL);
    for (const row of visible) {
      ul.appendChild(renderPill(row));
    }
    if (dayRows.length > visible.length) {
      const more = document.createElement("li");
      const moreBtn = document.createElement("button");
      moreBtn.type = "button";
      moreBtn.className = "views-calendar-pill views-calendar-pill--more";
      moreBtn.textContent = `+${dayRows.length - visible.length}`;
      moreBtn.setAttribute("aria-label", t("cal.day.open_day"));
      moreBtn.addEventListener("click", (e) => {
        e.stopPropagation();
        onDayDrill(dayDate);
      });
      more.appendChild(moreBtn);
      ul.appendChild(more);
    }
    cell.appendChild(ul);
  }
  return cell;
}

// --- Week view -----------------------------------------------------------

function renderWeek(anchor: Date, rows: ViewRow[]): HTMLElement {
  const lang = getLang() === "de" ? "de-DE" : "en-GB";
  const wrap = document.createElement("div");
  wrap.className = "views-calendar-week";

  const weekStart = startOfWeek(anchor);
  const header = document.createElement("h2");
  header.className = "views-calendar-month-label";
  const weekEnd = new Date(weekStart);
  weekEnd.setDate(weekStart.getDate() + 6);
  header.textContent = formatWeekHeader(weekStart, weekEnd, lang);
  wrap.appendChild(header);

  const grid = document.createElement("div");
  grid.className = "views-calendar-week-grid";

  for (let i = 0; i < 7; i++) {
    const day = new Date(weekStart);
    day.setDate(weekStart.getDate() + i);
    const col = renderWeekColumn(day, rows);
    grid.appendChild(col);
  }

  wrap.appendChild(grid);
  return wrap;
}

function renderWeekColumn(day: Date, rows: ViewRow[]): HTMLElement {
  const lang = getLang() === "de" ? "de-DE" : "en-GB";
  const col = document.createElement("div");
  col.className = "views-calendar-week-column";
  if (isToday(day)) col.classList.add("views-calendar-week-column--today");

  const head = document.createElement("div");
  head.className = "views-calendar-week-head";
  const weekdayKey = WEEKDAY_KEYS[(day.getDay() + 6) % 7];
  const dow = document.createElement("span");
  dow.className = "views-calendar-week-dow";
  dow.textContent = t(weekdayKey);
  const dnum = document.createElement("span");
  dnum.className = "views-calendar-week-dnum";
  dnum.textContent = day.toLocaleDateString(lang, { day: "numeric", month: "short" });
  head.appendChild(dow);
  head.appendChild(dnum);
  col.appendChild(head);

  // No 3-row cap on week / day views — show everything for that day.
  const dayRows = filterByDay(rows, day);
  if (dayRows.length === 0) {
    const empty = document.createElement("p");
    empty.className = "views-calendar-week-empty";
    empty.textContent = t("cal.day.no_entries");
    col.appendChild(empty);
    return col;
  }

  const ul = document.createElement("ul");
  ul.className = "views-calendar-week-list";
  for (const row of dayRows) {
    const li = document.createElement("li");
    li.appendChild(renderRowAnchor(row, "week"));
    ul.appendChild(li);
  }
  col.appendChild(ul);
  return col;
}

// --- Day view ------------------------------------------------------------

function renderDay(anchor: Date, rows: ViewRow[]): HTMLElement {
  const lang = getLang() === "de" ? "de-DE" : "en-GB";
  const wrap = document.createElement("div");
  wrap.className = "views-calendar-day-wrap";

  const header = document.createElement("h2");
  header.className = "views-calendar-month-label";
  header.textContent = anchor.toLocaleDateString(lang, {
    weekday: "long", year: "numeric", month: "long", day: "numeric",
  });
  wrap.appendChild(header);

  const dayRows = filterByDay(rows, anchor);
  if (dayRows.length === 0) {
    const empty = document.createElement("p");
    empty.className = "views-calendar-day-empty";
    empty.textContent = t("cal.day.no_entries");
    wrap.appendChild(empty);
    return wrap;
  }

  const ul = document.createElement("ul");
  ul.className = "views-calendar-day-list";
  for (const row of dayRows) {
    const li = document.createElement("li");
    li.appendChild(renderRowAnchor(row, "day"));
    ul.appendChild(li);
  }
  wrap.appendChild(ul);
  return wrap;
}

// --- Row rendering -------------------------------------------------------

function renderPill(row: ViewRow): HTMLElement {
  const li = document.createElement("li");
  const a = document.createElement("a");
  a.className = `views-calendar-pill views-calendar-pill--${row.kind}`;
  a.href = rowHref(row);
  a.textContent = row.title;
  a.title = row.title + (row.project_title ? ` — ${row.project_title}` : "");
  // Pills are anchors — month-cell day-button click ignores them via
  // stopPropagation on the button; cell-level handlers would intercept
  // them otherwise.
  a.addEventListener("click", (e) => e.stopPropagation());
  li.appendChild(a);
  return li;
}

function renderRowAnchor(row: ViewRow, density: "week" | "day"): HTMLElement {
  const a = document.createElement("a");
  a.className = `views-calendar-row views-calendar-row--${density} views-calendar-row--${row.kind}`;
  a.href = rowHref(row);

  const dot = document.createElement("span");
  dot.className = `views-calendar-row-dot views-calendar-row-dot--${row.kind}`;
  a.appendChild(dot);

  const body = document.createElement("span");
  body.className = "views-calendar-row-body";

  const title = document.createElement("span");
  title.className = "views-calendar-row-title";
  title.textContent = row.title;
  body.appendChild(title);

  const metaParts: string[] = [];
  metaParts.push(tDyn("views.kind." + row.kind));
  if (row.project_reference) metaParts.push(row.project_reference);
  else if (row.project_title) metaParts.push(row.project_title);
  if (metaParts.length > 0) {
    const meta = document.createElement("span");
    meta.className = "views-calendar-row-meta";
    meta.textContent = metaParts.join(" · ");
    body.appendChild(meta);
  }

  a.appendChild(body);
  return a;
}

function rowHref(row: ViewRow): string {
  switch (row.kind) {
    case "deadline":         return `/deadlines/${encodeURIComponent(row.id)}`;
    case "appointment":      return `/appointments/${encodeURIComponent(row.id)}`;
    case "approval_request": return `/inbox`;
    case "project_event":
      // project_events surface on the project's Verlauf — best we can do
      // is link to the project. If no project, leave as a non-link target.
      return row.project_id ? `/projects/${encodeURIComponent(row.project_id)}` : "#";
  }
}

// --- Bucketing / date helpers --------------------------------------------

const WEEKDAY_KEYS: I18nKey[] = [
  "cal.day.mon", "cal.day.tue", "cal.day.wed", "cal.day.thu",
  "cal.day.fri", "cal.day.sat", "cal.day.sun",
];

function bucketByDate(rows: ViewRow[], filter: (d: Date) => boolean): Map<string, ViewRow[]> {
  const out = new Map<string, ViewRow[]>();
  for (const row of rows) {
    const d = new Date(row.event_date);
    if (isNaN(d.getTime())) continue;
    if (!filter(d)) continue;
    const key = isoDate(d);
    const arr = out.get(key);
    if (arr) arr.push(row);
    else out.set(key, [row]);
  }
  return out;
}

function filterByDay(rows: ViewRow[], day: Date): ViewRow[] {
  const key = isoDate(day);
  return rows.filter((r) => {
    const d = new Date(r.event_date);
    if (isNaN(d.getTime())) return false;
    return isoDate(d) === key;
  });
}

function startOfWeek(d: Date): Date {
  const out = new Date(d.getFullYear(), d.getMonth(), d.getDate());
  const offset = (out.getDay() + 6) % 7; // Mon=0
  out.setDate(out.getDate() - offset);
  return out;
}

function shift(d: Date, view: CalView, dir: number): Date {
  if (view === "month") return new Date(d.getFullYear(), d.getMonth() + dir, 1);
  if (view === "week")  return new Date(d.getFullYear(), d.getMonth(), d.getDate() + dir * 7);
  return new Date(d.getFullYear(), d.getMonth(), d.getDate() + dir);
}

function isToday(d: Date): boolean {
  const now = new Date();
  return d.getFullYear() === now.getFullYear()
    && d.getMonth() === now.getMonth()
    && d.getDate() === now.getDate();
}

function isoDate(d: Date): string {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${y}-${m}-${day}`;
}

function formatRangeLabel(view: CalView, anchor: Date): string {
  const lang = getLang() === "de" ? "de-DE" : "en-GB";
  if (view === "month") {
    return anchor.toLocaleDateString(lang, { month: "long", year: "numeric" });
  }
  if (view === "week") {
    const start = startOfWeek(anchor);
    const end = new Date(start);
    end.setDate(start.getDate() + 6);
    return formatWeekHeader(start, end, lang);
  }
  return anchor.toLocaleDateString(lang, {
    weekday: "short", year: "numeric", month: "long", day: "numeric",
  });
}

function formatWeekHeader(start: Date, end: Date, lang: string): string {
  const startStr = start.toLocaleDateString(lang, { day: "numeric", month: "short" });
  const endStr = end.toLocaleDateString(lang, { day: "numeric", month: "short", year: "numeric" });
  return `${startStr} – ${endStr}`;
}

// --- URL state -----------------------------------------------------------

function readView(defaultView: CalView | undefined): CalView {
  const params = new URLSearchParams(window.location.search);
  const raw = params.get(VIEW_PARAM);
  if (raw === "month" || raw === "week" || raw === "day") return raw;
  return defaultView ?? "month";
}

function readAnchor(rows: ViewRow[]): Date {
  const params = new URLSearchParams(window.location.search);
  const raw = params.get(DATE_PARAM);
  if (raw) {
    const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(raw);
    if (m) {
      const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
      if (!isNaN(d.getTime())) return d;
    }
  }
  // No URL anchor — pick the first row's date, or today.
  for (const row of rows) {
    const d = new Date(row.event_date);
    if (!isNaN(d.getTime())) return new Date(d.getFullYear(), d.getMonth(), d.getDate());
  }
  const now = new Date();
  return new Date(now.getFullYear(), now.getMonth(), now.getDate());
}

function writeURL(view: CalView, anchor: Date): void {
  const url = new URL(window.location.href);
  url.searchParams.set(VIEW_PARAM, view);
  url.searchParams.set(DATE_PARAM, isoDate(anchor));
  history.replaceState(null, "", url.toString());
}
