// components.jsx — Avatar, SevDot, SevChip, PageHeader. Pure UI, no data deps.
const { Fragment: F } = React;

function SevDot({ sev, size = 8 }) {
  const meta = window.YDLoader.SEV_META[sev];
  if (!meta) return null;
  return React.createElement('span', {
    style: { display: 'inline-block', width: size, height: size, borderRadius: '50%', background: meta.color, flexShrink: 0 },
  });
}

function SevChip({ sev, count, onClick, active }) {
  const meta = window.YDLoader.SEV_META[sev];
  return (
    <button
      onClick={onClick}
      className={'sev-chip' + (active ? ' active' : '')}
      style={{
        background: active ? meta.color : meta.bg,
        color: active ? '#fff' : meta.color,
        borderColor: active ? meta.color : meta.border,
      }}>
      <SevDot sev={sev} size={7}/>
      <span className="lbl">{meta.label}</span>
      {count != null && <span className="ct">{count}</span>}
    </button>
  );
}

function Avatar({ name, size = 26 }) {
  const safe = name || '?';
  const initials = safe.split(/\s+/).map(s => s[0]).filter(Boolean).slice(0, 2).join('').toUpperCase() || '?';
  let h = 0;
  for (let i = 0; i < safe.length; i++) h = (h * 31 + safe.charCodeAt(i)) % 360;
  return (
    <div style={{
      width: size, height: size, borderRadius: '50%',
      background: `hsl(${h}, 30%, 92%)`,
      color: `hsl(${h}, 40%, 28%)`,
      fontSize: size * 0.42, fontWeight: 700,
      display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
      flexShrink: 0, letterSpacing: '-0.02em',
    }}>{initials}</div>
  );
}

function PageHeader({ eyebrow, title, subtitle, actions }) {
  return (
    <div className="page-hd">
      <div>
        {eyebrow && <div className="eyebrow">{eyebrow}</div>}
        <h1 className="page-title">{title}</h1>
        {subtitle && <div className="page-sub">{subtitle}</div>}
      </div>
      {actions && <div className="page-actions">{actions}</div>}
    </div>
  );
}

// Action types match what the legacy app recorded — first three are
// quick contacts (a Dismiss closes them), last three escalation tiers
// (require target + review-by date and close via Resolve / Escalate).
// addActionItem stores `actionType` separately from `action` (free-text note).
const ACTION_TYPES = [
  'Called',
  'Messaged',
  'Coaching note',
  'Performance Notice',
  'Verbal Warning',
  'Pace Improvement Plan',
];

const MONITORED_TYPES = new Set([
  'Performance Notice',
  'Verbal Warning',
  'Pace Improvement Plan',
]);

// Default review-by horizons in days (PIP gets the longest runway, contact
// logs are unused since they bypass review entirely).
const REVIEW_DEFAULT_DAYS = {
  'Performance Notice': 7,
  'Verbal Warning': 7,
  'Pace Improvement Plan': 14,
};

function isoDateOffset(days) {
  const d = new Date();
  d.setDate(d.getDate() + days);
  return d.toISOString().slice(0, 10);
}

// Shared modal used by Clock-in, Performance, Roster, and Action Log tabs.
// `targets` is an array — singular UX is just an array of one. Calls
// addActionItem for each target serially. Monitored types reveal target +
// review-by fields; the backend rejects the write without them.
// `defaultType` lets callers pre-select an action type (e.g. the Action Log
// suggestion-card opens straight into "Pace Improvement Plan" with the
// monitored fields already showing).
function ActionModal({ targets, onClose, onSaved, defaultType }) {
  const initialType = defaultType && ACTION_TYPES.indexOf(defaultType) >= 0 ? defaultType : ACTION_TYPES[0];
  const [type, setType] = React.useState(initialType);
  const [note, setNote] = React.useState('');
  const [target, setTarget] = React.useState('');
  const [reviewBy, setReviewBy] = React.useState(() => {
    if (MONITORED_TYPES.has(initialType)) {
      return isoDateOffset(REVIEW_DEFAULT_DAYS[initialType] || 7);
    }
    return isoDateOffset(7);
  });
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState(null);

  const isMonitored = MONITORED_TYPES.has(type);

  // Bump default reviewBy when the user picks a monitored type — only
  // overrides if the field still holds the previous default (don't trample
  // an explicit user pick).
  const onTypeChange = (e) => {
    const next = e.target.value;
    setType(next);
    if (MONITORED_TYPES.has(next)) {
      const days = REVIEW_DEFAULT_DAYS[next] || 7;
      setReviewBy(isoDateOffset(days));
    }
  };

  const submit = async () => {
    if (isMonitored) {
      if (!target.trim()) { setErr('Target description is required for ' + type + '.'); return; }
      if (!reviewBy)      { setErr('Review-by date is required for ' + type + '.'); return; }
    }
    setBusy(true);
    setErr(null);
    try {
      for (const m of targets) {
        const payload = {
          email: m.email,
          name: m.name,
          team: m.team,
          action: note || type,
          actionType: type,
        };
        if (isMonitored) {
          payload.targetDescription = target.trim();
          payload.reviewBy = reviewBy;
        }
        await window.YDApp.call('addActionItem', payload);
      }
      onSaved(targets.length);
    } catch (e) {
      setErr(e.message || String(e));
      setBusy(false);
    }
  };

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-card" onClick={(e) => e.stopPropagation()}>
        <div className="modal-hd">
          <h3>
            Log action {targets.length > 1
              ? `· ${targets.length} people`
              : `· ${targets[0]?.name}`}
          </h3>
          <button className="modal-x" onClick={onClose} aria-label="Close">×</button>
        </div>
        <div className="modal-bd">
          <label className="modal-lbl">Type</label>
          <select className="modal-input" value={type} onChange={onTypeChange} disabled={busy}>
            {ACTION_TYPES.map((t) => <option key={t}>{t}</option>)}
          </select>
          {isMonitored && (
            <>
              <label className="modal-lbl">Target to reach</label>
              <textarea
                className="modal-input"
                value={target}
                onChange={(e) => setTarget(e.target.value)}
                disabled={busy}
                rows={2}
                placeholder="e.g. clock in on time 5 days running, hit 80% of pace…"/>
              <label className="modal-lbl">Review by</label>
              <input
                type="date"
                className="modal-input"
                value={reviewBy}
                onChange={(e) => setReviewBy(e.target.value)}
                disabled={busy}
                min={isoDateOffset(0)}/>
              <div className="meta" style={{ marginTop: -4, marginBottom: 6 }}>
                Stays on the Active list until you mark it Met or Not met.
              </div>
            </>
          )}
          <label className="modal-lbl">Note (optional)</label>
          <textarea
            className="modal-input"
            value={note}
            onChange={(e) => setNote(e.target.value)}
            disabled={busy}
            rows={3}
            placeholder="Anything to remember…"/>
          {err && <div className="modal-err">{err}</div>}
        </div>
        <div className="modal-actions">
          <button className="btn" onClick={onClose} disabled={busy}>Cancel</button>
          <button className="btn btn-dark" onClick={submit} disabled={busy}>
            {busy ? 'Saving…' : 'Save'}
          </button>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { SevDot, SevChip, Avatar, PageHeader, ActionModal, ACTION_TYPES, MONITORED_TYPES });

// Returns a make-up summary for an annotator if their cumulative weekly
// target through yesterday is short, otherwise null.
//
// Cumulative accounting (changed from the original yesterday-only logic):
// we walk every committed (non-leave) day BEFORE today and sum each
// axis. If actual < target on either axis, that gap rolls forward —
// missing Monday AND Tuesday means today owes both days' deficits, not
// just Tuesday's. Today's actuals contribute to clearing the cumulative
// gap; once today's actual ≥ today's effective target, `madeUp` flips.
//
// Make-up window is "by EOD today" — if they don't clear by then,
// tomorrow's effective target rolls again with the new day added.
//
// Used by MeApp to show the annotator their make-up status, and by the
// Action Log's Make-up watch card to show PLs who's still owed.
//
// Inputs are uniform across MeApp (data.weekly.days + data.kpi.dailyBreakdown
// + data.expectedPace) and lead-side (member.dailyShifts + member.dailyKpi
// + member.expectedPace). Both have the fields we need with the same names.
function computeMakeUp({ days, kpi, expectedPace }) {
  if (!Array.isArray(days) || !days.length) return null;
  if (!expectedPace || expectedPace <= 0) return null;

  const todayStr = new Date().toISOString().slice(0, 10);
  const todayIdx = days.findIndex((d) => d && d.date === todayStr);
  if (todayIdx <= 0) return null; // need at least one day before today

  // Walk every past committed day this week, accumulate target vs actual.
  let cumTargetRes = 0;
  let cumActualRes = 0;
  let cumCommittedHrs = 0;
  let cumQuidloHrs = 0;
  const missedDays = [];
  for (let i = 0; i < todayIdx; i++) {
    const d = days[i] || {};
    if (d.isLeave) continue;
    const committed = d.committed || 0;
    if (committed <= 0) continue;
    const dayTarget = expectedPace * committed;
    const dayActual = (kpi && kpi[i] && kpi[i].resources) || 0;
    cumTargetRes += dayTarget;
    cumActualRes += dayActual;
    cumCommittedHrs += committed;
    cumQuidloHrs += d.quidlo || 0;
    if (dayActual < dayTarget) missedDays.push(d.day || `Day ${i + 1}`);
  }

  const resDeficit = Math.max(0, cumTargetRes - cumActualRes);
  const hrDeficit = Math.max(0, cumCommittedHrs - cumQuidloHrs);
  if (resDeficit <= 0 && hrDeficit <= 0) return null;

  const today = days[todayIdx] || {};
  const todayKpi = (kpi && kpi[todayIdx]) || { resources: 0, hours: 0 };
  const todayCommitted = today.committed || 0;
  const baseTargetRes = expectedPace * todayCommitted;
  const effectiveTargetRes = baseTargetRes + resDeficit;
  const effectiveTargetHrs = todayCommitted + hrDeficit;
  const todayActualRes = todayKpi.resources || 0;
  const todayActualHrs = today.quidlo || 0;

  const resStillNeeded = Math.max(0, effectiveTargetRes - todayActualRes);
  const hrStillNeeded = Math.max(0, effectiveTargetHrs - todayActualHrs);

  const resCleared = resDeficit === 0 || resStillNeeded <= 0;
  const hrCleared = hrDeficit === 0 || hrStillNeeded <= 0;
  const madeUp = resCleared && hrCleared;

  // Compact label for "from <which days>" — show first + count if many.
  const fromLabel = missedDays.length === 0
    ? ''
    : missedDays.length === 1
      ? missedDays[0]
      : missedDays.length === 2
        ? `${missedDays[0]} + ${missedDays[1]}`
        : `${missedDays.length} days (${missedDays.join(', ')})`;

  return {
    yesterday: {
      // Field name kept for callers; value now reflects cumulative
      // through yesterday rather than just yesterday alone.
      day: fromLabel || ((days[todayIdx - 1] && days[todayIdx - 1].day) || ''),
      committedHrs: Math.round(cumCommittedHrs * 10) / 10,
      targetRes: Math.round(cumTargetRes),
      actualRes: Math.round(cumActualRes),
      actualHrs: Math.round(cumQuidloHrs * 10) / 10,
      resDeficit: Math.round(resDeficit),
      hrDeficit: Math.round(hrDeficit * 10) / 10,
      missedDayCount: missedDays.length,
    },
    today: {
      committedHrs: Math.round(todayCommitted * 10) / 10,
      baseTargetRes: Math.round(baseTargetRes),
      effectiveTargetRes: Math.round(effectiveTargetRes),
      effectiveTargetHrs: Math.round(effectiveTargetHrs * 10) / 10,
      actualRes: Math.round(todayActualRes),
      actualHrs: Math.round(todayActualHrs * 10) / 10,
      resStillNeeded: Math.round(resStillNeeded),
      hrStillNeeded: Math.round(hrStillNeeded * 10) / 10,
    },
    madeUp,
  };
}

Object.assign(window, { computeMakeUp });
