// Weekly goals — count habits + time tracking, with a live Valorant card.

const { useState, useEffect, useRef, useMemo, useCallback } = React;

function goalUid() {
  return `g-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
}
function weekDaysOf(weekStart) {
  return Array.from({ length: 7 }, (_, i) => addDays(weekStart, i));
}
function logCount(logs, iso) {
  return Math.max(0, Number((logs || {})[iso] || 0));
}
function weekSum(logs, days) {
  return days.reduce((n, d) => n + logCount(logs, toISODate(d)), 0);
}
function fmtMins(m) {
  m = Math.max(0, Math.round(Number(m) || 0));
  const h = Math.floor(m / 60), r = m % 60;
  if (h && r) return `${h}h ${r}m`;
  if (h) return `${h}h`;
  return `${r}m`;
}
function selectedDayLabel(iso, todayIso) {
  if (iso === todayIso) return t('goals.doneToday');
  const d = fromISODate(iso);
  if (!d) return iso;
  return `${t('weekdaysLong')[d.getDay()]} ${d.getDate()}`;
}
const PERSONAL_USERNAME = 'jaumesaa';
function isPersonalUsername(username) {
  return String(username || '').toLowerCase() === PERSONAL_USERNAME;
}
function isPersonalGoal(g) {
  return g.source === 'valorant' || g.source === 'gym' || g.id === 'g-valorant' || g.id === 'g-gym';
}
function exampleGoals(isPersonal) {
  if (!isPersonal) return [];
  const now = new Date().toISOString();
  return [
    { id: 'g-valorant', title: 'Valorant', kind: 'time', target: 0, cat: 'social', source: 'valorant', icon: 'valorant', unit: '', logs: {}, createdAt: now, updatedAt: Date.now() },
    { id: 'g-gym', title: 'Gym', kind: 'count', target: 6, cat: 'salut', source: 'gym', icon: '', unit: '', logs: {}, createdAt: now, updatedAt: Date.now() },
  ];
}
function emptyGoalDraft() {
  return { id: null, isNew: true, title: '', unit: '', target: 5, cat: 'personal', kind: 'count' };
}

function ValorantMark({ size = 28 }) {
  return (
    <img className="valo-mark" src="assets/valorant.png" width={size} height={size} alt="" draggable="false"/>
  );
}

function GoalRing({ value, max, color, size = 96, stroke = 7, children, spin }) {
  const r = size / 2 - stroke - 1;
  const c = 2 * Math.PI * r;
  const pct = max > 0 ? Math.min(1, value / max) : (value > 0 ? 1 : 0);
  const off = c * (1 - pct);
  return (
    <div className={`goal-ring ${spin ? 'goal-ring-live' : ''}`} style={{ width: size, height: size, '--ring': color }}>
      <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
        <circle className="goal-ring-track" cx={size/2} cy={size/2} r={r} fill="none" strokeWidth={stroke}/>
        <circle className="goal-ring-arc" cx={size/2} cy={size/2} r={r} fill="none"
                stroke={color} strokeWidth={stroke} strokeLinecap="round"
                strokeDasharray={c.toFixed(2)} strokeDashoffset={off.toFixed(2)}
                transform={`rotate(-90 ${size/2} ${size/2})`}/>
      </svg>
      <div className="goal-ring-inner">{children}</div>
    </div>
  );
}

function TimeWheel({ minutes, onChange, color, max = 180 }) {
  const ref = useRef(null);
  const [shown, setShown] = useState(minutes);
  const dragging = useRef(false);

  useEffect(() => {
    let raf;
    const step = () => {
      setShown(s => {
        const d = minutes - s;
        if (Math.abs(d) < 0.15) return minutes;
        return s + d * 0.28;
      });
      raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [minutes]);

  const apply = (clientX, clientY) => {
    const el = ref.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    const x = clientX - (r.left + r.width / 2);
    const y = clientY - (r.top + r.height / 2);
    let ang = Math.atan2(x, -y);
    if (ang < 0) ang += Math.PI * 2;
    const snapped = Math.round(((ang / (Math.PI * 2)) * max) / 5) * 5;
    onChange(Math.max(0, Math.min(max, snapped)));
  };

  useEffect(() => {
    const move = (e) => {
      if (!dragging.current) return;
      const p = e.touches ? e.touches[0] : e;
      apply(p.clientX, p.clientY);
      e.preventDefault();
    };
    const up = () => { dragging.current = false; };
    window.addEventListener('pointermove', move, { passive: false });
    window.addEventListener('pointerup', up);
    window.addEventListener('touchmove', move, { passive: false });
    window.addEventListener('touchend', up);
    return () => {
      window.removeEventListener('pointermove', move);
      window.removeEventListener('pointerup', up);
      window.removeEventListener('touchmove', move);
      window.removeEventListener('touchend', up);
    };
  }, [max, onChange]);

  const pct = shown / max;
  const rot = pct * 360;
  const ticks = [];
  for (let i = 0; i <= max; i += 5) {
    const on = i <= minutes;
    const major = i % 15 === 0;
    ticks.push(
      <span key={i} className={`tw-tick ${on ? 'is-on' : ''} ${major ? 'is-major' : ''}`}
            style={{ transform: `rotate(${(i / max) * 360}deg)` }}/>
    );
  }

  return (
    <div className="timewheel">
      <div className="tw-face" ref={ref}
           onPointerDown={(e) => { dragging.current = true; apply(e.clientX, e.clientY); }}
           onTouchStart={(e) => { dragging.current = true; apply(e.touches[0].clientX, e.touches[0].clientY); }}>
        <div className="tw-glow" style={{ background: `conic-gradient(${color} ${pct * 360}deg, transparent 0)` }}/>
        {ticks}
        <div className="tw-knob" style={{ transform: `rotate(${rot}deg)` }}>
          <span className="tw-knob-dot" style={{ background: color }}/>
        </div>
        <div className="tw-core">
          <div className="tw-num">{fmtMins(Math.round(shown))}</div>
          <div className="tw-hint">{t('goals.wheelHint')}</div>
        </div>
      </div>
    </div>
  );
}

function GoalEditor({ open, draft, onChange, onSave, onCancel, onDelete }) {
  if (!open) return null;
  const isEditing = !draft.isNew;
  const isTime = draft.kind === 'time';
  return (
    <div className="modal-back" onClick={(e) => { if (e.target === e.currentTarget) onCancel(); }}>
      <div className="modal" role="dialog" aria-modal="true">
        <div className="modal-h">
          <div className="modal-h-title">{isEditing ? t('goals.editorEdit') : t('goals.editorNew')}</div>
          <button className="icon-btn sm" onClick={onCancel} aria-label={t('editor.close')}>
            <Icon.close width="14" height="14"/>
          </button>
        </div>
        <div className="modal-body">
          <div className="field">
            <span className="field-lbl">{t('goals.kindLabel')}</span>
            <div className="cat-chips">
              <button type="button" className={`chip ${!isTime ? 'chip-sel' : ''}`} onClick={() => onChange({ ...draft, kind: 'count', target: draft.target || 5 })}>{t('goals.kindCount')}</button>
              <button type="button" className={`chip ${isTime ? 'chip-sel' : ''}`} onClick={() => onChange({ ...draft, kind: 'time', target: draft.target || 0 })}>{t('goals.kindTime')}</button>
            </div>
          </div>
          <label className="field">
            <span className="field-lbl">{t('goals.titleLabel')}</span>
            <input type="text" value={draft.title} autoFocus
              placeholder={isTime ? t('goals.titlePlaceholderTime') : t('goals.titlePlaceholder')}
              onChange={(e) => onChange({ ...draft, title: e.target.value })}/>
          </label>
          <label className="field">
            <span className="field-lbl">{isTime ? t('goals.targetMinutes') : t('goals.targetLabel')} <span className="field-opt">{t('goals.targetHint')}</span></span>
            <input type="number" min="0" max={isTime ? 10080 : 99} value={draft.target ?? 0}
              onChange={(e) => onChange({ ...draft, target: Math.max(0, Number(e.target.value) || 0) })}/>
          </label>
          <div className="field">
            <span className="field-lbl">{t('editor.categoryLabel')}</span>
            <div className="cat-chips">
              {CAT_KEYS.map(k => {
                const c = CATEGORIES[k];
                return (
                  <button key={k} type="button" className={`chip ${draft.cat === k ? 'chip-sel' : ''}`}
                          style={{ '--dot': c.dot }} onClick={() => onChange({ ...draft, cat: k })}>
                    <span className="dot"/>{catLabel(k)}
                  </button>
                );
              })}
            </div>
          </div>
        </div>
        <div className="modal-f">
          {isEditing ? <button className="btn-danger" onClick={() => onDelete(draft.id)}>{t('editor.delete')}</button> : <span/>}
          <div className="modal-f-right">
            <button className="btn-ghost" onClick={onCancel}>{t('editor.cancel')}</button>
            <button className="btn-primary" disabled={!draft.title.trim()} onClick={onSave}>
              {isEditing ? t('editor.save') : t('editor.add')}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

function GoalCard({ goal, days, selectedIso, onSelectDay, onBump, onSetTime, onEdit, index, live, addMins, setAddMins }) {
  const c = CATEGORIES[goal.cat] || CATEGORIES.personal;
  const isTime = goal.kind === 'time';
  const isValo = goal.source === 'valorant' || goal.icon === 'valorant';
  const isGym = goal.source === 'gym';
  const locked = !!goal.source;
  const done = weekSum(goal.logs, days);
  const target = goal.target || 0;
  const met = target > 0 && done >= target;
  const todayIso = toISODate(TODAY);
  const sel = logCount(goal.logs, selectedIso);
  const maxBar = Math.max(isTime ? 30 : 1, ...days.map(d => logCount(goal.logs, toISODate(d))));

  return (
    <article className={`goal-card ${met ? 'goal-card-met' : ''} ${isValo ? 'goal-card-valo' : ''} ${isTime ? 'goal-card-time' : ''}`}
             style={{ '--cat': isValo ? '#ff4655' : c.dot, '--i': index }}>
      <span className="goal-card-stripe" aria-hidden="true"/>
      <div className="goal-card-head">
        <GoalRing value={done} max={target || Math.max(done, isTime ? 60 : 1)} color={isValo ? '#FF4655' : c.dot} size={92} stroke={6.5} spin={isValo && live}>
          {isValo ? <ValorantMark size={26}/> : (
            <>
              <div className="goal-ring-num">{isTime ? fmtMins(done) : done}</div>
              {target ? <div className="goal-ring-den">{isTime ? fmtMins(target) : `/${target}`}</div> : <div className="goal-ring-den">{t('goals.trackOnly')}</div>}
            </>
          )}
        </GoalRing>
        <div className="goal-card-meta">
          <button type="button" className="goal-card-title" onClick={() => onEdit(goal)}>
            {goal.title}
            {isValo && live ? <span className="valo-live-pill">{t('goals.live')}</span> : null}
          </button>
          <div className="goal-card-sub">
            {isValo || isGym ? <span className="goal-unit">{t(isGym ? 'goals.autoSyncGym' : 'goals.autoSync')}</span> : null}
            {target > 0
              ? <span>{isTime ? t('goals.perWeekTime', { n: fmtMins(target) }) : t('goals.perWeek', { n: target })}</span>
              : <span>{t('goals.noTarget')}</span>}
            {met ? <span className="goal-met-pill">{t('goals.met')}</span> : null}
          </div>
          {isValo ? (
            <div className="valo-big">{fmtMins(done)}</div>
          ) : target > 0 && !isTime ? (
            <div className="goal-slots" aria-hidden="true">
              {Array.from({ length: target }, (_, i) => (
                <span key={i} className={`goal-slot ${i < done ? 'goal-slot-on' : ''}`}/>
              ))}
            </div>
          ) : null}
        </div>
        {!locked && (
          <button className="icon-btn sm goal-edit-btn" onClick={() => onEdit(goal)} aria-label={t('goals.editorEdit')}>
            <Icon.edit width="14" height="14"/>
          </button>
        )}
      </div>

      <div className="goal-days" role="group">
        {days.map((d) => {
          const iso = toISODate(d);
          const n = logCount(goal.logs, iso);
          const h = Math.min(100, (n / maxBar) * 100);
          return (
            <button key={iso} type="button"
              className={`goal-day ${iso === selectedIso ? 'goal-day-on' : ''} ${iso === todayIso ? 'goal-day-today' : ''} ${n ? 'goal-day-has' : ''}`}
              onClick={() => onSelectDay(iso)}>
              <span className="goal-day-lbl">{t('weekdaysShort')[daysBetween(days[0], d)]}</span>
              <span className="goal-day-stack">
                {isTime ? (
                  <span className="goal-day-filltime" style={{ height: n ? `${Math.max(10, h)}%` : '0%' }}/>
                ) : (
                  <span className="goal-pips">
                    {Array.from({ length: Math.min(n, 8) }, (_, i) => (
                      <span key={i} className="goal-pip" style={{ animationDelay: `${i * 28}ms` }}/>
                    ))}
                    {n > 8 ? <span className="goal-pip-more">+{n - 8}</span> : null}
                  </span>
                )}
              </span>
              <span className={`goal-day-n ${n ? '' : 'is-empty'}`}>{n ? (isTime ? fmtMins(n) : n) : '·'}</span>
            </button>
          );
        })}
      </div>

      {locked ? (
        <div className="goal-step-row">
          <span className="goal-step-lbl">{t(isGym ? 'goals.autoHintGym' : 'goals.autoHint')}</span>
          <span className={`valo-dot ${live ? 'is-on' : ''}`}/>
        </div>
      ) : isTime ? (
        <div className="goal-time-ctrl">
          <TimeWheel minutes={addMins} onChange={setAddMins} color={c.dot}/>
          <div className="goal-time-actions">
            <span className="goal-step-lbl">{selectedDayLabel(selectedIso, todayIso)} · {fmtMins(sel)}</span>
            <div className="goal-time-btns">
              <button type="button" className="btn-ghost" disabled={sel <= 0} onClick={() => onSetTime(goal, selectedIso, Math.max(0, sel - addMins))}>{t('goals.subtract')}</button>
              <button type="button" className="btn-primary" onClick={() => onSetTime(goal, selectedIso, sel + addMins)}>
                + {fmtMins(addMins)}
              </button>
            </div>
          </div>
        </div>
      ) : (
        <div className="goal-step-row">
          <span className="goal-step-lbl">{selectedDayLabel(selectedIso, todayIso)}</span>
          <div className="goal-step">
            <button type="button" className="goal-step-btn" disabled={sel <= 0} onClick={() => onBump(goal, selectedIso, -1)}>−</button>
            <span className="goal-step-val">{sel}</span>
            <button type="button" className="goal-step-btn goal-step-plus" onClick={() => onBump(goal, selectedIso, 1)}>+</button>
          </div>
        </div>
      )}
    </article>
  );
}

function GoalsView({ username }) {
  const storeRef = useRef(null);
  if (!storeRef.current) {
    storeRef.current = createStore({
      apiPath: 'goals',
      storageKey: 'goals.items.v1',
      metaKey: 'goals.meta.v1',
      demoFactory: () => [],
    });
  }
  const store = storeRef.current;

  const [items, setItems] = useState([]);
  const [weekStart, setWeekStart] = useState(() => startOfWeek(TODAY));
  const [selectedIso, setSelectedIso] = useState(() => toISODate(TODAY));
  const [editorOpen, setEditorOpen] = useState(false);
  const [draft, setDraft] = useState(emptyGoalDraft());
  const [playDays, setPlayDays] = useState({});
  const [playLive, setPlayLive] = useState(false);
  const [gymDays, setGymDays] = useState({});
  const isPersonal = isPersonalUsername(username);
  const [addMins, setAddMins] = useState(30);
  const [monthAnchor, setMonthAnchor] = useState(() => startOfMonth(TODAY));
  const seeded = useRef(false);
  const [ready, setReady] = useState(false);

  useEffect(() => {
    let cancelled = false;
    store.load().then(({ items: next }) => {
      if (cancelled) return;
      setItems(next || []);
      setReady(true);
    });
    const unsub = store.subscribe(({ items: next }) => {
      if (Array.isArray(next)) setItems(next);
    });
    return () => { cancelled = true; unsub(); };
  }, [store]);

  const commit = useCallback((next) => {
    setItems(next);
    store.save(next).then(({ items: saved }) => {
      if (Array.isArray(saved)) setItems(saved);
    });
  }, [store]);

  useEffect(() => {
    if (!ready || seeded.current) return;
    if (!username) return;
    seeded.current = true;
    if (!isPersonal) return;
    const examples = exampleGoals(true);
    const have = new Set(items.map(g => g.id));
    const missing = examples.filter(g => !have.has(g.id));
    const migrated = items.map(g => {
      if (g.id === 'g-gym' && g.source !== 'gym') return { ...g, source: 'gym', kind: 'count', updatedAt: Date.now() };
      if (g.id === 'g-valorant' && g.source !== 'valorant') return { ...g, source: 'valorant', kind: 'time', icon: 'valorant', updatedAt: Date.now() };
      return g;
    });
    const changed = missing.length || migrated.some((g, i) => g !== items[i]);
    if (changed) commit([...migrated, ...missing]);
  }, [ready, items, commit, isPersonal, username]);

  useEffect(() => {
    if (!isPersonal) {
      setPlayDays({});
      setPlayLive(false);
      setGymDays({});
      return;
    }
    let stop = false;
    const load = () => {
      fetch('/api/playtime/week', { credentials: 'same-origin' })
        .then(r => r.ok ? r.json() : null)
        .then(d => {
          if (stop || !d) return;
          setPlayDays(d.days || {});
          setPlayLive(!!d.live);
        })
        .catch(() => {});
      fetch('/api/gym/week', { credentials: 'same-origin' })
        .then(r => r.ok ? r.json() : null)
        .then(d => {
          if (stop || !d) return;
          setGymDays(d.days || {});
        })
        .catch(() => {});
    };
    load();
    const id = setInterval(load, 15000);
    return () => { stop = true; clearInterval(id); };
  }, [isPersonal]);

  const days = useMemo(() => weekDaysOf(weekStart), [weekStart]);
  const isCurrentWeek = toISODate(weekStart) === toISODate(startOfWeek(TODAY));
  const todayIso = toISODate(TODAY);

  useEffect(() => {
    if (!days.some(d => toISODate(d) === selectedIso)) {
      setSelectedIso(isCurrentWeek ? todayIso : toISODate(days[0]));
    }
  }, [days, selectedIso, isCurrentWeek, todayIso]);

  const visible = useMemo(() => {
    const list = isPersonal ? items : items.filter(g => !isPersonalGoal(g));
    return list.map(g => {
      if (g.source === 'valorant') {
        const logs = {};
        for (const d of days) {
          const iso = toISODate(d);
          if (playDays[iso]) logs[iso] = playDays[iso];
        }
        return { ...g, logs, kind: 'time', icon: 'valorant' };
      }
      if (g.source === 'gym') {
        const logs = {};
        for (const d of days) {
          const iso = toISODate(d);
          if (gymDays[iso]) logs[iso] = 1;
        }
        return { ...g, logs, kind: 'count' };
      }
      return g;
    });
  }, [items, days, playDays, gymDays, isPersonal]);

  const bump = useCallback((goal, iso, delta) => {
    const cur = logCount(goal.logs, iso);
    const nextN = Math.max(0, Math.min(99, cur + delta));
    const logs = { ...(goal.logs || {}) };
    if (nextN === 0) delete logs[iso]; else logs[iso] = nextN;
    commit(items.map(g => g.id === goal.id ? { ...g, logs, updatedAt: Date.now() } : g));
  }, [items, commit]);

  const setTime = useCallback((goal, iso, minutes) => {
    const nextN = Math.max(0, Math.min(1440, Math.round(minutes)));
    const logs = { ...(goal.logs || {}) };
    if (nextN === 0) delete logs[iso]; else logs[iso] = nextN;
    commit(items.map(g => g.id === goal.id ? { ...g, logs, updatedAt: Date.now() } : g));
  }, [items, commit]);

  const saveDraft = useCallback(() => {
    if (!draft.title.trim()) return;
    const clean = {
      id: draft.id || goalUid(),
      title: draft.title.trim(),
      unit: (draft.unit || '').trim(),
      kind: draft.kind === 'time' ? 'time' : 'count',
      target: Math.max(0, Number(draft.target) || 0),
      cat: draft.cat || 'personal',
      source: draft.source || '',
      icon: draft.icon || '',
      logs: draft.logs || {},
      createdAt: draft.createdAt || new Date().toISOString(),
      updatedAt: Date.now(),
    };
    const idx = items.findIndex(x => x.id === clean.id);
    commit(idx === -1 ? [...items, clean] : items.map(x => x.id === clean.id ? { ...x, ...clean, logs: x.logs, source: x.source } : x));
    setEditorOpen(false);
  }, [draft, items, commit]);

  const deleteGoal = useCallback((id) => {
    commit(items.filter(x => x.id !== id));
    setEditorOpen(false);
  }, [items, commit]);

  const activityOn = useCallback((iso) => {
    const list = isPersonal ? items : items.filter(g => !isPersonalGoal(g));
    return list.reduce((s, g) => {
      if (g.source === 'valorant') return s + (playDays[iso] || 0);
      if (g.source === 'gym') return s + (gymDays[iso] ? 1 : 0);
      return s + logCount(g.logs, iso);
    }, 0);
  }, [items, playDays, gymDays, isPersonal]);

  const timeDone = visible.filter(g => g.kind === 'time').reduce((n, g) => n + weekSum(g.logs, days), 0);
  const todayTime = visible.filter(g => g.kind === 'time').reduce((n, g) => n + logCount(g.logs, todayIso), 0);
  const weekMax = Math.max(1, ...days.map(d => activityOn(toISODate(d))));

  const pickDay = (d) => {
    const iso = toISODate(d);
    setSelectedIso(iso);
    setWeekStart(startOfWeek(d));
  };

  const monthCells = useMemo(() => {
    const start = startOfMonth(monthAnchor);
    const end = endOfMonth(monthAnchor);
    const gridStart = startOfWeek(start);
    const cells = [];
    for (let i = 0; i < 42; i++) {
      const d = addDays(gridStart, i);
      cells.push({ d, iso: toISODate(d), inMonth: d.getMonth() === start.getMonth() && d <= end });
    }
    return cells;
  }, [monthAnchor]);

  const monthMax = useMemo(() => Math.max(1, ...monthCells.map(c => activityOn(c.iso))), [monthCells, activityOn]);

  const chartPts = days.map((d, i) => {
    const v = activityOn(toISODate(d));
    const x = 22 + i * 38;
    const h = (v / weekMax) * 88;
    return { iso: toISODate(d), x, y: 100 - h, h, v, i };
  });
  const chartLine = chartPts.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x} ${p.y}`).join(' ');
  const chartArea = `M ${chartPts[0].x} 100 ${chartPts.map(p => `L ${p.x} ${p.y}`).join(' ')} L ${chartPts[6].x} 100 Z`;

  return (
    <main className="goals-wrap">
      <div className="goals-toolbar">
        <div className="goals-week-nav">
          <button className="icon-btn" onClick={() => setWeekStart(d => addDays(d, -7))} aria-label={t('goals.prevWeek')}><Icon.left width="16" height="16"/></button>
          <div className="goals-week-lbl">
            <div className="goals-week-title">{isCurrentWeek ? t('goals.thisWeek') : t('goals.weekOf', { start: fmtShort(days[0]) })}</div>
            <div className="goals-week-range">{fmtDayMonth(days[0])} — {fmtDayMonth(days[6])}</div>
          </div>
          <button className="icon-btn" onClick={() => setWeekStart(d => addDays(d, 7))} aria-label={t('goals.nextWeek')}><Icon.right width="16" height="16"/></button>
        </div>
        <button className="btn-primary" onClick={() => { setDraft(emptyGoalDraft()); setEditorOpen(true); }}>
          <Icon.plus width="16" height="16"/><span>{t('goals.add')}</span>
        </button>
      </div>

      {visible.length > 0 && (
        <section className="goals-today">
          <div className="goals-today-orb" aria-hidden="true"/>
          <div className="goals-today-k">{t('goals.todayGlance')}</div>
          <div className="goals-today-time">{fmtMins(todayTime)}</div>
          <div className="goals-today-sub">{t('goals.weekTotal', { n: fmtMins(timeDone) })}{playLive ? <span className="valo-live-pill">{t('goals.live')}</span> : null}</div>
          <div className="goals-today-strip">
            {days.map((d, i) => {
              const iso = toISODate(d);
              const n = activityOn(iso);
              return (
                <button key={iso} type="button"
                  className={`goals-today-seg ${iso === todayIso ? 'is-today' : ''} ${iso === selectedIso ? 'is-on' : ''} ${n ? 'has' : ''}`}
                  style={{ '--p': n / weekMax, '--d': i }}
                  onClick={() => pickDay(d)}>
                  <span className="goals-today-seg-fill"/>
                  <span className="goals-today-seg-lbl">{t('weekdaysShort')[i]}</span>
                </button>
              );
            })}
          </div>
          <div className="goals-today-chips">
            {visible.map(g => {
              const n = logCount(g.logs, todayIso);
              const c = CATEGORIES[g.cat] || CATEGORIES.personal;
              return (
                <span key={g.id} className="goals-today-chip" style={{ '--cat': g.source === 'valorant' ? '#FF4655' : c.dot }}>
                  {g.source === 'valorant' ? <ValorantMark size={12}/> : null}
                  {g.title}
                  <em>{n ? (g.kind === 'time' ? fmtMins(n) : n) : '—'}</em>
                </span>
              );
            })}
          </div>
        </section>
      )}

      <div className="goals-grid">
        {visible.map((g, i) => (
          <GoalCard key={g.id} goal={g} days={days} selectedIso={selectedIso} onSelectDay={setSelectedIso}
            onBump={bump} onSetTime={setTime} onEdit={(goal) => { setDraft({ ...goal, isNew: false }); setEditorOpen(true); }}
            index={i} live={g.source === 'valorant' && playLive} addMins={addMins} setAddMins={setAddMins}/>
        ))}
      </div>

      {visible.length > 0 && (
        <div className="goals-below">
          <section className="gcal">
            <div className="gcal-h">
              <button className="icon-btn sm" onClick={() => setMonthAnchor(d => startOfMonth(addDays(startOfMonth(d), -1)))} aria-label={t('cal.prevMonth')}><Icon.left width="14" height="14"/></button>
              <div className="gcal-title">{fmtMonthYear(monthAnchor)}</div>
              <button className="icon-btn sm" onClick={() => setMonthAnchor(d => startOfMonth(addDays(endOfMonth(d), 1)))} aria-label={t('cal.nextMonth')}><Icon.right width="14" height="14"/></button>
            </div>
            <div className="gcal-week">
              {t('weekdaysShort').map((w) => <span key={w}>{w}</span>)}
            </div>
            <div className="gcal-grid">
              {monthCells.map(({ d, iso, inMonth }) => {
                const n = activityOn(iso);
                const heat = n ? 0.18 + 0.82 * (n / monthMax) : 0;
                return (
                  <button key={iso} type="button"
                    className={`gcal-day ${inMonth ? '' : 'is-out'} ${iso === todayIso ? 'is-today' : ''} ${iso === selectedIso ? 'is-on' : ''}`}
                    style={{ '--heat': heat }}
                    onClick={() => pickDay(d)}>
                    {d.getDate()}
                  </button>
                );
              })}
            </div>
          </section>
          <section className="gchart">
            <div className="gchart-title">{t('goals.chartTitle')}</div>
            <svg className="gchart-svg" viewBox="0 0 280 128" preserveAspectRatio="xMidYMid meet">
              <path className="gchart-area" d={chartArea}/>
              <path className="gchart-line" d={chartLine}/>
              {chartPts.map((p) => (
                <g key={p.iso} className="gchart-col" onClick={() => pickDay(days[p.i])}>
                  <rect className={`gchart-bar ${p.iso === todayIso ? 'is-today' : ''} ${p.iso === selectedIso ? 'is-on' : ''}`}
                    x={p.x - 8} y={p.y} width="16" height={Math.max(p.h, 2)} rx="5"/>
                  <text className="gchart-lbl" x={p.x} y="122">{t('weekdaysShort')[p.i]}</text>
                </g>
              ))}
            </svg>
          </section>
        </div>
      )}

      <GoalEditor open={editorOpen} draft={draft} onChange={setDraft} onSave={saveDraft}
        onCancel={() => setEditorOpen(false)} onDelete={deleteGoal}/>
    </main>
  );
}

window.GoalsView = GoalsView;
