/* global React */
// Advisory pills for the hymn look-ahead system (see Advisory_Pills_Spec).
//
// Three advisory overlays on the tiered hymn lists. They INFORM the director's
// choice; they never hide or remove a hymn from any list:
//   ←  Last-sung   (backward) — actual performance, from window.SUNG_LOG
//   ↗  Better-ahead (forward) — same hymn at a HIGHER tier within 8 weeks
//   ↔  Also-ahead   (forward) — same hymn at the SAME tier within 8 weeks
//
// The two forward pills scan EXACTLY 8 weeks ahead of the viewed Sunday — a
// distance cap, not a season boundary (it deliberately crosses seasons). Only
// last-sung touches ordering, and only as a soft within-tier demotion.

const PILL_WINDOW_DAYS = 56; // N = 8 weeks
const SUNG_HORIZON_DAYS = 183; // ~6 months display horizon for last-sung
const MS_DAY = 86400000;

// ---- score / tier model -------------------------------------------------
// A hymn's standing on a given Sunday = its placement tier (primary > core
// pick > later list) blended with its rank band (R3 > R2 > R1). One comparable
// integer; higher is a stronger slot.
function _rankNum(h) {
  const m = /R\s*(\d)/i.exec(h && h.rank || "");
  return m ? +m[1] : 2;
}
function _placeWeight(placement) {
  return placement === "primary" ? 3 : placement === "core" ? 2 : 1;
}
function _band(h, placement) {
  return _placeWeight(placement) * 10 + _rankNum(h);
}

// ---- look-ahead index ---------------------------------------------------
// Every appearance of every hymn across the whole season, keyed "HYMNAL:NUM",
// each carrying its Sunday, timestamp, placement tier, and band. Built once;
// the data is static. This is the "rolling horizon resolved in memory" the
// spec requires before forward pills can be computed.
let _pillIdx = null;
function buildPillIndex(plans) {
  if (_pillIdx) return _pillIdx;
  const sundays = window.SUNDAYS || [];
  const tsOf = {}, dateOf = {}, labelOf = {};
  sundays.forEach((s) => {tsOf[s.id] = Date.parse(s.date) || 0;dateOf[s.id] = s.date;labelOf[s.id] = s.day;});
  const byKey = {};
  const push = (h, sundayId, placement) => {
    if (!h || h.num == null) return;
    const key = `${h.hymnal}:${h.num}`;
    (byKey[key] = byKey[key] || []).push({
      sundayId, ts: tsOf[sundayId] || 0, dateStr: dateOf[sundayId] || "",
      label: labelOf[sundayId] || "", placement, band: _band(h, placement)
    });
  };
  for (const id in plans || {}) {
    const pl = plans[id];
    (pl.primary || []).forEach((h) => push(h, id, "primary"));
    (pl.passages || []).forEach((p) => {
      (p.hymns || []).forEach((h, i) => push(h, id, i < 4 ? "core" : "more"));
    });
  }
  _pillIdx = { byKey };
  return _pillIdx;
}

// Most recent actual sung date on/before `currentTs`, or null.
function _lastSung(key, currentTs) {
  const log = window.SUNG_LOG || {};
  const dates = log[key.replace(":", " ")] || log[key];
  if (!dates || !dates.length) return null;
  let best = null;
  for (const d of dates) {
    const t = Date.parse(d);
    if (t && t <= currentTs && (best == null || t > best)) best = t;
  }
  return best;
}

// True if the hymn was actually sung within `days` of the viewed Sunday.
// Drives the soft within-tier demotion (NOT removal).
function sungWithin(key, currentTs, days) {
  const t = _lastSung(key, currentTs);
  return t != null && (currentTs - t) / MS_DAY <= days;
}

// Shared last-sung lookup for a bare log key (a "HYMNAL NUM" or a CCLI #).
// Returns { ts, days, weeks, within8 } within the display horizon, else null.
// planner.jsx renders this as the contemporary "← Sung … ago" pill.
function lastSungInfo(key, currentTs) {
  const t = _lastSung(key, currentTs);
  if (t == null) return null;
  const days = (currentTs - t) / MS_DAY;
  if (days > SUNG_HORIZON_DAYS) return null;
  return { ts: t, days, weeks: Math.round(days / 7), within8: days <= PILL_WINDOW_DAYS };
}

// The advisory result for one hymn on one Sunday: { lastSung?, better?, also? }.
// `better`/`also` reference the BEST upcoming slot (highest band; tie → earliest).
function computePills(key, currentSundayId, currentTs) {
  const idx = buildPillIndex(window.HYMN_PLAN);
  const apps = idx.byKey[key] || [];
  let curBand = -1;
  for (const a of apps) if (a.sundayId === currentSundayId && a.band > curBand) curBand = a.band;

  const horizon = currentTs + PILL_WINDOW_DAYS * MS_DAY;
  let best = null;
  for (const a of apps) {
    if (a.ts > currentTs && a.ts <= horizon) {
      if (!best || a.band > best.band || a.band === best.band && a.ts < best.ts) best = a;
    }
  }

  const out = {};
  if (best && curBand >= 0) {
    if (best.band > curBand) out.better = best;else
    if (best.band === curBand) out.also = best;
  }

  const sungTs = _lastSung(key, currentTs);
  if (sungTs != null) {
    const days = (currentTs - sungTs) / MS_DAY;
    if (days <= SUNG_HORIZON_DAYS) {
      out.lastSung = { ts: sungTs, days, weeks: Math.round(days / 7), within8: days <= PILL_WINDOW_DAYS };
    }
  }
  return out;
}

// ---- labels -------------------------------------------------------------
function _shortDate(s) {return (s || "").replace(/,\s*\d+\s*$/, "");}
function _placeWord(p) {return p === "primary" ? "primary" : p === "core" ? "core pick" : "later pick";}
function _sungLabel(ls) {
  if (ls.within8) {
    const w = ls.weeks;
    return w <= 0 ? "Sung this week" : w === 1 ? "Sung 1 week ago" : `Sung ${w} weeks ago`;
  }
  const d = new Date(ls.ts);
  return `Sung ${d.toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" })}`;
}

// ---- pill stack component ----------------------------------------------
// Renders whatever pills apply, each individually dismissible per hymn-per-
// Sunday. Quiet styling — badges remain the dominant information.
function HymnPillStack({ pills, sundayId, hymnKey, dismissed, onDismiss }) {
  if (!pills) return null;
  const base = `${sundayId}|${hymnKey}|`;
  const items = [];

  if (pills.lastSung)
  items.push({
    type: "last", cls: "wp-pill-last", glyph: "←",
    text: _sungLabel(pills.lastSung),
    title: "Actually sung recently. It sorts to the bottom of its tier this week but stays available — a soft nudge, not a block."
  });
  if (pills.better)
  items.push({
    type: "better", cls: "wp-pill-better", glyph: "↗",
    text: `Stronger match ${_shortDate(pills.better.dateStr)} (${_placeWord(pills.better.placement)})`,
    title: `This hymn fits today, but it's an even stronger match on ${_shortDate(pills.better.dateStr)} (${pills.better.label}). Using it now may spend your best ${_shortDate(pills.better.dateStr)} pick early.`
  });
  if (pills.also)
  items.push({
    type: "also", cls: "wp-pill-also", glyph: "↔",
    text: `Also fits ${_shortDate(pills.also.dateStr)} (same tier)`,
    title: `Returns at equal quality on ${_shortDate(pills.also.dateStr)} (${pills.also.label}) — safe to bank for then if you're short on slots today.`
  });

  const visible = items.filter((it) => !(dismissed && dismissed[base + it.type]));
  if (!visible.length) return null;

  return (
    <span className="wp-pills">
      {visible.map((it) =>
      <span key={it.type} className={`wp-pill ${it.cls}`} title={it.title}>
          <span className="wp-pill-glyph" aria-hidden="true">{it.glyph}</span>
          <span className="wp-pill-text">{it.text}</span>
          <button type="button" className="wp-pill-x" aria-label="Dismiss"
        onClick={(e) => {e.stopPropagation();onDismiss && onDismiss(base + it.type);}}>×</button>
        </span>
      )}
    </span>);

}

// sungPillLabel shares the last-sung wording with the advisory pill stack.
const sungPillLabel = _sungLabel;

Object.assign(window, { buildPillIndex, computePills, sungWithin, HymnPillStack, lastSungInfo, sungPillLabel });
