// Agenda UI components.
// All components expect global helpers from agenda-data.jsx
// (TODAY, addDays, daysBetween, fromISODate, toISODate, CATEGORIES, etc.)
// and the i18n helper t() from src/i18n/.

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

/* ─── Icons ──────────────────────────────────────────────────────────── */
const Icon = {
  plus:      (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round"><path d="M12 5v14M5 12h14"/></svg>,
  close:     (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round"><path d="M6 6l12 12M18 6L6 18"/></svg>,
  check:     (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6L9 17l-5-5"/></svg>,
  moon:      (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinejoin="round"><path d="M20.5 14.5A8.5 8.5 0 0 1 9.5 3.5a8.5 8.5 0 1 0 11 11Z"/></svg>,
  sun:       (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg>,
  trash:     (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M4 7h16M10 7V4h4v3M6 7l1 13a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-13M10 11v7M14 11v7"/></svg>,
  edit:      (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M4 20h4l10-10-4-4L4 16v4z"/><path d="M14 6l4 4"/></svg>,
  left:      (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M15 18l-6-6 6-6"/></svg>,
  right:     (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M9 18l6-6-6-6"/></svg>,
  list:      (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"><path d="M4 6h16M4 12h16M4 18h10"/></svg>,
  grid:      (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><rect x="3.5" y="4.5" width="17" height="16" rx="2"/><path d="M3.5 9h17M8 4.5v16M16 4.5v16"/></svg>,
  pin:       (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M12 21s-6-5.686-6-10a6 6 0 0 1 12 0c0 4.314-6 10-6 10z"/><circle cx="12" cy="11" r="2"/></svg>,
  paperclip: (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66L9.41 17.41a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>,
  link2:     (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>,
  filePdf:   (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M9 15h.01M9 12h6M9 18h6"/></svg>,
  image:     (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>,
  video:     (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-4v12l-6-4"/></svg>,
  spinner:   (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" opacity=".4"/><path d="M12 2v4"/></svg>,
  bubbles:   (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6"><circle cx="9" cy="9" r="5.5"/><circle cx="16.5" cy="16" r="3.5"/></svg>,
  user:      (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4 21c0-4 4-7 8-7s8 3 8 7"/></svg>,
  help:      (p) => <svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9"/><path d="M9.5 9a2.5 2.5 0 1 1 3.5 2.3c-.7.4-1 .9-1 1.7"/><path d="M12 17h.01"/></svg>,
};

/* ─── AttachIcon ─────────────────────────────────────────────────────── */
function AttachIcon({ type, ...props }) {
  if (type === 'pdf')   return <Icon.filePdf {...props}/>;
  if (type === 'image') return <Icon.image {...props}/>;
  if (type === 'video') return <Icon.video {...props}/>;
  return <Icon.link2 {...props}/>;
}

/* ─── FilterBar ─────────────────────────────────────────────────────── */
function FilterBar({ filters, onToggle }) {
  const active = filters.hidePast || filters.hideDone;
  return (
    <div className={`filterbar ${active ? 'filterbar-active' : ''}`}>
      <button
        className={`filter-chip ${filters.hidePast ? 'filter-chip-on' : ''}`}
        onClick={() => onToggle('hidePast')}
      >
        {filters.hidePast ? <Icon.check width="12" height="12"/> : null}
        {t('filter.hidePast')}
      </button>
      <button
        className={`filter-chip ${filters.hideDone ? 'filter-chip-on' : ''}`}
        onClick={() => onToggle('hideDone')}
      >
        {filters.hideDone ? <Icon.check width="12" height="12"/> : null}
        {t('filter.hideDone')}
      </button>
    </div>
  );
}

/* ─── ViewToggle ────────────────────────────────────────────────────── */
function ViewToggle({ value, onChange }) {
  return (
    <div className="vt" role="tablist" aria-label={t('header.viewAriaLabel')}>
      <button role="tab" aria-selected={value==='agenda'}
              className={`vt-btn ${value==='agenda' ? 'vt-on' : ''}`}
              onClick={() => onChange('agenda')}>
        <Icon.list width="14" height="14"/><span>{t('header.viewAgenda')}</span>
      </button>
      <button role="tab" aria-selected={value==='calendar'}
              className={`vt-btn ${value==='calendar' ? 'vt-on' : ''}`}
              onClick={() => onChange('calendar')}>
        <Icon.grid width="14" height="14"/><span>{t('header.viewCalendar')}</span>
      </button>
      <button role="tab" aria-selected={value==='projects'}
              className={`vt-btn ${value==='projects' ? 'vt-on' : ''}`}
              onClick={() => onChange('projects')}>
        <Icon.bubbles width="14" height="14"/><span>{t('header.viewProjects')}</span>
      </button>
      <span className="vt-thumb" data-pos={value}/>
    </div>
  );
}

/* ─── Header ────────────────────────────────────────────────────────── */
function Header({ dark, onToggleTheme, onAdd, syncStatus, view, onChangeView, username, onOpenAccount, onOpenHelp }) {
  const greet = (() => {
    const h = new Date().getHours();
    if (h < 6) return t('header.greetingNight');
    if (h < 13) return t('header.greetingMorning');
    if (h < 21) return t('header.greetingAfternoon');
    return t('header.greetingNight');
  })();
  return (
    <header className="hdr">
      <div className="hdr-left">
        <div className="hdr-greet">{greet}<span className="hdr-greet-em">,</span></div>
        <div className="hdr-date">
          {fmtLongDate(TODAY)}
          {syncStatus ? <><span className="hdr-sync-sep">·</span><SyncBadge status={syncStatus}/></> : null}
        </div>
      </div>
      <div className="hdr-actions">
        <ViewToggle value={view} onChange={onChangeView}/>
        {onOpenHelp && (
          <button className="icon-btn" onClick={onOpenHelp} aria-label={t('header.help')} title={t('header.help')}>
            <Icon.help width="18" height="18"/>
          </button>
        )}
        {onOpenAccount && (
          <button className="icon-btn" onClick={onOpenAccount} aria-label={t('header.account')} title={username || t('header.account')}>
            <Icon.user width="18" height="18"/>
          </button>
        )}
        <button className="icon-btn" onClick={onToggleTheme} aria-label={t('header.toggleTheme')}>
          {dark ? <Icon.sun width="18" height="18"/> : <Icon.moon width="18" height="18"/>}
        </button>
        <button className="btn-primary" onClick={() => onAdd()}>
          <Icon.plus width="16" height="16"/>
          <span>{t('header.add')}</span>
        </button>
      </div>
    </header>
  );
}

/* ─── ItemCard ──────────────────────────────────────────────────────── */
function ItemCard({ item, onOpen, onDelete, onToggleDone, index, big }) {
  const d = fromISODate(item.date);
  const c = CATEGORIES[item.cat] || CATEGORIES.personal;
  const n = daysBetween(TODAY, d);
  const overdue = n < 0;
  const done = !!item.done;
  const locName = item.location_name || item.location || '';
  const locAddr = item.location_address || item.location_name || item.location || '';
  const hasLocation = !!locName;
  const attachments = item.attachments || [];

  return (
    <div className={`card ${big ? 'card-big' : ''} ${overdue ? 'card-overdue' : ''} ${done ? 'card-done' : ''}`}
         style={{ '--cat': c.dot, '--i': index }}>
      <button className="card-main" onClick={() => onOpen(item)}>
        <span className="card-stripe" aria-hidden="true"/>
        <div className="card-body">
          <div className="card-title">{item.title}</div>
          {item.note ? <div className="card-note">{item.note}</div> : null}
          <div className="card-meta">
            <span className="card-cat"><span className="dot"/>{catLabel(item.cat)}</span>
            <span className="card-sep">·</span>
            <span className="card-date">{fmtShort(d)}</span>
            {item.time && (
              <><span className="card-sep">·</span>
              <span className="card-time">{item.time}{item.time_end ? `–${item.time_end}` : ''}</span></>
            )}
            {!big && (<>
              <span className="card-sep">·</span>
              <span className={`card-rel ${overdue ? 'rel-late' : (n===0 ? 'rel-today' : '')}`}>
                {relativeLabel(d)}
              </span>
            </>)}
          </div>
          {(hasLocation || attachments.length > 0) && (
            <div className="card-extras">
              {hasLocation && (
                <a className="card-location"
                   href={`https://maps.google.com/?q=${encodeURIComponent(locAddr)}`}
                   target="_blank" rel="noopener noreferrer"
                   onClick={e => e.stopPropagation()}
                   title={t('card.openMaps', { name: locName })}>
                  <Icon.pin width="11" height="11"/>
                  <span>{locName}</span>
                </a>
              )}
              {attachments.map(a => (
                <a key={a.id} className="attach-chip"
                   href={a.url} target="_blank" rel="noopener noreferrer"
                   onClick={e => e.stopPropagation()}
                   title={a.name || a.url}>
                  <AttachIcon type={a.type} width="11" height="11"/>
                  <span>{a.name || a.url}</span>
                </a>
              ))}
            </div>
          )}
        </div>
      </button>
      <div className="card-actions">
        <button className={`card-action card-action-done ${done ? 'card-action-done-on' : ''}`}
                onClick={() => onToggleDone(item.id)}
                aria-label={done ? t('card.markPending') : t('card.markDone')}>
          <Icon.check width="13" height="13"/>
        </button>
        <button className="card-action card-action-del"
                onClick={() => onDelete(item.id)} aria-label={t('card.delete')}>
          <Icon.close width="13" height="13"/>
        </button>
      </div>
    </div>
  );
}

/* ─── DaySection ─────────────────────────────────────────────────────── */
function DaySection({ date, items, onOpen, onDelete, onToggleDone, variant }) {
  // variant: 'today' | 'tomorrow' | 'day' | 'overdue' | 'later'
  const isToday = variant === 'today';
  const isTomorrow = variant === 'tomorrow';
  const isLater = variant === 'later';
  const isOver  = variant === 'overdue';
  // "Far" = day of week is ambiguous (next week or later) → emphasize date too.
  const n = date ? daysBetween(TODAY, date) : 0;
  const isFar = variant === 'day' && n >= 7;
  return (
    <section className={`daysec daysec-${variant} ${isFar ? 'daysec-far' : ''}`}>
      <header className="daysec-h">
        <div className="daysec-h-left">
          <span className="daysec-pill">{
            isToday ? t('agendaView.today') :
            isTomorrow ? t('agendaView.tomorrow') :
            isLater ? t('agendaView.later') :
            isOver ? t('agendaView.overdue') :
            fmtWeekday(date)
          }</span>
          {!isToday && !isLater && date && (
            <span className="daysec-date">
              {isTomorrow ? fmtShort(date) :
               isOver ? fmtLongDate(date) :
               fmtDayMonth(date)}
            </span>
          )}
        </div>
        <span className="daysec-count">{items.length}</span>
      </header>
      <div className="daysec-list">
        {items.map((it, i) => (
          <ItemCard key={it.id} item={it} index={i} big={isToday}
                    onOpen={onOpen} onDelete={onDelete} onToggleDone={onToggleDone}/>
        ))}
      </div>
    </section>
  );
}

/* ─── AgendaView — list grouped by day ──────────────────────────────── */
function AgendaView({ items, onOpen, onDelete, onToggleDone, onAddOnDay }) {
  const { sections, later, overdue } = useMemo(() => {
    // Group by ISO date
    const byDate = new Map();
    items.forEach(it => {
      if (!byDate.has(it.date)) byDate.set(it.date, []);
      byDate.get(it.date).push(it);
    });
    const sortedDates = [...byDate.keys()].sort();
    const sections = [];
    const later = [];
    const overdue = [];
    sortedDates.forEach(iso => {
      const d = fromISODate(iso);
      const n = daysBetween(TODAY, d);
      const its = byDate.get(iso).slice().sort((a, b) => {
        if (a.time && b.time) return a.time.localeCompare(b.time);
        if (a.time) return -1;
        if (b.time) return 1;
        return a.title.localeCompare(b.title);
      });
      if (n < 0) {
        overdue.push({ date: d, items: its });
      } else if (n <= 14) {
        let variant = 'day';
        if (n === 0) variant = 'today';
        else if (n === 1) variant = 'tomorrow';
        sections.push({ date: d, items: its, variant });
      } else {
        later.push(...its.map(it => ({ ...it, _d: d })));
      }
    });
    later.sort((a,b) => a._d - b._d);
    return { sections, later, overdue };
  }, [items]);

  // Always show today's section even when empty, with empty-state hint.
  const todayKey = toISODate(TODAY);
  const hasToday = sections.some(s => toISODate(s.date) === todayKey);
  if (!hasToday) {
    sections.unshift({ date: TODAY, items: [], variant: 'today' });
    sections.sort((a,b) => a.date - b.date);
  }

  return (
    <div className="agendaview">
      {overdue.length > 0 && overdue.map(({ date, items }, i) => (
        <DaySection key={`ov-${i}`} date={date} items={items}
                    variant="overdue" onOpen={onOpen} onDelete={onDelete} onToggleDone={onToggleDone}/>
      ))}
      {sections.map((s, i) => {
        if (s.variant === 'today' && s.items.length === 0) {
          return (
            <section key={`s-${i}`} className="daysec daysec-today daysec-empty-today">
              <header className="daysec-h">
                <div className="daysec-h-left">
                  <span className="daysec-pill">{t('agendaView.today')}</span>
                </div>
              </header>
              <div className="daysec-empty">
                {t('agendaView.emptyToday')}
                <button className="btn-ghost" onClick={() => onAddOnDay(TODAY)}>
                  <Icon.plus width="14" height="14"/> {t('agendaView.add')}
                </button>
              </div>
            </section>
          );
        }
        return (
          <DaySection key={`s-${i}`} date={s.date} items={s.items}
                      variant={s.variant}
                      onOpen={onOpen} onDelete={onDelete} onToggleDone={onToggleDone}/>
        );
      })}
      {later.length > 0 && (
        <DaySection date={null} items={later} variant="later"
                    onOpen={onOpen} onDelete={onDelete} onToggleDone={onToggleDone}/>
      )}
    </div>
  );
}

/* ─── MiniCalendar (side, Agenda view) ──────────────────────────────── */
function MiniCalendar({ items, monthAnchor, setMonthAnchor, selectedDate, setSelectedDate, onAddOnDay }) {
  const first = startOfMonth(monthAnchor);
  const gridStart = startOfWeek(first);
  const days = useMemo(() => Array.from({ length: 42 }, (_, i) => addDays(gridStart, i)),
                       [gridStart.getTime()]);
  const counts = useMemo(() => {
    const map = new Map();
    items.forEach(it => map.set(it.date, (map.get(it.date) || 0) + 1));
    return map;
  }, [items]);
  const goPrev = () => setMonthAnchor(addDays(startOfMonth(monthAnchor), -1));
  const goNext = () => setMonthAnchor(addDays(endOfMonth(monthAnchor), 1));
  const goToday = () => { setMonthAnchor(TODAY); setSelectedDate(TODAY); };

  return (
    <div className="cal">
      <div className="cal-h">
        <div className="cal-h-title">{fmtMonthYear(monthAnchor)}</div>
        <div className="cal-h-nav">
          <button className="icon-btn sm" onClick={goPrev} aria-label={t('cal.prevMonth')}><Icon.left width="15" height="15"/></button>
          <button className="cal-today" onClick={goToday}>{t('cal.today')}</button>
          <button className="icon-btn sm" onClick={goNext} aria-label={t('cal.nextMonth')}><Icon.right width="15" height="15"/></button>
        </div>
      </div>
      <div className="cal-wkrow">
        {t('weekdaysShort').map(w => <div key={w} className="cal-wk">{w}</div>)}
      </div>
      <div className="cal-grid" key={`${monthAnchor.getFullYear()}-${monthAnchor.getMonth()}`}>
        {days.map(d => {
          const inMonth = d.getMonth() === monthAnchor.getMonth();
          const isToday = isSameDay(d, TODAY);
          const isSel = selectedDate && isSameDay(d, selectedDate);
          const count = counts.get(toISODate(d)) || 0;
          return (
            <button
              key={toISODate(d)}
              className={`cal-d ${inMonth?'':'cal-d-out'} ${isToday?'cal-d-today':''} ${isSel?'cal-d-sel':''} ${count?'cal-d-has':''}`}
              onClick={() => {
                if (count === 0) onAddOnDay(d);
                else setSelectedDate(isSel ? null : d);
              }}
              onDoubleClick={() => onAddOnDay(d)}
              title={count ? t('cal.itemsThisDay', { n: count }) : t('cal.addHere')}
            >
              <span className="cal-d-num">{d.getDate()}</span>
              {count > 0 && (
                <span className="cal-d-dots" aria-hidden="true">
                  {Array.from({ length: Math.min(count, 3) }, (_, i) => <span key={i} className="cal-d-dot"/>)}
                </span>
              )}
            </button>
          );
        })}
      </div>
    </div>
  );
}

/* ─── CalendarView — two months side by side, deadlines as pills ───── */
function CalendarView({ items, monthAnchor, setMonthAnchor, onOpen, onAddOnDay, onDelete, onToggleDone }) {
  const monthA = startOfMonth(monthAnchor);
  const monthB = startOfMonth(addDays(endOfMonth(monthA), 1));

  const byDate = useMemo(() => {
    const m = new Map();
    items.forEach(it => {
      if (!m.has(it.date)) m.set(it.date, []);
      m.get(it.date).push(it);
    });
    m.forEach(arr => arr.sort((a, b) => {
      if (a.time && b.time) return a.time.localeCompare(b.time);
      if (a.time) return -1;
      if (b.time) return 1;
      return a.title.localeCompare(b.title);
    }));
    return m;
  }, [items]);

  const [pickDay, setPickDay] = useState(null);
  const goPrev = () => setMonthAnchor(addDays(monthA, -1));
  const goNext = () => setMonthAnchor(addDays(endOfMonth(monthA), 1));
  const goToday = () => setMonthAnchor(TODAY);

  return (
    <div className="cv">
      <div className="cv-h">
        <div className="cv-h-titles">
          <span className="cv-h-title">{fmtMonthYear(monthA)}</span>
          <span className="cv-h-arr" aria-hidden="true">→</span>
          <span className="cv-h-title cv-h-title-b">{fmtMonthYear(monthB)}</span>
        </div>
        <div className="cv-h-nav">
          <button className="icon-btn sm" onClick={goPrev} aria-label={t('cal.prevMonth')}><Icon.left width="15" height="15"/></button>
          <button className="cal-today" onClick={goToday}>{t('cal.today')}</button>
          <button className="icon-btn sm" onClick={goNext} aria-label={t('cal.nextMonth')}><Icon.right width="15" height="15"/></button>
        </div>
      </div>
      <div className="cv-twin">
        <CMonth month={monthA} byDate={byDate} onOpen={onOpen} onAddOnDay={onAddOnDay} onMore={setPickDay}/>
        <CMonth month={monthB} byDate={byDate} onOpen={onOpen} onAddOnDay={onAddOnDay} onMore={setPickDay}/>
      </div>
      {pickDay && (
        <DayPanel
          date={pickDay}
          items={(byDate.get(toISODate(pickDay)) || [])}
          onOpen={(it) => { setPickDay(null); onOpen(it); }}
          onDelete={onDelete}
          onToggleDone={onToggleDone}
          onClose={() => setPickDay(null)}
          onAddOnDay={onAddOnDay}
          floating
        />
      )}
    </div>
  );
}

function CMonth({ month, byDate, onOpen, onAddOnDay, onMore }) {
  const first = startOfMonth(month);
  const gridStart = startOfWeek(first);
  const lastOfMonth = endOfMonth(month);

  const weeks = [];
  let cursor = gridStart;
  while (cursor <= lastOfMonth) {
    weeks.push(Array.from({ length: 7 }, (_, i) => addDays(cursor, i)));
    cursor = addDays(cursor, 7);
  }

  const monthIdx = month.getMonth();
  const weekLoad = weeks.map(w =>
    w.reduce((sum, d) => {
      if (d.getMonth() !== monthIdx) return sum;
      return sum + ((byDate.get(toISODate(d)) || []).length);
    }, 0)
  );
  const maxLoad = Math.max(1, ...weekLoad);

  return (
    <div className="cmo">
      <div className="cmo-h">
        <span className="cmo-h-num">{t('monthsLong')[month.getMonth()]}</span>
        <span className="cmo-h-yr">{month.getFullYear()}</span>
      </div>
      <div className="cmo-wkrow">
        <div className="cmo-loadhdr" aria-hidden="true"/>
        {t('weekdaysShort').map(w => <div key={w} className="cmo-wkd">{w}</div>)}
      </div>
      <div className="cmo-weeks">
        {weeks.map((week, wi) => {
          const load = weekLoad[wi];
          const ratio = load / maxLoad;
          return (
            <div key={wi} className="cmo-week">
              <div className="cmo-load" title={t('cal.itemsThisWeek', { n: load })}>
                <span className="cmo-load-bar" style={{ '--r': ratio }}/>
                {load > 0 && <span className="cmo-load-num">{load}</span>}
              </div>
              {week.map(d => {
                const inMonth = d.getMonth() === monthIdx;
                const isToday = isSameDay(d, TODAY);
                const isPast = daysBetween(TODAY, d) < 0;
                const its = inMonth ? (byDate.get(toISODate(d)) || []) : [];
                return (
                  <div key={toISODate(d)}
                       className={`cmo-d ${inMonth?'':'cmo-d-out'} ${isToday?'cmo-d-today':''} ${isPast?'cmo-d-past':''} ${its.length?'cmo-d-busy':''}`}>
                    <button className="cmo-d-num" onClick={() => onAddOnDay(d)}
                            aria-label={t('cal.addOnDate', { day: d.getDate() })}>
                      <span>{d.getDate()}</span>
                    </button>
                    {its.length > 0 && (
                      <div className="cmo-d-list">
                        {its.map(it => {
                          const c = CATEGORIES[it.cat] || CATEGORIES.personal;
                          return (
                            <button key={it.id}
                                    className={`cmo-it ${it.done ? 'cmo-it-done' : ''}`}
                                    style={{ '--c': c.dot }}
                                    onClick={() => onOpen(it)} title={it.title}>
                              <span className="cmo-it-title">{it.title}</span>
                            </button>
                          );
                        })}
                      </div>
                    )}
                  </div>
                );
              })}
            </div>
          );
        })}
      </div>
    </div>
  );
}

/* ─── DayPanel ──────────────────────────────────────────────────────── */
function DayPanel({ date, items, onOpen, onDelete, onToggleDone, onClose, onAddOnDay, floating }) {
  if (!date) return null;
  const content = (
    <div className={`daypanel ${floating ? 'daypanel-floating' : ''}`}>
      <div className="daypanel-h">
        <div>
          <div className="daypanel-rel">{relativeLabel(date)}</div>
          <div className="daypanel-date">{fmtLongDate(date)}</div>
        </div>
        <button className="icon-btn sm" onClick={onClose} aria-label={t('daypanel.close')}>
          <Icon.close width="14" height="14"/>
        </button>
      </div>
      {items.length === 0 ? (
        <div className="daypanel-empty">
          <div>{t('daypanel.empty')}</div>
          <button className="btn-ghost" onClick={() => onAddOnDay(date)}>
            <Icon.plus width="14" height="14"/> {t('daypanel.add')}
          </button>
        </div>
      ) : (
        <div className="daypanel-list">
          {items.map((it, i) => (
            <ItemCard key={it.id} item={it} index={i} onOpen={onOpen} onDelete={onDelete} onToggleDone={onToggleDone}/>
          ))}
          <button className="btn-ghost block" onClick={() => onAddOnDay(date)}>
            <Icon.plus width="14" height="14"/> {t('daypanel.addOnDay')}
          </button>
        </div>
      )}
    </div>
  );
  if (!floating) return content;
  return (
    <div className="daypanel-back" onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      {content}
    </div>
  );
}

/* ─── EditorModal ───────────────────────────────────────────────────── */
function EditorModal({ open, draft, onChange, onSave, onCancel, onDelete, apiBase }) {
  const titleRef = useRef(null);
  const fileInputRef = useRef(null);
  const [addingLink, setAddingLink] = useState(false);
  const [linkForm, setLinkForm] = useState({ name: '', url: '' });
  const [uploading, setUploading] = useState(false);
  const [uploadError, setUploadError] = useState('');

  useEffect(() => {
    if (open && titleRef.current) {
      const tm = setTimeout(() => titleRef.current && titleRef.current.focus(), 60);
      return () => clearTimeout(tm);
    }
  }, [open]);

  useEffect(() => {
    if (!open) {
      setAddingLink(false);
      setLinkForm({ name: '', url: '' });
      setUploading(false);
      setUploadError('');
    }
  }, [open]);

  useEffect(() => {
    if (!open) return;
    const onKey = (e) => {
      if (e.key === 'Escape') { if (addingLink) { setAddingLink(false); } else { onCancel(); } }
      if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) onSave();
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [open, onSave, onCancel, addingLink]);

  if (!open) return null;
  const isEditing = !!draft.id && !draft.isNew;
  const attachments = draft.attachments || [];

  function removeAttachment(id) {
    const att = attachments.find(a => a.id === id);
    onChange({ ...draft, attachments: attachments.filter(a => a.id !== id) });
    // Best-effort server cleanup for uploaded files
    if (att && att.type !== 'link' && apiBase) {
      const filename = att.url.split('/').pop();
      fetch(`${apiBase}/upload/${encodeURIComponent(filename)}`, { method: 'DELETE', credentials: 'same-origin' }).catch(() => {});
    }
  }

  function confirmLink() {
    if (!linkForm.url.trim()) return;
    const url = linkForm.url.trim();
    const name = linkForm.name.trim() || url;
    const newAtt = { id: `att-${Date.now().toString(36)}`, type: 'link', name, url };
    onChange({ ...draft, attachments: [...attachments, newAtt] });
    setAddingLink(false);
    setLinkForm({ name: '', url: '' });
  }

  function handleFileChange(e) {
    const file = e.target.files?.[0];
    if (!file || !apiBase) return;
    e.target.value = '';
    setUploading(true);
    setUploadError('');
    const fd = new FormData();
    fd.append('file', file);
    fetch(`${apiBase}/upload`, { method: 'POST', body: fd, credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : r.json().then(j => Promise.reject(j.error || t('editor.uploadError'))))
      .then(data => {
        const newAtt = {
          id:   `att-${Date.now().toString(36)}`,
          type: data.type,
          name: data.name,
          url:  data.url,
        };
        onChange({ ...draft, attachments: [...attachments, newAtt] });
        setUploading(false);
      })
      .catch(err => {
        setUploadError(typeof err === 'string' ? err : t('editor.uploadError'));
        setUploading(false);
      });
  }

  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('editor.editTitle') : t('editor.newTitle')}</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">
          <label className="field">
            <span className="field-lbl">{t('editor.titleLabel')}</span>
            <input ref={titleRef} type="text" value={draft.title}
              placeholder={t('editor.titlePlaceholder')}
              onChange={(e) => onChange({ ...draft, title: e.target.value })}/>
          </label>
          <label className="field">
            <span className="field-lbl">{t('editor.dateLabel')}</span>
            <input type="date" value={draft.date}
              onChange={(e) => onChange({ ...draft, date: e.target.value })}/>
          </label>
          <div className="field-row">
            <label className="field field-half">
              <span className="field-lbl">{t('editor.timeLabel')} <span className="field-opt">{t('editor.optional')}</span></span>
              <input type="time" value={draft.time || ''}
                onChange={(e) => onChange({ ...draft, time: e.target.value })}/>
            </label>
            <label className="field field-half">
              <span className="field-lbl">{t('editor.timeEndLabel')} <span className="field-opt">{t('editor.optional')}</span></span>
              <input type="time" value={draft.time_end || ''}
                onChange={(e) => onChange({ ...draft, time_end: e.target.value })}/>
            </label>
          </div>
          <div className="field">
            <span className="field-lbl">{t('editor.categoryLabel')}</span>
            <div className="cat-chips">
              {CAT_KEYS.map(k => {
                const c = CATEGORIES[k];
                const sel = draft.cat === k;
                return (
                  <button key={k}
                          className={`chip ${sel ? 'chip-sel' : ''}`}
                          style={{ '--dot': c.dot }}
                          onClick={() => onChange({ ...draft, cat: k })}>
                    <span className="dot"/>{catLabel(k)}
                  </button>
                );
              })}
            </div>
          </div>
          <label className="field">
            <span className="field-lbl">{t('editor.noteLabel')} <span className="field-opt">{t('editor.optional')}</span></span>
            <textarea rows={2} value={draft.note}
              placeholder={t('editor.notePlaceholder')}
              onChange={(e) => onChange({ ...draft, note: e.target.value })}/>
          </label>

          {/* Location */}
          <label className="field">
            <span className="field-lbl">
              <Icon.pin width="11" height="11" style={{marginRight:4,verticalAlign:'middle'}}/>
              {t('editor.locationNameLabel')} <span className="field-opt">{t('editor.optional')}</span>
            </span>
            <input type="text" value={draft.location_name || ''}
              placeholder={t('editor.locationNamePlaceholder')}
              onChange={(e) => onChange({ ...draft, location_name: e.target.value })}/>
          </label>
          <label className="field">
            <span className="field-lbl">
              {t('editor.locationAddressLabel')} <span className="field-opt">{t('editor.locationAddressHint')}</span>
            </span>
            <input type="text" value={draft.location_address || ''}
              placeholder={t('editor.locationAddressPlaceholder')}
              onChange={(e) => onChange({ ...draft, location_address: e.target.value })}/>
          </label>

          {/* Attachments */}
          <div className="field">
            <span className="field-lbl">
              <Icon.paperclip width="11" height="11" style={{marginRight:4,verticalAlign:'middle'}}/>
              {t('editor.attachmentsLabel')} <span className="field-opt">{t('editor.optional')}</span>
            </span>

            {attachments.length > 0 && (
              <div className="attach-list">
                {attachments.map(a => (
                  <div key={a.id} className="attach-item">
                    <AttachIcon type={a.type} width="14" height="14" className="attach-item-icon"/>
                    <a className="attach-item-name" href={a.url} target="_blank" rel="noopener noreferrer"
                       title={a.name || a.url}>
                      {a.name || a.url}
                    </a>
                    <button className="attach-item-del icon-btn sm" onClick={() => removeAttachment(a.id)}
                            aria-label={t('editor.removeAttachment')}>
                      <Icon.close width="12" height="12"/>
                    </button>
                  </div>
                ))}
              </div>
            )}

            {addingLink ? (
              <div className="attach-add-form">
                <input type="text" value={linkForm.name} placeholder={t('editor.linkNamePlaceholder')}
                  onChange={e => setLinkForm(f => ({ ...f, name: e.target.value }))}/>
                <div className="attach-add-form-row">
                  <input type="url" value={linkForm.url} placeholder={t('editor.linkUrlPlaceholder')}
                    style={{ flex: 1 }}
                    onChange={e => setLinkForm(f => ({ ...f, url: e.target.value }))}
                    onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); confirmLink(); } }}
                    autoFocus/>
                  <button className="btn-primary" onClick={confirmLink} disabled={!linkForm.url.trim()}>
                    {t('editor.confirmAdd')}
                  </button>
                  <button className="btn-ghost" onClick={() => { setAddingLink(false); setLinkForm({ name: '', url: '' }); }}>
                    <Icon.close width="13" height="13"/>
                  </button>
                </div>
              </div>
            ) : (
              <div className="attach-add-row">
                <button className="btn-ghost attach-add-btn" onClick={() => setAddingLink(true)}>
                  <Icon.link2 width="13" height="13"/> {t('editor.addLink')}
                </button>
                {apiBase ? (
                  <button className="btn-ghost attach-add-btn" onClick={() => fileInputRef.current?.click()}
                          disabled={uploading}>
                    {uploading
                      ? <><Icon.spinner width="13" height="13" className="spin"/> {t('editor.uploading')}</>
                      : <><Icon.paperclip width="13" height="13"/> {t('editor.addFile')}</>}
                  </button>
                ) : null}
                <input ref={fileInputRef} type="file"
                  accept=".pdf,image/*,video/*"
                  style={{ display: 'none' }}
                  onChange={handleFileChange}/>
              </div>
            )}
            {uploadError && <div className="attach-error">{uploadError}</div>}
          </div>
        </div>
        <div className="modal-f">
          {isEditing
            ? <button className="btn-danger" onClick={() => onDelete(draft.id)}>
                <Icon.trash width="14" height="14"/> {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>
  );
}

/* ─── FAB (mobile add) ──────────────────────────────────────────────── */
function FAB({ onAdd }) {
  return (
    <button className="fab" onClick={() => onAdd()} aria-label={t('fab.add')}>
      <Icon.plus width="22" height="22"/>
    </button>
  );
}

Object.assign(window, {
  Header, ViewToggle, ItemCard, DaySection, AgendaView,
  MiniCalendar, CalendarView, DayPanel, EditorModal, FAB, Icon, AttachIcon,
});
