/* Dump — calendar.jsx: Week strip view + month.

   Week view: 7 horizontal day strips. Each strip shows real events and
   committed tasks as time-labeled chips — no grid, no hour lines.
   Anchors are entirely invisible at this zoom level (they are context,
   not content). Capacity shown as 3 quiet dots at row end.

   Day detail lives in calendar-day.jsx.
   Scheduler panel lives in scheduler.jsx (reuses window helpers below).
*/

/* ---- date helpers (local, noon-anchored to dodge DST drift) ---- */
function calIso(d) {
  return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
}
function calParse(s) { return new Date(s + 'T12:00:00'); }
function calAddDays(d, n) { const x = new Date(d); x.setDate(x.getDate() + n); return x; }
function calStartOfWeek(d) { const x = new Date(d); const dow = (x.getDay() + 6) % 7; return calAddDays(x, -dow); }
function calStartOfMonth(d) { return new Date(d.getFullYear(), d.getMonth(), 1, 12); }
const CAL_DOW = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
const CAL_MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];

/* ---- time helpers (minutes since midnight) ---- */
function hmToMin(hm) { if (!hm) return null; const p = hm.split(':'); return (parseInt(p[0], 10) || 0) * 60 + (parseInt(p[1], 10) || 0); }
function minToHm(m) { const h = Math.floor(m / 60), mm = m % 60; return String(h).padStart(2, '0') + ':' + String(mm).padStart(2, '0'); }
const CAL_RANGES = { day: [480, 1140], extended: [360, 1380], work: [480, 1080] };
function calRange(dx) { return CAL_RANGES[(dx && dx.calRange)] || CAL_RANGES.day; }
const CAL_HOUR_PX = 44;
function axisHourLabel(min) { const h = Math.floor(min / 60); return String(h).padStart(2, '0'); }

/* ---- anchors for a given JS Date ---- */
function anchorsForDay(anchors, date) {
  const dow = date.getDay();
  return (anchors || [])
    .filter((a) => Array.isArray(a.days) && a.days.includes(dow))
    .map((a) => ({ kind: 'anchor', name: a.name, start: hmToMin(a.time), duration: a.duration || 30 }))
    .filter((a) => a.start != null)
    .sort((a, b) => a.start - b.start);
}

/* ---- capacity: committed minutes vs available window ---- */
function clampSpan(start, dur, winS, winE) {
  if (start == null) return 0;
  const s = Math.max(start, winS), e = Math.min(start + (dur || 0), winE);
  return Math.max(0, e - s);
}
function dayCapacity(date, dayItems, anchors, range) {
  const [winS, winE] = range;
  const win = winE - winS;
  let committed = 0;
  anchorsForDay(anchors, date).forEach((a) => { committed += clampSpan(a.start, a.duration, winS, winE); });
  (dayItems || []).forEach((it) => { if (it.start != null) committed += clampSpan(it.start, it.duration, winS, winE); });
  const frac = win > 0 ? committed / win : 0;
  return { frac, committed, free: Math.max(0, win - committed), level: frac >= 0.85 ? 'red' : frac >= 0.55 ? 'amber' : 'green' };
}

/* ---- collect events + dated tasks keyed by ISO day ---- */
function useCalData() {
  const { tasks, google, anchors } = useDumpStore();
  let events = [];
  if (google.connected) {
    if (google.demo && window.DumpDemo && DumpDemo.rangeEvents) {
      events = DumpDemo.rangeEvents().map((e) => ({ kind: 'event', date: e.date, time: e.time, start: hmToMin(e.time), duration: 60, title: e.title }));
    } else {
      const today = DumpUtil.todayISO();
      events = (google.events || []).map((e) => ({ kind: 'event', date: today, time: e.time, start: hmToMin(e.time), duration: 60, title: e.title }));
    }
  }
  const taskItems = tasks
    .filter((t) => !t.done && !t.trackDaily && t.dueDate && (!t.status || t.status === 'active'))
    .map((t) => ({ kind: 'task', date: t.dueDate, time: t.time || null, start: t.time ? hmToMin(t.time) : null, duration: t.minutes || 30, title: t.text, category: t.category, task: t }));
  const byDate = {};
  events.concat(taskItems).forEach((it) => { (byDate[it.date] = byDate[it.date] || []).push(it); });
  Object.keys(byDate).forEach((d) => {
    byDate[d].sort((a, b) => {
      if ((a.start == null) !== (b.start == null)) return a.start == null ? 1 : -1;
      if (a.start != null && b.start != null) return a.start - b.start;
      return 0;
    });
  });
  return { byDate, anchors };
}

/* ---- single heavy-day flag (replaces capacity dots) ---- */
function HeavyFlag({ frac }) {
  if (frac < 0.75) return null;
  const over = frac > 1.0;
  return (
    <span
      className={'heavy-flag' + (over ? ' is-over' : '')}
      title={over ? 'Over-committed today' : 'Busy day'}
    ></span>
  );
}

/* CapacityDots kept as shim for any stale refs */
function CapacityDots({ frac }) { return <HeavyFlag frac={frac} />; }
function CapacityBar({ frac }) { return <HeavyFlag frac={frac} />; }

/* ---- GridBlock — renders anchor + event blocks in scheduler's time grid.
   Called as window.GridBlock({ item, top, height }) from scheduler.jsx.
   item.kind: 'anchor' | 'event' | 'task' */
function GridBlock({ item, top, height, onClick }) {
  const kind = item.kind;
  const cls = 'tgrid-block' +
    (kind === 'anchor' ? ' is-anchor' : kind === 'event' ? ' is-event' : ' is-task');
  const label = item.name || item.title || item.text || '';
  return (
    <div
      className={cls}
      style={{ position: 'absolute', top: top, height: Math.max(height, 16), left: 4, right: 4 }}
      onClick={onClick}>
      <span className="tgb-title">{label}</span>
      {kind !== 'anchor' && item.time ? <span className="tgb-time">{item.time}</span> : null}
    </div>
  );
}
function FloatChip() { return null; }
function calLayoutColumns() { return new Map(); }

/* ---- duration label helper ---- */
function durationLabel(mins) {
  if (!mins || mins < 5) return null;
  if (mins < 60) return mins + 'm';
  const h = Math.floor(mins / 60), m = mins % 60;
  return m ? h + 'h ' + m + 'm' : h + 'h';
}

/* ---- week view: one strip per day ---- */
function CalWeek({ anchor, byDate, anchors, onSelect, onOpenDay }) {
  const cwState = useDumpStore();
  const settings = cwState.settings;
  const dayPlan = cwState.dayPlan || {};
  const dx = settings.dx || {};
  const range = calRange(dx);
  const start = calStartOfWeek(anchor);
  const todayIso = DumpUtil.todayISO();
  const days = Array.from({ length: 7 }, (_, i) => calAddDays(start, i));

  const catOf = (it) => it.kind === 'task' ? DumpUtil.catColor(it.category, settings.categories) : null;

  return (
    <div className="calw-strips">
      {days.map((d) => {
        const iso = calIso(d);
        const allItems = byDate[iso] || [];
        const cap = dayCapacity(d, allItems, anchors, range);
        const isToday = iso === todayIso;
        const dow = CAL_DOW[(d.getDay() + 6) % 7];

        /* timed items only — floating tasks live in the day detail */
        const timed = allItems.filter((it) => it.start != null)
          .sort((a, b) => a.start - b.start);

        /* soft day-plan hint — a plan exists, but it isn't an appointment */
        const planCount = ((dayPlan[iso] || {}).entries || []).filter((e) => {
          const t = cwState.tasks.find((x) => x.id === e.taskId);
          return t && !t.done && t.status !== 'binned';
        }).length;

        return (
          <div
            key={iso}
            className={'day-strip' + (isToday ? ' is-today' : '')}
            role="button"
            tabIndex={0}
            onClick={() => onOpenDay(iso)}
            onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') onOpenDay(iso); }}
          >
            {/* Day label */}
            <div className="ds-label">
              <span className="ds-dow">{dow}</span>
              <span className={'ds-date' + (isToday ? ' is-today' : '')}>{d.getDate()}</span>
            </div>

            {/* Event / task chips */}
            <div className="ds-chips">
              {timed.map((it, i) => {
                const cat = catOf(it);
                const isEvent = it.kind === 'event';
                const chipStyle = isEvent
                  ? { background: 'var(--cal-event)', color: '#fff' }
                  : { background: cat ? cat.bg : 'var(--soft-07)', color: cat ? cat.text : 'var(--ink)' };
                const dur = durationLabel(it.duration);
                return (
                  <button
                    key={i}
                    className={'ds-chip' + (isEvent ? ' is-event' : ' is-task')}
                    style={chipStyle}
                    onClick={(e) => { e.stopPropagation(); onSelect(it); }}
                    title={it.title + (dur ? ' · ' + dur : '')}
                  >
                    <span className="ds-chip-time">{minToHm(it.start)}</span>
                    <span className="ds-chip-text">{it.title}</span>
                    {dur ? <span className="ds-chip-dur">{dur}</span> : null}
                  </button>
                );
              })}
              {planCount ? (
                <span className="ds-plan-hint" title={planCount + ' planned task' + (planCount === 1 ? '' : 's') + ' — suggestions, in the day view'}>
                  {Icons.sparkle(11)} {planCount} planned
                </span>
              ) : null}
            </div>

            {/* Capacity dots */}
            <CapacityDots frac={cap.frac} />
          </div>
        );
      })}
    </div>
  );
}

/* ---- 4-week upcoming view (replaces month grid) ---- */
function CalUpcoming({ anchor, byDate, anchors, onSelect, onOpenDay }) {
  const { settings } = useDumpStore();  
  const dx = settings.dx || {};
  const range = calRange(dx);
  const todayIso = DumpUtil.todayISO();
  const catOf = (it) => it.kind === 'task' ? DumpUtil.catColor(it.category, settings.categories) : null;

  /* 28 days starting from Monday of anchor's week */
  const start = calStartOfWeek(anchor);
  const days = Array.from({ length: 28 }, (_, i) => calAddDays(start, i));

  /* group into 4 weeks */
  const weeks = [0, 1, 2, 3].map((w) => days.slice(w * 7, w * 7 + 7));

  return (
    <div className="calup-root">
      {weeks.map((week, wi) => {
        const weekStart = week[0];
        const sm = CAL_MONTHS[weekStart.getMonth()].slice(0, 3);
        const weekEnd = week[6];
        const em = CAL_MONTHS[weekEnd.getMonth()].slice(0, 3);
        const weekLabel = weekStart.getMonth() === weekEnd.getMonth()
          ? sm + ' ' + weekStart.getDate() + '–' + weekEnd.getDate()
          : sm + ' ' + weekStart.getDate() + ' – ' + em + ' ' + weekEnd.getDate();
        const isCurrentWeek = week.some((d) => calIso(d) === todayIso);

        return (
          <div key={wi} className={'calup-week' + (isCurrentWeek ? ' is-current' : '')}>
            <div className="calup-week-label">
              {isCurrentWeek ? 'This week' : weekLabel}
            </div>
            <div className="calup-strips">
              {week.map((d) => {
                const iso = calIso(d);
                const allItems = byDate[iso] || [];
                /* hard landscape only: timed items (events + committed tasks) */
                const timed = allItems
                  .filter((it) => it.start != null)
                  .sort((a, b) => a.start - b.start);
                const cap = dayCapacity(d, allItems, anchors, range);
                const isToday = iso === todayIso;
                const dow = CAL_DOW[(d.getDay() + 6) % 7];

                return (
                  <div
                    key={iso}
                    className={'day-strip calup-strip' + (isToday ? ' is-today' : '') + (timed.length === 0 ? ' is-empty' : '')}
                    role="button"
                    tabIndex={0}
                    onClick={() => onOpenDay(iso)}
                    onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') onOpenDay(iso); }}
                  >
                    <div className="ds-label">
                      <span className="ds-dow">{dow}</span>
                      <span className={'ds-date' + (isToday ? ' is-today' : '')}>{d.getDate()}</span>
                    </div>
                    <div className="ds-chips">
                      {timed.map((it, i) => {
                        const cat = catOf(it);
                        const isEvent = it.kind === 'event';
                        const chipStyle = isEvent
                          ? { background: 'var(--cal-event)', color: '#fff' }
                          : { background: cat ? cat.bg : 'var(--soft-07)', color: cat ? cat.text : 'var(--ink)' };
                        const dur = durationLabel(it.duration);
                        return (
                          <button
                            key={i}
                            className={'ds-chip' + (isEvent ? ' is-event' : ' is-task')}
                            style={chipStyle}
                            onClick={(e) => { e.stopPropagation(); onSelect(it); }}
                            title={it.title}
                          >
                            <span className="ds-chip-time">{minToHm(it.start)}</span>
                            <span className="ds-chip-text">{it.title}</span>
                            {dur ? <span className="ds-chip-dur">{dur}</span> : null}
                          </button>
                        );
                      })}
                    </div>
                    {timed.length > 0 ? <CapacityDots frac={cap.frac} /> : null}
                  </div>
                );
              })}
            </div>
          </div>
        );
      })}
    </div>
  );
}

/* ---- detail popover (tap an event or task chip) ---- */
function CalDetail({ item, onClose }) {
  const { settings } = useDumpStore();
  if (!item) return null;
  const isTask = item.kind === 'task';
  const cat = isTask ? DumpUtil.catColor(item.category, settings.categories) : null;
  const dateLabel = (() => {
    const d = calParse(item.date);
    return CAL_DOW[(d.getDay() + 6) % 7] + ' ' + d.getDate() + ' ' + CAL_MONTHS[d.getMonth()].slice(0, 3);
  })();
  const markDone = () => {
    DumpStore.set((s) => {
      const t = s.tasks.find((x) => x.id === item.task.id);
      if (!t) return {};
      return {
        tasks: s.tasks.filter((x) => x.id !== t.id),
        archive: [Object.assign({}, t, { done: true, doneAt: new Date().toISOString(), status: 'active' })].concat(s.archive)
      };
    });
    DumpAPI.toast('Done — cleared from the calendar.');
    onClose();
  };
  return (
    <React.Fragment>
      <div className="cal-detail-scrim" onClick={onClose}></div>
      <div className="cal-detail" role="dialog">
        <div className="cal-detail-head">
          {isTask
            ? <span className="cal-detail-tag is-task" style={{ color: cat.text, background: cat.bg }}>{item.category}</span>
            : <span className="cal-detail-tag is-event">{Icons.calendar(13)} Calendar event</span>}
          <button className="icon-btn" onClick={onClose} aria-label="Close">{Icons.x(16)}</button>
        </div>
        <p className="cal-detail-title">{item.title}</p>
        <p className="cal-detail-meta">{dateLabel}{item.time ? ' · ' + item.time : isTask ? ' · any time that day' : ''}</p>
        {isTask ? (
          <div className="cal-detail-actions">
            <button className="btn btn-sm btn-primary" onClick={markDone}>Mark done</button>
            {item.time ? (
              <button className="btn btn-sm btn-ghost" onClick={() => {
                DumpStore.set((s) => ({ tasks: s.tasks.map((t) => (t.id === item.task.id ? { ...t, time: null, calendarType: 'day-action' } : t)) }));
                DumpAPI.toast('Untimed — any time that day.');
                onClose();
              }}>Any time</button>
            ) : null}
            <button className="btn btn-sm btn-ghost" onClick={() => {
              const prev = { dueDate: item.task.dueDate, time: item.task.time, calendarType: item.task.calendarType };
              DumpStore.set((s) => ({ tasks: s.tasks.map((t) => (t.id === item.task.id ? { ...t, dueDate: null, time: null, calendarType: null } : t)) }));
              DumpAPI.toast('Off the calendar — still on your board.', {
                action: { label: 'Undo', fn: () => DumpStore.set((s) => ({ tasks: s.tasks.map((t) => (t.id === item.task.id ? { ...t, ...prev } : t)) })) }
              });
              onClose();
            }}>Take off this day</button>
            <button className="btn btn-sm btn-ghost" onClick={() => { DumpStore.set({ route: 'tasks', tasksView: 'board' }); onClose(); }}>Open in Tasks</button>
          </div>
        ) : (
          <p className="cal-detail-note">From your connected calendar — read-only here.</p>
        )}
      </div>
    </React.Fragment>
  );
}

/* ---- section shell ---- */
function CalendarSection() {
  const { settings, google, anchors, seenHints } = useDumpStore();
  const view = settings.calendarView === 'month' ? 'month' : 'week';
  const [anchor, setAnchor] = useState(() => { const d = new Date(); d.setHours(12, 0, 0, 0); return d; });
  const [selected, setSelected] = useState(null);
  const [dayView, setDayView] = useState(null);
  const { byDate } = useCalData();

  const setView = (v) => DumpStore.set((s) => ({ settings: { ...s.settings, calendarView: v } }));
  const step = (dir) => setAnchor((a) => view === 'week' ? calAddDays(a, dir * 7) : new Date(a.getFullYear(), a.getMonth() + dir, 1, 12));
  const goToday = () => { const d = new Date(); d.setHours(12, 0, 0, 0); setAnchor(d); };
  const todayInView = (() => {
    const now = new Date();
    if (view === 'month') return now.getFullYear() === anchor.getFullYear() && now.getMonth() === anchor.getMonth();
    return calIso(calStartOfWeek(now)) === calIso(calStartOfWeek(anchor));
  })();

  if (dayView) {
    return <CalendarDay iso={dayView} byDate={byDate} anchors={anchors} onBack={() => setDayView(null)} onSelect={setSelected} selected={selected} closeSelect={() => setSelected(null)} />;
  }

  const heading = (() => {
    if (view === 'month') return CAL_MONTHS[anchor.getMonth()] + ' ' + anchor.getFullYear();
    const s = calStartOfWeek(anchor), e = calAddDays(s, 6);
    const sm = CAL_MONTHS[s.getMonth()].slice(0, 3), em = CAL_MONTHS[e.getMonth()].slice(0, 3);
    return s.getMonth() === e.getMonth()
      ? sm + ' ' + s.getDate() + '–' + e.getDate()
      : sm + ' ' + s.getDate() + ' – ' + em + ' ' + e.getDate();
  })();

  const showAnchorPrompt = !(seenHints || []).includes('anchors-intro');

  return (
    <div className="section cal-section" data-screen-label="Calendar">
      <div className="section-head">
        <h1 className="section-title">Calendar</h1>
        <div className="cal-head-actions">
          <button className="cal-plan-btn" onClick={() => DumpAPI.openScheduler(DumpUtil.todayISO())} title="Suggest times for today's untimed tasks">{Icons.sparkle(13)} Plan my day</button>
          <button className="linklike cal-anchorlink" onClick={() => DumpStore.set({ route: 'anchors' })}>Edit anchors</button>
        </div>
      </div>

      {showAnchorPrompt ? (
        <div className="context-hint">
          {Icons.calendar(16)}
          <span className="context-hint-body">
            Set your <strong>daily anchors</strong> — the school run, work, bedtime — so the calendar can show how much time you actually have.{' '}
            <button className="linklike" onClick={() => DumpStore.set({ route: 'anchors' })}>Set them up →</button>
          </span>
          <button className="context-hint-dismiss" onClick={() => DumpAPI.dismissHint('anchors-intro')} aria-label="Dismiss">×</button>
        </div>
      ) : null}

      <div className="cal-controls">
        <div className="view-tabs cal-view-tabs">
          <button className={'vt-tab' + (view === 'week' ? ' active' : '')} onClick={() => setView('week')}>Week</button>
          <button className={'vt-tab' + (view === 'month' ? ' active' : '')} onClick={() => setView('month')}>Upcoming</button>
        </div>
        <div className="cal-nav">
          <button className="cal-nav-btn" onClick={() => step(-1)} aria-label="Previous">{Icons.chevL(16)}</button>
          <span className="cal-heading">{heading}</span>
          <button className="cal-nav-btn" onClick={() => step(1)} aria-label="Next">{Icons.chevR(16)}</button>
          <button className="btn btn-sm btn-ghost cal-today" onClick={goToday} disabled={todayInView}
            title={todayInView ? 'Already on ' + (view === 'month' ? 'this month' : 'this week') : 'Jump back to today'}>Today</button>
        </div>
      </div>

      {view === 'week'
        ? <CalWeek anchor={anchor} byDate={byDate} anchors={anchors} onSelect={setSelected} onOpenDay={setDayView} />
        : <CalUpcoming anchor={anchor} byDate={byDate} anchors={anchors} onSelect={setSelected} onOpenDay={setDayView} />}

      <CalDetail item={selected} onClose={() => setSelected(null)} />
    </div>
  );
}

window.CalendarSection = CalendarSection;
Object.assign(window, {
  calIso, calParse, calAddDays, calStartOfWeek, CAL_DOW, CAL_MONTHS,
  hmToMin, minToHm, calRange, CAL_HOUR_PX, axisHourLabel,
  anchorsForDay, dayCapacity, clampSpan,
  CapacityBar, CapacityDots, CalDetail, GridBlock, FloatChip, calLayoutColumns
});
