// people-shared.jsx — atoms for the People (HR) module
//
// Shared between PeopleApp (manager) and PpEmployeeApp (employee):
//   PpAvatar, PpIcon (mapped to existing Icons), StatusPill,
//   KpiCard, PpSidebar, PpTopbar, PpRing (SVG progress ring),
//   PP_STATUS / leave colour tokens, helper utilities.

// ── status enum metadata ────────────────────────────────────────
const PP_STATUS = {
  in:      { label: "In",            bg: "#d6f5e8", fg: "#066039" },
  late:    { label: "Late",          bg: "#fef0d9", fg: "#8a5a14" },
  leave:   { label: "On leave",      bg: "#ecdaf6", fg: "#5a2891" },
  sick:    { label: "Sick",          bg: "#ecdaf6", fg: "#5a2891" },
  remote:  { label: "Remote",        bg: "#d9e8ff", fg: "#1d4e9a" },
  absent:  { label: "Absent",        bg: "#ffd9df", fg: "#8a1d2e" },
  holiday: { label: "Holiday",       bg: "#fff3e0", fg: "#8c4a00" },
  out:     { label: "Out",           bg: "#eef0f5", fg: "#4a4f5e" },
  pending: { label: "Pending",       bg: "#fff1d9", fg: "#8c5a00" },
  approved:{ label: "Approved",      bg: "#d6f5e8", fg: "#066039" },
  rejected:{ label: "Rejected",      bg: "#ffd9df", fg: "#8a1d2e" },
  draft:   { label: "Draft",         bg: "#eef0f4", fg: "#4a4f5e" },
};

// Leave-type colour fallbacks when the API row doesn't carry a
// colour. Keep the prototype's vocabulary so existing CSS works.
const PP_LEAVE_COLORS = {
  Vacation: "#0073ea",
  Sick:     "#e2445c",
  Personal: "#fdab3d",
  "Comp time": "#00c875",
  Bereavement: "#5a2891",
  Holiday:  "#8c5a00",
};

// ── PpAvatar — initials in a coloured square ────────────────────
function PpAvatar({ person, size = "sm" }) {
  // Re-render once celebration ids (birthday/anniversary) load, so
  // avatars that mounted before the fetch finished pick up the ring.
  const [, _ppForce] = React.useState(0);
  React.useEffect(() => {
    const h = () => _ppForce(v => v + 1);
    window.addEventListener("flowboard:bday-loaded", h);
    return () => window.removeEventListener("flowboard:bday-loaded", h);
  }, []);
  if (!person) return null;
  const isBday = !!(person.id && typeof window !== "undefined"
    && window.__fbBdayIds__ && window.__fbBdayIds__.has(person.id));
  // Work-anniversary celebration: same global treatment as birthday but
  // with a gold/teal ring + 🎉 badge. Birthday wins if both fall today.
  const isAnniv = !isBday && !!(person.id && typeof window !== "undefined"
    && window.__fbAnnivIds__ && window.__fbAnnivIds__.has(person.id));
  let el;
  if (person.avatar) {
    el = <img className={`pp-av pp-av-img ${size}`} src={person.avatar} alt={person.name}/>;
  } else {
    const initials = (person.name || "?")
      .trim().split(/\s+/).map(w => w[0]).slice(0, 2).join("").toUpperCase();
    el = (
      <div className={`pp-av ${size}`} style={{ background: person.color || "#b3b8c2" }}>
        {initials}
      </div>
    );
  }
  if (isBday) {
    return <span className={`avatar-bday-wrap avatar-bday-${size}`} aria-label="Birthday today">{el}</span>;
  }
  if (isAnniv) {
    return <span className={`avatar-anniv-wrap avatar-anniv-${size}`} aria-label="Work anniversary today">{el}</span>;
  }
  return el;
}

// ── PpIcon — maps the prototype's icon names onto Icons (ui.jsx)
// so we don't duplicate SVG paths. Falls back to a span when the
// name isn't in our existing set.
function PpIcon({ name, size = 14, ...props }) {
  const map = {
    Home: "Home", Clock: "Clock", Calendar: "Calendar", Sun: "Sun",
    Heart: "Heart", Coffee: "Coffee", Coffee2: "Coffee", Users: "Users",
    Bar: "Bar", Search: "Search", Filter: "Filter", Plus: "Plus",
    Download: "Download", ChevronR: "ChevronRt", ChevronL: "ChevronLt",
    Check: "Check", X: "Close", Pause: "Pause", Stop: "Stop",
    ArrowUp: "ArrowUp", ArrowDown: "ArrowDown", More: "DotsH",
    Bell: "Bell", MapPin: "MapPin", Play: "Play", Login: "Login",
    Logout: "Logout", Sparkle: "Sparkle",
  };
  const ic = (typeof Icons !== "undefined" && Icons[map[name]]) || null;
  if (ic) {
    const Cmp = ic;
    return <Cmp size={size} {...props}/>;
  }
  // Fallback dot.
  return <span aria-hidden="true" style={{ display: "inline-block", width: size, height: size }}/>;
}

// ── StatusPill ───────────────────────────────────────────────────
// Uses the prototype's .pp-pill-status.* class set. Falls back to
// inline styles if the status isn't in PP_STATUS so a future status
// the server adds doesn't render unstyled.
function PpStatusPill({ status, children }) {
  const meta = PP_STATUS[status] || { label: status, bg: "#eef0f5", fg: "#4a4f5e" };
  return (
    <span className={`pp-pill-status ${status}`}
          style={!PP_STATUS[status] ? { background: meta.bg, color: meta.fg } : null}>
      {children || meta.label}
    </span>
  );
}

// ── KpiCard — 4-up KPI strip atom ────────────────────────────────
function PpKpiCard({ icon, label, value, sub, delta, deltaTone = "" }) {
  return (
    <div className="pp-kpi">
      <div className="pp-kpi-label">
        {icon ? <PpIcon name={icon}/> : null} {label}
      </div>
      <div className="pp-kpi-value">{value}</div>
      {(delta || sub) && (
        <div className={`pp-kpi-delta ${deltaTone}`}>
          {deltaTone === "up"   && <PpIcon name="ArrowUp"/>}
          {deltaTone === "down" && <PpIcon name="ArrowDown"/>}
          {delta || sub}
        </div>
      )}
    </div>
  );
}

// ── PpRing — SVG progress ring (used by leave balances) ──────────
// Center label is absolutely positioned so the SVG dictates the box
// size. Without these inline positioning rules the label flows BELOW
// the ring — reported via screenshot, "12 days left" rendered outside
// the ring on the Leave balances card because no global .pp-ring CSS
// rule existed.
function PpRing({ pct, color = "#0073ea", size = 76, stroke = 7, label, sublabel }) {
  const r = (size - stroke) / 2;
  const c = 2 * Math.PI * r;
  const clamped = Math.max(0, Math.min(100, pct || 0));
  const dash = (clamped / 100) * c;
  // Scale the centered text so a 64px ring doesn't overflow with a
  // 2-digit number + caption.
  const valSize = Math.max(14, Math.round(size * 0.28));
  const capSize = Math.max(9,  Math.round(size * 0.14));
  return (
    <div className="pp-ring"
         style={{ position: "relative", width: size, height: size,
                  display: "inline-block", flex: "none" }}>
      <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}
           style={{ display: "block" }}>
        <circle cx={size / 2} cy={size / 2} r={r}
                stroke="#eef0f5" strokeWidth={stroke} fill="none"/>
        <circle cx={size / 2} cy={size / 2} r={r}
                stroke={color} strokeWidth={stroke} fill="none"
                strokeDasharray={`${dash} ${c}`}
                strokeLinecap="round"
                transform={`rotate(-90 ${size / 2} ${size / 2})`}/>
      </svg>
      {(label != null || sublabel) && (
        <div className="pp-ring-text"
             style={{
               position: "absolute", inset: 0,
               display: "flex", flexDirection: "column",
               alignItems: "center", justifyContent: "center",
               textAlign: "center", pointerEvents: "none",
               lineHeight: 1.1,
             }}>
          {label != null && (
            <div className="pp-ring-val"
                 style={{ color, fontWeight: 800, fontSize: valSize,
                          fontVariantNumeric: "tabular-nums",
                          letterSpacing: "-0.01em" }}>
              {label}
            </div>
          )}
          {sublabel && (
            <div className="pp-ring-cap"
                 style={{ fontSize: capSize, fontWeight: 600,
                          color: "var(--ink-muted, #6c7385)",
                          marginTop: 1, whiteSpace: "nowrap" }}>
              {sublabel}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ── PpSidebar — used by both shells; the manager variant adds the
// admin section. `items` is { id, label, icon, count } list.
function PpSidebar({ brand = "People", subtitle = "", items = [], adminItems = [], active, onChange }) {
  return (
    <aside className="pp-side">
      <div className="pp-side-brand">
        <div className="pp-side-logo">{(brand && brand[0]) || "P"}</div>
        <div>
          <div className="pp-side-name">{brand}</div>
          {subtitle && <div className="pp-side-sub">{subtitle}</div>}
        </div>
      </div>
      {items.map(it => (
        <div key={it.id}
             className={`pp-side-item ${active === it.id ? "is-active" : ""}`}
             onClick={() => onChange(it.id)}>
          <PpIcon name={it.icon}/> {it.label}
          {it.count ? <span className="pp-side-count">{it.count}</span> : null}
        </div>
      ))}
      {adminItems.length > 0 && (
        <>
          <div className="pp-side-section">Manage</div>
          {adminItems.map(it => (
            <div key={it.id}
                 className={`pp-side-item ${active === it.id ? "is-active" : ""}`}
                 onClick={() => onChange(it.id)}>
              <PpIcon name={it.icon}/> {it.label}
              {it.count ? <span className="pp-side-count">{it.count}</span> : null}
            </div>
          ))}
        </>
      )}
    </aside>
  );
}

// ── PpTopbar — breadcrumb + actions ──────────────────────────────
function PpTopbar({ crumbs = [], actions }) {
  return (
    <div className="pp-topbar">
      <div className="pp-crumbs">
        {crumbs.map((c, i) => (
          <React.Fragment key={i}>
            {i > 0 && <PpIcon name="ChevronR"/>}
            {i === crumbs.length - 1 ? <b>{c}</b> : <span>{c}</span>}
          </React.Fragment>
        ))}
      </div>
      <div className="pp-spacer"/>
      <div className="pp-topbar-actions">{actions}</div>
    </div>
  );
}

// ── helpers ─────────────────────────────────────────────────────
function ppFmtDateShort(iso) {
  if (!iso) return "—";
  const d = new Date(String(iso).slice(0, 10) + "T00:00:00");
  if (isNaN(d.getTime())) return iso;
  return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
}
function ppFmtRange(fromIso, toIso) {
  if (!fromIso) return "—";
  if (!toIso || fromIso === toIso) return ppFmtDateShort(fromIso);
  return ppFmtDateShort(fromIso) + " – " + ppFmtDateShort(toIso);
}
function ppRelTime(iso) {
  if (!iso) return "";
  const d = new Date(iso);
  if (isNaN(d.getTime())) return iso;
  const diff = Date.now() - d.getTime();
  const m = Math.floor(diff / 60000);
  if (m < 1)  return "just now";
  if (m < 60) return m + "m ago";
  const h = Math.floor(m / 60);
  if (h < 24) return h + "h ago";
  const days = Math.floor(h / 24);
  return days + "d ago";
}
function ppPctOfYear(d) {
  const date = d || new Date();
  const start = new Date(date.getFullYear(), 0, 1);
  const end   = new Date(date.getFullYear() + 1, 0, 1);
  return Math.round(((date - start) / (end - start)) * 100);
}
function ppPctOfDay(d) {
  const date = d || new Date();
  const mins = date.getHours() * 60 + date.getMinutes();
  return Math.round((mins / 1440) * 100);
}
function ppDow(d) {
  return ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][d.getDay()];
}

// Format a decimal-hours number as "Xh Ym" so 0.1 → "6m",
// 1.5 → "1h 30m", 8 → "8h". Used by the punch card, hero stats,
// and timeline week totals so the UI never shows "0.1h".
function ppFmtHours(hoursDecimal) {
  const totalMins = Math.max(0, Math.round((Number(hoursDecimal) || 0) * 60));
  const h = Math.floor(totalMins / 60);
  const m = totalMins % 60;
  if (h === 0 && m === 0) return "0m";
  if (h === 0) return m + "m";
  if (m === 0) return h + "h";
  return h + "h " + m + "m";
}

// ── PpTimeline — graphical day timeline ─────────────────────────
// One shared renderer used everywhere a day's punches are shown
// (Timesheets list, roster click-through, employee My-day). Draws a
// thin dashed baseline (untracked span), solid line segments (work
// green / break amber) and dots at the punch points (in green, break
// start/end amber, out grey). No tick/axis marks by design.
//
//   segments — [{ kind:'work'|'break', from, to }]  (minutes of day)
//   height   — px height of the strip (default 12)
//   win      — optional [startMin, endMin] window; defaults 08:00–19:00,
//              auto-expanding to include any out-of-window segment. The
//              tighter window means typical office punches (9–6) fill
//              more of the strip and are easier to scan; early-arrival
//              or late-departure days still render correctly because
//              the auto-expand keeps every segment inside the strip.
function PpTimeline({ segments = [], height = 12, win = null, band = null }) {
  const segs = Array.isArray(segments) ? segments : [];
  // No punches but it's a leave / holiday / absent day → show a labeled
  // band across the line instead of an empty timeline.
  if (!segs.length && band && band.text) {
    return (
      <div className="pp-tl" style={{ height }}>
        <div className="pp-tl-band" style={{ color: band.color || "#5b6172", background: (band.color || "#5b6172") + "22" }}>
          {band.text}
        </div>
      </div>
    );
  }
  // Defaults: 08:00 (480 min) → 19:00 (1140 min). Auto-expanded below
  // if any segment falls outside the window.
  let ws = 480, we = 1140;
  if (win && win.length === 2) { ws = win[0]; we = win[1]; }
  else if (segs.length) { ws = Math.min(ws, segs[0].from); we = Math.max(we, segs[segs.length - 1].to); }
  const span = Math.max(60, we - ws);
  const pct = v => Math.max(0, Math.min(100, ((v - ws) / span) * 100));

  const last = segs.length ? segs[segs.length - 1] : null;
  const isLive = !!(last && last.live);   // currently clocked in / on break

  // ── Build the dot list with a label per dot ──────────────────
  // Each dot carries `kind` (in / break-start / break-end / out / live)
  // and a formatted clock string so the hover tooltip + pill can
  // identify what time the dot represents.
  function fmtMins(m) {
    const h = Math.floor(m / 60), mm = m % 60;
    return String(h).padStart(2, "0") + ":" + String(mm).padStart(2, "0");
  }
  const KIND_LABEL = {
    in: "Clock in", "break-start": "Break start", "break-end": "Break end",
    out: "Clock out", live: "Working now",
  };
  const dots = [];
  if (segs.length) {
    dots.push({ at: segs[0].from, c: "#21a366", kind: "in" });          // clock-in
    segs.filter(s => s.kind === "break").forEach(b => {
      dots.push({ at: b.from, c: "#fdab3d", kind: "break-start" });
      if (!b.live) dots.push({ at: b.to, c: "#fdab3d", kind: "break-end" });
    });
    if (isLive) {
      dots.push({ at: last.to, c: "#ff8c1a", live: true, kind: "live" });
    } else {
      const le = last.to;
      if (le !== segs[0].from) dots.push({ at: le, c: "#e2445c", kind: "out" });
    }
  }

  return (
    <div className="pp-tl" style={{ height }}>
      <div className="pp-tl-base"/>
      {segs.map((s, i) => (
        <div key={"s" + i}
             className={"pp-tl-seg " + (s.kind === "break" ? "is-break" : "is-work") + (s.live ? " is-live" : "")}
             style={{ left: pct(s.from) + "%", width: Math.max(0.6, pct(s.to) - pct(s.from)) + "%" }}/>
      ))}
      {dots.map((d, i) => {
        const label = (KIND_LABEL[d.kind] || "") + " · " + fmtMins(d.at);
        return (
          <span key={"d" + i} className={"pp-tl-dot" + (d.live ? " is-live" : "")}
                style={{ left: pct(d.at) + "%", background: d.c }}
                title={label}
                aria-label={label}>
            <span className="pp-tl-dot-tip" style={{ background: d.c }}>
              <span className="pp-tl-dot-tip-icon" aria-hidden="true">🕒</span>
              {label}
            </span>
          </span>
        );
      })}
    </div>
  );
}

// Derive a leave/holiday/absent band for a resolved day (or null for a
// normal/weekend day). Used by the day-list timelines so a no-punch day
// labels WHY it's empty (e.g. "Sick Leave") right on the line.
function ppDayBand(d) {
  if (!d) return null;
  if (d.status === "leave")   return { text: d.leaveType || "Leave", color: "#a25ddc" };
  if (d.status === "holiday") return { text: d.holiday || "Holiday", color: "#e8862a" };
  if (d.status === "absent")  return { text: "Absent", color: "#c93636" };
  return null;
}

// Inject the timeline CSS once (works no matter which shell renders first).
(function ppInjectTimelineCss() {
  if (typeof document === "undefined" || document.getElementById("pp-tl-css")) return;
  const st = document.createElement("style");
  st.id = "pp-tl-css";
  st.textContent = [
    ".pp-tl{position:relative;width:100%;}",
    ".pp-tl-base{position:absolute;left:0;right:0;top:50%;border-top:1.5px dashed var(--border,#d3d8e0);}",
    ".pp-tl-seg{position:absolute;top:50%;transform:translateY(-50%);height:3px;border-radius:2px;}",
    ".pp-tl-seg.is-work{background:#21a366;}",
    ".pp-tl-seg.is-break{background:#fdab3d;}",
    // Live (ongoing) segment — marching stripes so it reads as "in progress".
    ".pp-tl-seg.is-live{background-image:repeating-linear-gradient(90deg,#21a366 0,#21a366 5px,#7fe0b3 5px,#7fe0b3 10px);background-size:10px 100%;animation:pp-tl-march .7s linear infinite;}",
    ".pp-tl-seg.is-break.is-live{background-image:repeating-linear-gradient(90deg,#fdab3d 0,#fdab3d 5px,#ffd699 5px,#ffd699 10px);}",
    "@keyframes pp-tl-march{from{background-position:0 0;}to{background-position:10px 0;}}",
    ".pp-tl-dot{position:absolute;top:50%;transform:translate(-50%,-50%);width:7px;height:7px;border-radius:50%;box-shadow:0 0 0 1.5px var(--bg-card,#fff);cursor:default;}",
    ".pp-tl-dot:hover{width:11px;height:11px;z-index:3;}",
    // Live "now" dot — orange with a pulsing ring.
    ".pp-tl-dot.is-live{width:9px;height:9px;animation:pp-tl-pulse 1.4s ease-out infinite;}",
    "@keyframes pp-tl-pulse{0%{box-shadow:0 0 0 0 rgba(255,140,26,.55),0 0 0 1.5px var(--bg-card,#fff);}70%{box-shadow:0 0 0 7px rgba(255,140,26,0),0 0 0 1.5px var(--bg-card,#fff);}100%{box-shadow:0 0 0 0 rgba(255,140,26,0),0 0 0 1.5px var(--bg-card,#fff);}}",
    // Hover tooltip pill — shown on dot hover. Anchored above the dot,
    // matches the dot's color, includes a small clock glyph + label.
    ".pp-tl-dot-tip{position:absolute;left:50%;bottom:calc(100% + 8px);transform:translateX(-50%);padding:3px 8px;border-radius:99px;color:#fff;font-size:10.5px;font-weight:700;line-height:1.2;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .12s ease;box-shadow:0 4px 10px rgba(15,23,41,.18);display:inline-flex;align-items:center;gap:4px;}",
    ".pp-tl-dot:hover .pp-tl-dot-tip{opacity:1;}",
    ".pp-tl-dot-tip-icon{font-size:11px;line-height:1;}",
    // Leave/holiday band — full-width labeled strip on the line.
    ".pp-tl-band{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;border-radius:5px;font-size:10.5px;font-weight:700;letter-spacing:.02em;text-transform:capitalize;overflow:hidden;white-space:nowrap;}",
  ].join("");
  document.head.appendChild(st);
})();

// Publish on window so the two shell files (people-employee.jsx,
// people-manager.jsx) can reach them without a build step.
Object.assign(window, {
  PP_STATUS, PP_LEAVE_COLORS,
  PpAvatar, PpIcon, PpStatusPill, PpKpiCard, PpRing,
  PpSidebar, PpTopbar, PpTimeline, ppDayBand,
  ppFmtDateShort, ppFmtRange, ppRelTime, ppPctOfYear, ppPctOfDay, ppDow,
  ppFmtHours,
});
