/* Custom dashboards — preset dashboards + edit mode + widget library */

const DEFAULT_DASHBOARDS = [
  {
    id: 'my-focus',
    name: 'My Focus',
    sub: 'Personal — what Alex sees first thing',
    icon: '☀',
    widgets: [
      { id: 'w1', type: 'ai-snapshot', col: 12, prompt: 'morning' },
      { id: 'w2', type: 'kpi',         col: 3,  metric: 'my-today',    title: 'On today',   tint: 'accent' },
      { id: 'w3', type: 'kpi',         col: 3,  metric: 'my-overdue',  title: 'Overdue',    tint: 'brick' },
      { id: 'w4', type: 'kpi',         col: 3,  metric: 'my-mentions', title: 'Mentions',   tint: 'warm' },
      { id: 'w5', type: 'kpi',         col: 3,  metric: 'my-week-est', title: 'This week',  tint: 'forest' },
      { id: 'w6', type: 'task-list',   col: 7,  filter: 'mine-today',  title: 'My day, ranked' },
      { id: 'w7', type: 'calendar',    col: 5,  title: 'Next 14 days' },
    ],
  },
  {
    id: 'team-pulse',
    name: 'Team Pulse',
    sub: 'Distribution, workload, momentum',
    icon: '◐',
    widgets: [
      { id: 'w1', type: 'kpi',         col: 4, metric: 'active',         title: 'Active',       tint: 'accent'  },
      { id: 'w2', type: 'kpi',         col: 4, metric: 'overdue',        title: 'Overdue',      tint: 'brick'   },
      { id: 'w3', type: 'kpi',         col: 4, metric: 'done-this-week', title: 'Done this wk', tint: 'forest'  },
      { id: 'w4', type: 'bar',         col: 6, by: 'assignee',           title: 'By assignee'   },
      { id: 'w5', type: 'donut',       col: 6, by: 'priority',           title: 'Priority mix'  },
      { id: 'w6', type: 'time-tracker',col: 7, title: 'Estimate vs logged · this month' },
      { id: 'w7', type: 'activity',    col: 5, title: 'Live activity' },
    ],
  },
  {
    id: 'practice-manager',
    name: 'Practice manager',
    sub: 'Compliance, SLA, billing',
    icon: '◉',
    widgets: [
      { id: 'w1', type: 'ai-snapshot', col: 12, prompt: 'weekly-summary' },
      { id: 'w2', type: 'kpi',         col: 3, metric: 'sla-health',     title: 'SLA health',   tint: 'forest' },
      { id: 'w3', type: 'kpi',         col: 3, metric: 'billing-outs',   title: 'Outstanding',  tint: 'warm' },
      { id: 'w4', type: 'kpi',         col: 3, metric: 'compliance',     title: 'Compliance',   tint: 'navy' },
      { id: 'w5', type: 'kpi',         col: 3, metric: 'safeguarding',   title: 'Safeguarding', tint: 'brick' },
      { id: 'w6', type: 'bar',         col: 6, by: 'space',              title: 'By space' },
      { id: 'w7', type: 'donut',       col: 6, by: 'status',             title: 'Status mix' },
    ],
  },
];

const WIDGET_LIBRARY = [
  { type: 'kpi',          name: 'KPI tile',         glyph: '◇', desc: 'Number + label + sparkline',           col: 3 },
  { type: 'bar',          name: 'Bar chart',        glyph: '▤', desc: 'Group tasks by a dimension',           col: 6 },
  { type: 'donut',        name: 'Donut chart',      glyph: '◐', desc: 'Composition of a dimension',           col: 6 },
  { type: 'task-list',    name: 'Task list',        glyph: '☰', desc: 'Saved filter as a feed',               col: 7 },
  { type: 'calendar',     name: 'Calendar mini',    glyph: '▦', desc: 'Compact month or fortnight',           col: 5 },
  { type: 'activity',     name: 'Activity feed',    glyph: '◴', desc: 'Live team activity',                    col: 5 },
  { type: 'time-tracker', name: 'Time tracker',     glyph: '⏱', desc: 'Estimate vs logged per person',         col: 7 },
  { type: 'ai-snapshot',  name: 'Pulse snapshot',   glyph: '✦', desc: 'AI-generated narrative paragraph',     col: 12 },
];

function DashboardsView({ allTasks }) {
  const [dashboards, setDashboards] = React.useState(() => {
    try {
      const saved = JSON.parse(localStorage.getItem('tashhub-dashboards') || 'null');
      if (saved && Array.isArray(saved) && saved.length) return saved;
    } catch (e) {}
    return DEFAULT_DASHBOARDS;
  });
  const [activeId, setActiveId] = React.useState(dashboards[0]?.id);
  const [editing, setEditing]   = React.useState(false);
  const [showLib, setShowLib]   = React.useState(false);

  const active = dashboards.find(d => d.id === activeId) || dashboards[0];

  const persist = (next) => {
    setDashboards(next);
    try { localStorage.setItem('tashhub-dashboards', JSON.stringify(next)); } catch (e) {}
  };

  const updateActive = (mut) => {
    persist(dashboards.map(d => d.id === activeId ? mut(d) : d));
  };

  const removeWidget = (wid) => updateActive(d => ({ ...d, widgets: d.widgets.filter(w => w.id !== wid) }));
  const moveWidget   = (wid, dir) => updateActive(d => {
    const arr = [...d.widgets];
    const i = arr.findIndex(w => w.id === wid);
    const j = i + dir;
    if (i < 0 || j < 0 || j >= arr.length) return d;
    [arr[i], arr[j]] = [arr[j], arr[i]];
    return { ...d, widgets: arr };
  });
  const addWidget = (type) => {
    const def = WIDGET_LIBRARY.find(w => w.type === type);
    const id = 'w' + Math.random().toString(36).slice(2, 7);
    const defaults = { kpi: { metric: 'active', title: 'New KPI', tint: 'accent' },
                       bar: { by: 'space', title: 'New bar chart' },
                       donut: { by: 'priority', title: 'New donut' },
                       'task-list': { filter: 'all', title: 'New list' },
                       calendar: { title: 'Calendar' },
                       activity: { title: 'Activity' },
                       'time-tracker': { title: 'Time tracker' },
                       'ai-snapshot': { prompt: 'morning' },
                     }[type] || {};
    updateActive(d => ({ ...d, widgets: [...d.widgets, { id, type, col: def.col, ...defaults }] }));
    setShowLib(false);
  };
  const resetActive = () => {
    const reset = DEFAULT_DASHBOARDS.find(d => d.id === activeId);
    if (reset) updateActive(() => ({ ...reset }));
  };

  return (
    <div style={{ flex: 1, overflowY: 'auto', background: 'rgb(var(--bg))' }}>
      <div style={{ padding: '24px 28px 60px' }}>
        {/* Header */}
        <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', marginBottom: 18 }}>
          <div>
            <p style={{ margin: 0, fontSize: 12, fontWeight: 600, letterSpacing: '0.04em', color: 'rgb(var(--accent))', textTransform: 'uppercase' }}>Dashboards</p>
            <h1 style={{ margin: '4px 0 0', fontSize: 30, fontWeight: 700, letterSpacing: '-0.025em' }}>{active.name}</h1>
            <p style={{ margin: '4px 0 0', fontSize: 13, color: 'rgb(var(--muted))' }}>{active.sub}</p>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            {editing && (
              <>
                <button onClick={() => setShowLib(true)} className="btn btn-accent" style={{ fontSize: 12 }}>+ Add widget</button>
                <button onClick={resetActive} className="btn btn-secondary" style={{ fontSize: 12 }}>Reset</button>
              </>
            )}
            <button onClick={() => setEditing(e => !e)} className={editing ? 'btn btn-primary' : 'btn btn-secondary'} style={{ fontSize: 12 }}>
              {editing ? '✓ Done' : '✎ Edit'}
            </button>
          </div>
        </div>

        {/* Dashboard switcher */}
        <div style={{ display: 'flex', gap: 8, marginBottom: 22, overflowX: 'auto', paddingBottom: 4 }}>
          {dashboards.map(d => (
            <button key={d.id} onClick={() => setActiveId(d.id)} style={{
              display: 'inline-flex', alignItems: 'center', gap: 8,
              padding: '8px 14px',
              background: d.id === activeId ? 'rgb(var(--ink))' : 'rgb(var(--surface))',
              color: d.id === activeId ? 'white' : 'rgb(var(--ink-2))',
              border: '1px solid ' + (d.id === activeId ? 'rgb(var(--ink))' : 'rgb(var(--rule))'),
              borderRadius: 999, fontSize: 13, fontWeight: 600,
              flexShrink: 0,
            }}>
              <span>{d.icon}</span>{d.name}
            </button>
          ))}
          {editing && (
            <button onClick={() => {
              const id = 'custom-' + Math.random().toString(36).slice(2, 6);
              const next = [...dashboards, { id, name: 'New dashboard', sub: 'Build it your way', icon: '◇', widgets: [] }];
              persist(next); setActiveId(id);
            }} className="btn btn-secondary" style={{ fontSize: 13, padding: '8px 14px', borderRadius: 999, flexShrink: 0 }}>+ New dashboard</button>
          )}
        </div>

        {/* Grid */}
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(12, 1fr)', gap: 14 }}>
          {active.widgets.map((w, i) => (
            <Widget
              key={w.id}
              widget={w}
              allTasks={allTasks}
              editing={editing}
              onRemove={() => removeWidget(w.id)}
              onMoveUp={() => moveWidget(w.id, -1)}
              onMoveDown={() => moveWidget(w.id, 1)}
              first={i === 0} last={i === active.widgets.length - 1}
            />
          ))}
          {active.widgets.length === 0 && (
            <div style={{
              gridColumn: 'span 12', padding: 60, textAlign: 'center',
              background: 'rgb(var(--surface))', border: '1.5px dashed rgb(var(--rule-2))', borderRadius: 14,
            }}>
              <p style={{ margin: 0, fontSize: 16, fontWeight: 600 }}>Empty dashboard</p>
              <p style={{ margin: '4px 0 14px', fontSize: 13, color: 'rgb(var(--muted))' }}>Add a KPI, chart, or AI snapshot to get started.</p>
              <button onClick={() => setShowLib(true)} className="btn btn-accent" style={{ fontSize: 13 }}>+ Add a widget</button>
            </div>
          )}
        </div>
      </div>

      {showLib && <WidgetLibrary onClose={() => setShowLib(false)} onAdd={addWidget} />}
    </div>
  );
}

/* ──────────── Widgets ──────────── */
function Widget({ widget: w, allTasks, editing, onRemove, onMoveUp, onMoveDown, first, last }) {
  const title = w.title || WIDGET_LIBRARY.find(l => l.type === w.type)?.name || w.type;
  return (
    <div style={{
      gridColumn: `span ${w.col || 4}`,
      background: 'rgb(var(--surface))',
      border: '1px solid rgb(var(--rule))',
      borderRadius: 14,
      boxShadow: 'var(--sh-sm)',
      overflow: 'hidden',
      position: 'relative',
      minHeight: 120,
      display: 'flex', flexDirection: 'column',
    }}>
      {/* Widget header */}
      <div style={{
        display: 'flex', alignItems: 'center', gap: 8,
        padding: '12px 16px',
        borderBottom: '1px solid rgb(var(--rule) / 0.7)',
      }}>
        <span style={{ fontSize: 12, color: 'rgb(var(--muted))', fontWeight: 600 }}>{WIDGET_LIBRARY.find(l => l.type === w.type)?.glyph}</span>
        <h3 style={{ margin: 0, fontSize: 13.5, fontWeight: 700, color: 'rgb(var(--ink))', letterSpacing: '-0.005em' }}>{title}</h3>
        <span style={{ flex: 1 }} />
        {editing && (
          <>
            <button onClick={onMoveUp}   disabled={first} title="Move up"   style={ctrlBtn(first)}>↑</button>
            <button onClick={onMoveDown} disabled={last}  title="Move down" style={ctrlBtn(last)}>↓</button>
            <button onClick={onRemove}   title="Remove"  style={{...ctrlBtn(false), color: 'rgb(var(--brick))'}}>×</button>
          </>
        )}
      </div>
      <div style={{ flex: 1, padding: 16 }}>
        {w.type === 'kpi'          && <KpiWidget w={w} allTasks={allTasks} />}
        {w.type === 'bar'          && <BarWidget w={w} allTasks={allTasks} />}
        {w.type === 'donut'        && <DonutWidget w={w} allTasks={allTasks} />}
        {w.type === 'task-list'    && <TaskListWidget w={w} allTasks={allTasks} />}
        {w.type === 'calendar'     && <CalendarWidget w={w} allTasks={allTasks} />}
        {w.type === 'activity'     && <ActivityWidget w={w} />}
        {w.type === 'time-tracker' && <TimeTrackerWidget w={w} allTasks={allTasks} />}
        {w.type === 'ai-snapshot'  && <AiSnapshotWidget w={w} allTasks={allTasks} />}
      </div>
    </div>
  );
}
const ctrlBtn = (disabled) => ({
  width: 24, height: 24, borderRadius: 6,
  fontSize: 13, color: 'rgb(var(--muted))',
  display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
  opacity: disabled ? 0.3 : 1,
  cursor: disabled ? 'default' : 'pointer',
});

/* KPI */
function KpiWidget({ w, allTasks }) {
  const tint =
    w.tint === 'brick'  ? 'rgb(var(--brick))' :
    w.tint === 'warm'   ? 'rgb(var(--warm))' :
    w.tint === 'forest' ? 'rgb(var(--forest))' :
    w.tint === 'navy'   ? 'rgb(var(--navy))' :
                          'rgb(var(--accent))';
  const soft =
    w.tint === 'brick'  ? 'rgb(var(--brick-soft))' :
    w.tint === 'warm'   ? 'rgb(var(--warm-soft))' :
    w.tint === 'forest' ? 'rgb(var(--forest-soft))' :
    w.tint === 'navy'   ? 'rgb(var(--navy-soft))' :
                          'rgb(var(--accent-soft))';

  let value, delta;
  switch (w.metric) {
    case 'active':         value = allTasks.filter(t => t.statusId !== 6).length; delta = '+3 this week'; break;
    case 'overdue':        value = allTasks.filter(t => t.dueDate && dayDelta(t.dueDate) < 0 && t.statusId !== 6).length; delta = '-2 since Monday'; break;
    case 'done-this-week': value = allTasks.filter(t => t.statusId === 6).length + 6; delta = '+6 vs last week'; break;
    case 'my-today':       value = allTasks.filter(t => t.myDay).length; delta = `${value > 4 ? 'heavy day' : 'manageable'}`; break;
    case 'my-overdue':     value = allTasks.filter(t => t.dueDate && dayDelta(t.dueDate) < 0 && t.assignees.includes(1)).length; delta = 'try to clear by EOD'; break;
    case 'my-mentions':    value = 3; delta = '3 unread'; break;
    case 'my-week-est':    value = fmtMins(allTasks.filter(t => t.assignees.includes(1)).reduce((s, t) => s + (t.estimateMins || 0), 0)); delta = 'this calendar week'; break;
    case 'sla-health':     value = '94%'; delta = '+2pp vs last week'; break;
    case 'billing-outs':   value = '£8.4k'; delta = '12 invoices'; break;
    case 'compliance':     value = '7/9'; delta = '2 outstanding items'; break;
    case 'safeguarding':   value = 2; delta = 'amber-flag, none red'; break;
    default: value = '—'; delta = '';
  }
  // Tiny sparkline
  const spark = Array.from({ length: 14 }, (_, i) => 4 + Math.round(Math.abs(Math.sin(i * 1.2 + (w.id?.charCodeAt?.(1) || 0))) * 12));
  const max = Math.max(...spark);
  return (
    <div>
      <p className="num" style={{
        margin: 0, fontSize: typeof value === 'string' && value.length > 4 ? 26 : 34,
        fontWeight: 700, letterSpacing: '-0.03em', color: tint, lineHeight: 1,
      }}>{value}</p>
      <p style={{ margin: '6px 0 12px', fontSize: 11.5, color: 'rgb(var(--muted))' }}>{delta}</p>
      <svg viewBox={`0 0 ${spark.length * 8} 30`} style={{ width: '100%', height: 30, display: 'block' }} preserveAspectRatio="none">
        <polyline
          fill="none" stroke={tint} strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round"
          points={spark.map((v, i) => `${i * 8},${28 - (v / max) * 26}`).join(' ')}
        />
        <polyline
          fill={soft} stroke="none"
          points={`0,30 ${spark.map((v, i) => `${i * 8},${28 - (v / max) * 26}`).join(' ')} ${(spark.length - 1) * 8},30`}
        />
      </svg>
    </div>
  );
}

/* Bar chart by dimension */
function BarWidget({ w, allTasks }) {
  const rows = React.useMemo(() => {
    if (w.by === 'space')    return D.spaces.map(s => ({ name: s.name, c: allTasks.filter(t => t.spaceId === s.id && t.statusId !== 6).length, colour: s.colour })).sort((a,b)=>b.c-a.c);
    if (w.by === 'priority') return [['urgent','Urgent','#EF4444'],['high','High','#F97316'],['medium','Medium','#F59E0B'],['low','Low','#94A3B8']].map(([k,n,c])=>({ name:n, c: allTasks.filter(t=>t.priority===k && t.statusId!==6).length, colour:c }));
    if (w.by === 'status')   return D.statuses.map(s => ({ name: s.title, c: allTasks.filter(t => t.statusId === s.id).length, colour: s.colour })).filter(r=>r.c>0);
    /* assignee */            return D.users.map(u => ({ name: u.name.split(' ')[0], c: allTasks.filter(t => t.assignees.includes(u.id) && t.statusId !== 6).length, colour: u.colour })).filter(r=>r.c>0).sort((a,b)=>b.c-a.c);
  }, [w.by, allTasks]);
  const max = Math.max(1, ...rows.map(r => r.c));
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      {rows.map(r => (
        <div key={r.name} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
          <span style={{ width: 90, fontSize: 12, color: 'rgb(var(--ink-2))', fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{r.name}</span>
          <div style={{ flex: 1, height: 10, background: 'rgb(var(--paper-2))', borderRadius: 999 }}>
            <div style={{ width: `${(r.c / max) * 100}%`, height: '100%', background: r.colour, borderRadius: 999, transition: 'width .4s ease-out' }} />
          </div>
          <span className="num" style={{ width: 26, textAlign: 'right', fontSize: 12, fontWeight: 600, color: 'rgb(var(--ink))' }}>{r.c}</span>
        </div>
      ))}
    </div>
  );
}

/* Donut */
function DonutWidget({ w, allTasks }) {
  const rows = React.useMemo(() => {
    if (w.by === 'priority') return [['urgent','Urgent','#EF4444'],['high','High','#F97316'],['medium','Medium','#F59E0B'],['low','Low','#94A3B8']].map(([k,n,c])=>({ name:n, c: allTasks.filter(t=>t.priority===k && t.statusId!==6).length, colour:c }));
    if (w.by === 'space')    return D.spaces.map(s => ({ name: s.name, c: allTasks.filter(t => t.spaceId === s.id && t.statusId !== 6).length, colour: s.colour })).filter(r=>r.c>0);
    return D.statuses.map(s => ({ name: s.title, c: allTasks.filter(t => t.statusId === s.id).length, colour: s.colour })).filter(r=>r.c>0);
  }, [w.by, allTasks]);
  const total = rows.reduce((s, r) => s + r.c, 0) || 1;
  let start = 0;
  const r = 50, cx = 70, cy = 70;
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 18 }}>
      <svg viewBox="0 0 140 140" width="140" height="140" style={{ flexShrink: 0 }}>
        {rows.map((row, i) => {
          const frac = row.c / total;
          const a0 = start * Math.PI * 2 - Math.PI / 2;
          const a1 = (start + frac) * Math.PI * 2 - Math.PI / 2;
          const x0 = cx + r * Math.cos(a0), y0 = cy + r * Math.sin(a0);
          const x1 = cx + r * Math.cos(a1), y1 = cy + r * Math.sin(a1);
          const large = frac > 0.5 ? 1 : 0;
          const d = `M ${cx},${cy} L ${x0},${y0} A ${r},${r} 0 ${large} 1 ${x1},${y1} Z`;
          start += frac;
          return <path key={i} d={d} fill={row.colour} stroke="rgb(var(--surface))" strokeWidth="2" />;
        })}
        <circle cx={cx} cy={cy} r={28} fill="rgb(var(--surface))" />
        <text x={cx} y={cy - 2} textAnchor="middle" style={{ fontSize: 18, fontWeight: 700, fill: 'rgb(var(--ink))' }}>{total}</text>
        <text x={cx} y={cy + 14} textAnchor="middle" style={{ fontSize: 9, fill: 'rgb(var(--muted))', textTransform: 'uppercase', letterSpacing: '0.06em', fontWeight: 600 }}>tasks</text>
      </svg>
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 5 }}>
        {rows.map(row => (
          <div key={row.name} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12 }}>
            <span style={{ width: 9, height: 9, background: row.colour, borderRadius: 3 }} />
            <span style={{ flex: 1, color: 'rgb(var(--ink-2))' }}>{row.name}</span>
            <span className="num" style={{ fontWeight: 600, color: 'rgb(var(--ink))' }}>{row.c}</span>
            <span style={{ fontSize: 10.5, color: 'rgb(var(--muted))', width: 32, textAlign: 'right' }}>{Math.round((row.c/total)*100)}%</span>
          </div>
        ))}
      </div>
    </div>
  );
}

/* Task list */
function TaskListWidget({ w, allTasks }) {
  let list = allTasks;
  if (w.filter === 'mine-today') list = list.filter(t => t.myDay && t.statusId !== 6);
  if (w.filter === 'overdue')    list = list.filter(t => t.dueDate && dayDelta(t.dueDate) < 0 && t.statusId !== 6);
  if (w.filter === 'urgent')     list = list.filter(t => t.priority === 'urgent' && t.statusId !== 6);
  const rank = { urgent: 0, high: 1, medium: 2, low: 3 };
  list = [...list].sort((a, b) => (a.dueDate || '9999').localeCompare(b.dueDate || '9999') || rank[a.priority] - rank[b.priority]).slice(0, 6);
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      {list.map(t => {
        const sp = spaceById(t.spaceId);
        return (
          <div key={t.id} style={{
            display: 'flex', alignItems: 'center', gap: 10,
            padding: '7px 10px',
            background: 'rgb(var(--paper-2) / 0.5)', borderRadius: 8,
          }}>
            <PriorityDot p={t.priority} />
            <span style={{ flex: 1, fontSize: 12.5, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: 'rgb(var(--ink))' }}>{t.title}</span>
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 11, color: 'rgb(var(--muted))' }}>
              <span style={{ width: 5, height: 5, background: sp.colour, borderRadius: 2 }} />{sp.prefix}
            </span>
            {t.dueDate && <DueChip iso={t.dueDate} />}
          </div>
        );
      })}
      {list.length === 0 && <p style={{ fontSize: 12, color: 'rgb(var(--muted))', textAlign: 'center', padding: 12 }}>Nothing here.</p>}
    </div>
  );
}

/* Calendar mini — next 14 days heat strip */
function CalendarWidget({ w, allTasks }) {
  const days = Array.from({ length: 14 }, (_, i) => {
    const d = new Date(TODAY); d.setDate(d.getDate() + i);
    const iso = d.toISOString().slice(0, 10);
    const count = allTasks.filter(t => t.dueDate === iso).length;
    return { d, iso, count };
  });
  const max = Math.max(1, ...days.map(d => d.count));
  return (
    <div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(14, 1fr)', gap: 4 }}>
        {days.map(({ d, iso, count }) => {
          const isToday = d.toDateString() === TODAY.toDateString();
          const intensity = count / max;
          return (
            <div key={iso} title={`${d.toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric', month: 'short' })} · ${count} task${count===1?'':'s'}`}
              style={{
                aspectRatio: '1 / 1.3', borderRadius: 6,
                background: count === 0 ? 'rgb(var(--paper-2))' :
                            `color-mix(in oklab, rgb(var(--accent)) ${20 + intensity * 60}%, rgb(var(--accent-soft)))`,
                border: isToday ? '2px solid rgb(var(--ink))' : '1px solid rgb(var(--rule))',
                display: 'flex', alignItems: 'flex-start', justifyContent: 'center',
                padding: '4px 0', fontSize: 10, fontWeight: 600,
                color: count > 0 ? 'rgb(var(--accent-2))' : 'rgb(var(--muted))',
                fontFamily: 'JetBrains Mono, monospace',
              }}>{d.getDate()}</div>
          );
        })}
      </div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 10, fontSize: 11, color: 'rgb(var(--muted))' }}>
        <span>Next 14 days</span>
        <span style={{ flex: 1 }} />
        <span>Less</span>
        <span style={{ display: 'inline-flex', gap: 2 }}>
          {[0.2, 0.4, 0.6, 0.8].map(p => (
            <span key={p} style={{
              width: 10, height: 10, borderRadius: 3,
              background: `color-mix(in oklab, rgb(var(--accent)) ${p * 80}%, rgb(var(--accent-soft)))`,
            }} />
          ))}
        </span>
        <span>More</span>
      </div>
    </div>
  );
}

/* Activity feed */
function ActivityWidget({ w }) {
  const events = [
    { who: userById(2), what: 'commented on', target: 'INT-2026-00104', at: '11:05' },
    { who: userById(3), what: 'created',      target: 'INT-2026-00105', at: '10:42' },
    { who: userById(1), what: 'moved',        target: 'COMP-2026-00007', at: '10:18', to: 'In progress' },
    { who: userById(4), what: 'logged time on', target: 'BILL-2026-00078', at: '09:55' },
    { who: userById(5), what: 'opened',       target: 'OPS-2026-00012',  at: '09:30' },
    { who: userById(6), what: 'archived',     target: 'CARE-2026-00318', at: 'yesterday' },
  ];
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      {events.map((e, i) => (
        <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
          <Avatar user={e.who} size={22} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 12, color: 'rgb(var(--ink-2))', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
              <strong style={{ color: 'rgb(var(--ink))', fontWeight: 600 }}>{e.who.name.split(' ')[0]}</strong>{' '}{e.what}{' '}
              <span style={{ fontFamily: 'JetBrains Mono, monospace', fontSize: 11, color: 'rgb(var(--accent))' }}>{e.target}</span>
              {e.to ? <> → <span style={{ fontWeight: 600 }}>{e.to}</span></> : null}
            </div>
          </div>
          <span style={{ fontSize: 11, color: 'rgb(var(--muted))' }}>{e.at}</span>
        </div>
      ))}
    </div>
  );
}

/* Time tracker */
function TimeTrackerWidget({ w, allTasks }) {
  const rows = D.users.map(u => {
    const tasks = allTasks.filter(t => t.assignees.includes(u.id));
    const est = tasks.reduce((s, t) => s + (t.estimateMins || 0), 0);
    const log = tasks.reduce((s, t) => s + (t.actualMins || 0), 0);
    return { name: u.name.split(' ')[0], colour: u.colour, est, log };
  }).filter(r => r.est + r.log > 0).sort((a, b) => b.log - a.log).slice(0, 5);
  const max = Math.max(1, ...rows.flatMap(r => [r.est, r.log]));
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      {rows.map(r => (
        <div key={r.name}>
          <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 3 }}>
            <span style={{ fontSize: 12.5, fontWeight: 500, color: 'rgb(var(--ink))' }}>{r.name}</span>
            <span style={{ fontSize: 10.5, color: 'rgb(var(--muted))', fontFamily: 'JetBrains Mono, monospace' }}>{fmtMins(r.log) || '0m'} <span style={{ opacity: 0.7 }}>of {fmtMins(r.est) || '0m'}</span></span>
          </div>
          <div style={{ position: 'relative', height: 6, background: 'rgb(var(--paper-2))', borderRadius: 999 }}>
            <div style={{ position: 'absolute', left: 0, top: 0, bottom: 0, width: `${(r.est/max)*100}%`, background: 'rgb(var(--rule-2))', borderRadius: 999 }} />
            <div style={{ position: 'absolute', left: 0, top: 0, bottom: 0, width: `${(r.log/max)*100}%`, background: r.colour, borderRadius: 999 }} />
          </div>
        </div>
      ))}
    </div>
  );
}

/* AI snapshot — calls Claude */
function AiSnapshotWidget({ w, allTasks }) {
  const [text, setText] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState(null);

  const generate = React.useCallback(async () => {
    setLoading(true); setError(null);
    try {
      const compact = allTasks.slice(0, 25).map(t => ({
        ticket: t.ticket, title: t.title,
        space: spaceById(t.spaceId)?.name,
        status: statusById(t.statusId)?.title,
        priority: t.priority, due: t.dueDate,
        assignees: t.assignees.map(id => userById(id)?.name),
      }));
      const prompts = {
        morning: `Write a short morning briefing for Alex (Lead Clinician) at Harley Mindcare. 2 short paragraphs, total under 80 words. Pull out: the day's headline (most urgent thing or theme), how the team's workload feels, and one specific suggestion ("start with X because Y"). Today is ${TODAY.toISOString().slice(0,10)}. Visible tasks: ${JSON.stringify(compact)}`,
        'weekly-summary': `Write a one-paragraph weekly summary for the practice manager at Harley Mindcare. Cover: volume, top themes, anything notable in compliance/billing/care. Factual, under 80 words. No bullet points. Today: ${TODAY.toISOString().slice(0,10)}. Tasks: ${JSON.stringify(compact)}`,
      };
      const out = await window.claude.complete(prompts[w.prompt] || prompts.morning);
      setText(out);
    } catch (e) {
      setError((e && e.message) || 'Could not reach the model.');
    } finally {
      setLoading(false);
    }
  }, [allTasks, w.prompt]);

  React.useEffect(() => { generate(); }, []); // eslint-disable-line

  return (
    <div style={{ display: 'flex', gap: 14, alignItems: 'flex-start' }}>
      <div style={{
        width: 40, height: 40, borderRadius: 12,
        background: 'linear-gradient(135deg, rgb(var(--accent)), rgb(var(--berry)))',
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
        color: 'white', fontWeight: 700, fontSize: 18, flexShrink: 0,
      }}>✦</div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
          <span style={{ fontSize: 11, fontWeight: 700, color: 'rgb(var(--accent))', letterSpacing: '0.04em', textTransform: 'uppercase' }}>Pulse · live</span>
          <button onClick={generate} disabled={loading} style={{
            marginLeft: 'auto', fontSize: 11, color: 'rgb(var(--muted))',
            padding: '2px 8px', borderRadius: 6,
          }} title="Refresh">{loading ? '…' : '↻'}</button>
        </div>
        {loading && !text && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            <span style={{ height: 12, width: '90%', background: 'rgb(var(--paper-2))', borderRadius: 4, animation: 'pulse-dot 1.4s infinite' }} />
            <span style={{ height: 12, width: '95%', background: 'rgb(var(--paper-2))', borderRadius: 4, animation: 'pulse-dot 1.4s 0.15s infinite' }} />
            <span style={{ height: 12, width: '75%', background: 'rgb(var(--paper-2))', borderRadius: 4, animation: 'pulse-dot 1.4s 0.3s infinite' }} />
          </div>
        )}
        {error && <p style={{ margin: 0, fontSize: 12, color: 'rgb(var(--brick))' }}>{error}</p>}
        {text && (
          <div style={{ fontSize: 13.5, color: 'rgb(var(--ink-2))', lineHeight: 1.6 }}
            dangerouslySetInnerHTML={{ __html: text
              .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
              .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
              .replace(/\n+/g, '<br><br>')
            }} />
        )}
      </div>
    </div>
  );
}

/* Widget library modal */
function WidgetLibrary({ onClose, onAdd }) {
  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, background: 'rgba(15,23,42,0.4)', backdropFilter: 'blur(4px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 220, padding: 40,
    }}>
      <div onClick={e => e.stopPropagation()} className="fadein shadow-pop" style={{
        background: 'rgb(var(--surface))', border: '1px solid rgb(var(--rule))',
        borderRadius: 14, width: 720, maxHeight: '80vh', overflow: 'hidden',
        display: 'flex', flexDirection: 'column',
      }}>
        <div style={{ padding: '16px 22px', borderBottom: '1px solid rgb(var(--rule))', display: 'flex', alignItems: 'center' }}>
          <div>
            <h3 style={{ margin: 0, fontSize: 17, fontWeight: 700 }}>Add a widget</h3>
            <p style={{ margin: '2px 0 0', fontSize: 12, color: 'rgb(var(--muted))' }}>Pick a building block — you can resize and move it after.</p>
          </div>
          <span style={{ flex: 1 }} />
          <button onClick={onClose} style={{ fontSize: 20, color: 'rgb(var(--muted))', padding: '0 6px', borderRadius: 6, lineHeight: 1 }}>×</button>
        </div>
        <div style={{ flex: 1, overflowY: 'auto', padding: 20, display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 12 }}>
          {WIDGET_LIBRARY.map(lib => (
            <button key={lib.type} onClick={() => onAdd(lib.type)} style={{
              textAlign: 'left',
              padding: 14,
              border: '1px solid rgb(var(--rule))',
              borderRadius: 12,
              background: 'rgb(var(--surface))',
              transition: 'border-color .12s, box-shadow .12s, transform .06s',
            }}
              onMouseEnter={e => { e.currentTarget.style.borderColor = 'rgb(var(--accent))'; e.currentTarget.style.boxShadow = '0 0 0 3px rgb(var(--accent) / 0.12)'; }}
              onMouseLeave={e => { e.currentTarget.style.borderColor = 'rgb(var(--rule))'; e.currentTarget.style.boxShadow = 'none'; }}>
              <div style={{
                width: 32, height: 32, borderRadius: 8,
                background: 'rgb(var(--accent-soft))', color: 'rgb(var(--accent))',
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                fontSize: 14, fontWeight: 700, marginBottom: 8,
              }}>{lib.glyph}</div>
              <div style={{ fontSize: 13.5, fontWeight: 700, color: 'rgb(var(--ink))' }}>{lib.name}</div>
              <div style={{ fontSize: 12, color: 'rgb(var(--muted))', marginTop: 3 }}>{lib.desc}</div>
            </button>
          ))}
        </div>
      </div>
    </div>
  );
}

window.DashboardsView = DashboardsView;
