import { Link, useNavigate } from 'react-router-dom'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { useMemo, useState, useEffect, useRef } from 'react'
import {
  BarChart, Bar, AreaChart, Area, Cell, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
} from 'recharts'
import GridLayout, { WidthProvider } from 'react-grid-layout'
import 'react-grid-layout/css/styles.css'
import 'react-resizable/css/styles.css'
import { startOfWeek, format, subWeeks, eachWeekOfInterval, subDays, addDays } from 'date-fns'
import api from '../utils/api'
import { useIsMobile } from '../hooks/useMediaQuery'
import StatCard from '../components/ui/StatCard'
import HrvBadge from '../components/ui/HrvBadge'
import SleepHypnogram from '../components/health/SleepHypnogram'
import ActivityMap from '../components/activity/ActivityMap'
import {
  formatDuration, formatDistance, formatHeartRate, formatElevation,
  formatDate, sportIcon, sportColor, formatSleep,
} from '../utils/format'
import { BB_INFERRED_COLOR, BB_INFERRED_LABEL, bbLevelColor, inferBBType } from '../utils/bodyBattery'

const Grid = WidthProvider(GridLayout)

const MEDALS = { 1: '🥇', 2: '🥈', 3: '🥉' }
const tooltipStyle = { background: '#111827', border: '1px solid #374151', borderRadius: 8, fontSize: 12, color: '#fff' }

// Compact single-stat widgets. val(health, ytdStats) → display string.
const STAT_DEFS = {
  stat_steps:      { label: 'Steps today', accent: 'green',  sub: 'goal 10,000', val: h => h.steps != null ? h.steps.toLocaleString() : '--' },
  stat_resting_hr: { label: 'Resting HR',  accent: 'red',    val: h => formatHeartRate(h.resting_hr) },
  stat_sleep:      { label: 'Sleep',       accent: 'default', val: h => formatSleep(h.sleep_duration_s) },
  stat_vo2max:     { label: 'VO₂ max',     accent: 'blue',   val: h => h.vo2max != null ? h.vo2max.toFixed(1) : '--', sub: h => h.fitness_age != null ? `fitness age ${h.fitness_age}` : undefined },
  stat_hrv:        { label: 'HRV status',  accent: 'purple', val: h => (h.hrv_weekly_avg ?? h.hrv_nightly_avg) != null ? `${Math.round(h.hrv_weekly_avg ?? h.hrv_nightly_avg)} ms` : '--', sub: h => h.hrv_status ? <HrvBadge status={h.hrv_status} /> : undefined },
  stat_running:    { label: 'Running this year', accent: 'blue',   val: (h, y) => y ? `${y.running_km.toFixed(0)} km` : '--' },
  stat_cycling:    { label: 'Cycling this year', accent: 'orange', val: (h, y) => y ? `${y.cycling_km.toFixed(0)} km` : '--' },
  stat_stress:     { label: 'Stress',          accent: 'purple', val: h => h.avg_stress != null ? Math.round(h.avg_stress) : '--' },
  stat_calories:   { label: 'Active calories', accent: 'orange', val: h => h.active_calories != null ? Math.round(h.active_calories).toLocaleString() : '--' },
  stat_floors:     { label: 'Floors climbed',  accent: 'green',  val: h => h.floors_climbed != null ? h.floors_climbed : '--' },
}

// Full widget registry: size defaults + palette label. Stats inherit from STAT_DEFS.
const WIDGETS = {
  ...Object.fromEntries(Object.entries(STAT_DEFS).map(([id, d]) => [id, { label: d.label, w: 2, h: 1, minW: 1, minH: 1 }])),
  weekly:      { label: 'Weekly distance',  w: 6, h: 3, minW: 4, minH: 2 },
  bodyBattery: { label: 'Body Battery',     w: 4, h: 3, minW: 3, minH: 2 },
  vo2maxTrend: { label: 'VO₂ max trend',    w: 3, h: 3, minW: 2, minH: 2 },
  sleepDetail: { label: 'Sleep stages',     w: 5, h: 3, minW: 3, minH: 2 },
  weight:      { label: 'Weight trend',     w: 3, h: 3, minW: 2, minH: 2 },
  featured:    { label: 'Latest activity',  w: 8, h: 5, minW: 4, minH: 3 },
  recent:      { label: 'Recent activities', w: 4, h: 5, minW: 3, minH: 3 },
  prs:         { label: 'Running PRs',      w: 12, h: 2, minW: 4, minH: 2 },
}

// Default arrangement (used for new users and to migrate pre-redesign layouts).
const DEFAULT_LAYOUT = [
  { i: 'stat_steps', x: 0, y: 0, w: 2, h: 1 },
  { i: 'stat_resting_hr', x: 2, y: 0, w: 2, h: 1 },
  { i: 'stat_sleep', x: 4, y: 0, w: 2, h: 1 },
  { i: 'stat_vo2max', x: 6, y: 0, w: 2, h: 1 },
  { i: 'stat_hrv', x: 8, y: 0, w: 2, h: 1 },
  { i: 'stat_running', x: 10, y: 0, w: 2, h: 1 },
  { i: 'weekly', x: 0, y: 1, w: 6, h: 3 },
  { i: 'bodyBattery', x: 6, y: 1, w: 4, h: 3 },
  { i: 'featured', x: 0, y: 4, w: 8, h: 5 },
  { i: 'recent', x: 8, y: 4, w: 4, h: 5 },
  { i: 'prs', x: 0, y: 9, w: 12, h: 2 },
]

const attachMins = (lay) =>
  lay.filter(l => WIDGETS[l.i]).map(l => ({ ...l, minW: WIDGETS[l.i].minW, minH: WIDGETS[l.i].minH }))

function buildLayout(saved) {
  const known = (saved || []).filter(l => WIDGETS[l.i])
  // Migrate old layouts (no stat_* widgets) or empty/missing to the new default.
  const hasStats = known.some(l => l.i.startsWith('stat_'))
  return attachMins(known.length && hasStats ? known : DEFAULT_LAYOUT)
}

// ── Reusable card shell ──────────────────────────────────────────────────────

function Card({ title, viewHref, children, className = '' }) {
  return (
    <div className={`bg-gray-900 rounded-xl border border-gray-800 p-4 h-full flex flex-col ${className}`}>
      {title && (
        <div className="flex items-center justify-between mb-2">
          <h3 className="text-sm font-medium text-gray-300">{title}</h3>
          {viewHref && <Link to={viewHref} className="text-xs text-blue-400 hover:underline">View →</Link>}
        </div>
      )}
      <div className="flex-1 min-h-0">{children}</div>
    </div>
  )
}

function Stat({ label, value }) {
  return (
    <div className="bg-gray-900 px-4 py-3 flex flex-col justify-center">
      <p className="text-xs text-gray-500">{label}</p>
      <p className="text-lg font-semibold text-white">{value}</p>
    </div>
  )
}

// ── Chart widgets ────────────────────────────────────────────────────────────

function BodyBatteryToday({ bb, hires, sleepStart, sleepEnd }) {
  const raw = (hires?.length ? hires : bb?.values || []).map(([ts, level]) => ({ t: ts, level }))
  const sleepStartMs = sleepStart ? new Date(sleepStart).getTime() : null
  const sleepEndMs   = sleepEnd   ? new Date(sleepEnd).getTime()   : null
  const data = raw.map((d, i) => ({
    ...d,
    type: inferBBType(d.t, d.level, i > 0 ? raw[i - 1].level : null, sleepStartMs, sleepEndMs),
  }))
  const charged = bb?.charged, drained = bb?.drained, end_level = bb?.end_level
  const peak = data.length ? Math.max(...data.map(d => d.level)) : end_level
  const hasGraph = data.length >= 2
  const presentTypes = [...new Set(data.map(d => d.type))]

  return (
    <Card title="Body Battery" viewHref="/health">
      <div className="flex flex-col h-full">
        <div className="flex items-baseline gap-3 flex-wrap">
          {peak != null && <span className="text-3xl font-bold" style={{ color: bbLevelColor(peak) }}>{Math.round(peak)}</span>}
          {charged != null && <span className="text-sm font-semibold text-green-400">+{charged}</span>}
          {drained != null && <span className="text-sm font-semibold text-orange-400">-{drained}</span>}
          {end_level != null && <span className="text-xs text-gray-500">now {Math.round(end_level)}</span>}
        </div>
        {hasGraph ? (
          <>
            <div className="flex-1 min-h-0 mt-2">
              <ResponsiveContainer width="100%" height="100%" minHeight={80}>
                <BarChart data={data} margin={{ top: 2, right: 4, bottom: 0, left: 0 }} barCategoryGap={0}>
                  <XAxis dataKey="t" tick={{ fontSize: 9, fill: '#6b7280' }} axisLine={false} tickLine={false}
                    tickFormatter={ts => format(new Date(ts), 'HH:mm')}
                    interval={Math.max(1, Math.floor(data.length / 6))} />
                  <YAxis domain={[0, 100]} tick={{ fontSize: 9, fill: '#6b7280' }} axisLine={false} tickLine={false}
                    width={26} ticks={[0, 50, 100]} />
                  <Tooltip contentStyle={tooltipStyle} itemStyle={{ color: '#fff' }} labelStyle={{ color: '#fff' }}
                    labelFormatter={ts => format(new Date(ts), 'HH:mm')} formatter={v => [`${Math.round(v)}%`, 'Battery']} />
                  <Bar dataKey="level" isAnimationActive={false} radius={0}>
                    {data.map((d, i) => <Cell key={i} fill={BB_INFERRED_COLOR[d.type]} />)}
                  </Bar>
                </BarChart>
              </ResponsiveContainer>
            </div>
            <div className="flex flex-wrap gap-x-3 gap-y-1 mt-2">
              {presentTypes.map(type => (
                <div key={type} className="flex items-center gap-1">
                  <div className="w-2 h-2 rounded-sm" style={{ backgroundColor: BB_INFERRED_COLOR[type] }} />
                  <span className="text-xs text-gray-500">{BB_INFERRED_LABEL[type]}</span>
                </div>
              ))}
            </div>
          </>
        ) : (
          <p className="text-xs text-gray-600 mt-3">No body battery data today</p>
        )}
      </div>
    </Card>
  )
}

function Sparkline({ data, dataKey, color, gradId, fmt }) {
  return (
    <ResponsiveContainer width="100%" height="100%" minHeight={60}>
      <AreaChart data={data} margin={{ top: 4, right: 2, bottom: 0, left: 0 }}>
        <defs>
          <linearGradient id={gradId} x1="0" y1="0" x2="0" y2="1">
            <stop offset="5%" stopColor={color} stopOpacity={0.3} />
            <stop offset="95%" stopColor={color} stopOpacity={0} />
          </linearGradient>
        </defs>
        <YAxis domain={['dataMin - 1', 'dataMax + 1']} hide />
        <Tooltip contentStyle={tooltipStyle} labelFormatter={d => format(new Date(d), 'MMM d')}
          formatter={v => [fmt ? fmt(v) : v, '']} />
        <Area type="monotone" dataKey={dataKey} stroke={color} strokeWidth={2} fill={`url(#${gradId})`}
          dot={false} connectNulls isAnimationActive={false} />
      </AreaChart>
    </ResponsiveContainer>
  )
}

function Vo2MaxTrend({ health, recentHealth }) {
  const series = useMemo(
    () => [...(recentHealth || [])].filter(d => d.vo2max != null)
      .sort((a, b) => new Date(a.date) - new Date(b.date))
      .map(d => ({ date: d.date, v: d.vo2max })),
    [recentHealth],
  )
  return (
    <Card title="VO₂ Max" viewHref="/health">
      <div className="flex flex-col h-full">
        <div className="flex items-baseline gap-2">
          <span className="text-3xl font-bold text-blue-400">{health.vo2max != null ? health.vo2max.toFixed(1) : '--'}</span>
          <span className="text-xs text-gray-500">ml/kg/min</span>
        </div>
        {health.fitness_age != null && <p className="text-xs text-gray-500 mt-0.5">Fitness age {health.fitness_age}</p>}
        <div className="flex-1 min-h-0 mt-2">
          {series.length >= 2
            ? <Sparkline data={series} dataKey="v" color="#3b82f6" gradId="grad-dash-vo2" fmt={v => v.toFixed(1)} />
            : <p className="text-xs text-gray-600">Not enough history</p>}
        </div>
      </div>
    </Card>
  )
}

function WeightMini({ recentHealth }) {
  const series = useMemo(
    () => [...(recentHealth || [])].filter(d => d.weight_kg != null)
      .sort((a, b) => new Date(a.date) - new Date(b.date))
      .map(d => ({ date: d.date, w: +d.weight_kg.toFixed(2) })),
    [recentHealth],
  )
  const latest = series.length ? series[series.length - 1].w : null
  return (
    <Card title="Weight" viewHref="/health">
      <div className="flex flex-col h-full">
        <div className="flex items-baseline gap-2">
          <span className="text-3xl font-bold text-emerald-300">{latest != null ? latest.toFixed(1) : '--'}</span>
          <span className="text-xs text-gray-500">kg</span>
        </div>
        <div className="flex-1 min-h-0 mt-2">
          {series.length >= 2
            ? <Sparkline data={series} dataKey="w" color="#34d399" gradId="grad-dash-weight" fmt={v => `${v.toFixed(1)} kg`} />
            : <p className="text-xs text-gray-600">Not enough history</p>}
        </div>
      </div>
    </Card>
  )
}

const SLEEP_STAGES = [
  { key: 'sleep_deep_s',  label: 'Deep',  color: '#3b82f6' },
  { key: 'sleep_rem_s',   label: 'REM',   color: '#8b5cf6' },
  { key: 'sleep_light_s', label: 'Light', color: '#60a5fa' },
  { key: 'sleep_awake_s', label: 'Awake', color: '#6b7280' },
]

function SleepDetail({ health, sleepStages }) {
  const total = SLEEP_STAGES.reduce((s, st) => s + (health[st.key] || 0), 0)
  const hasHypnogram = health.sleep_start && health.sleep_end && sleepStages?.length
  return (
    <Card title="Sleep" viewHref="/health">
      <div className="flex flex-col h-full">
        <div className="flex items-baseline gap-3 flex-wrap">
          <span className="text-3xl font-bold text-indigo-300">{formatSleep(health.sleep_duration_s)}</span>
          {health.sleep_score != null && (
            <span className="text-sm text-gray-400">score <span className="text-white font-semibold">{Math.round(health.sleep_score)}</span></span>
          )}
        </div>
        {total > 0 ? (
          <>
            <div className="flex h-3 rounded-full overflow-hidden gap-0.5 mt-3">
              {SLEEP_STAGES.map(st => {
                const pct = ((health[st.key] || 0) / total) * 100
                if (pct < 0.5) return null
                return <div key={st.key} style={{ width: `${pct}%`, backgroundColor: st.color }} title={`${st.label}: ${formatSleep(health[st.key])}`} />
              })}
            </div>
            <div className="flex flex-wrap gap-x-3 gap-y-1 mt-2">
              {SLEEP_STAGES.map(st => (health[st.key] ? (
                <div key={st.key} className="flex items-center gap-1.5">
                  <div className="w-2.5 h-2.5 rounded-sm" style={{ backgroundColor: st.color }} />
                  <span className="text-xs text-gray-400">{st.label}</span>
                  <span className="text-xs text-white">{formatSleep(health[st.key])}</span>
                </div>
              ) : null))}
            </div>
            {hasHypnogram && (
              <div className="flex-1 flex items-center mt-4">
                <div className="w-full">
                  <SleepHypnogram sleepStart={health.sleep_start} sleepEnd={health.sleep_end} stages={sleepStages} />
                </div>
              </div>
            )}
          </>
        ) : (
          <p className="text-xs text-gray-600 mt-3">No sleep stages for last night</p>
        )}
      </div>
    </Card>
  )
}

const sportLabel = s => (s ? s.charAt(0).toUpperCase() + s.slice(1) : 'Other')

function WeeklyChart({ activities }) {
  const navigate = useNavigate()
  const { data, sports } = useMemo(() => {
    if (!activities?.length) return { data: [], sports: [] }
    // Sports present, ordered by total distance (largest stacks at the bottom).
    const totals = {}
    for (const a of activities) totals[a.sport_type] = (totals[a.sport_type] || 0) + (a.distance_m || 0)
    const sports = Object.keys(totals).sort((x, y) => totals[y] - totals[x])
    const now = new Date()
    const weeks = eachWeekOfInterval({ start: subWeeks(startOfWeek(now), 7), end: startOfWeek(now) })
    const data = weeks.map(weekStart => {
      const weekEnd = addDays(weekStart, 7)
      const row = { week: format(weekStart, 'MMM d'), weekStartISO: format(weekStart, 'yyyy-MM-dd'), weekEndISO: format(weekEnd, 'yyyy-MM-dd') }
      for (const s of sports) row[s] = 0
      for (const a of activities) {
        const t = new Date(a.start_time)
        if (t >= weekStart && t < weekEnd) row[a.sport_type] += (a.distance_m || 0) / 1000
      }
      for (const s of sports) row[s] = +row[s].toFixed(2)
      return row
    })
    return { data, sports }
  }, [activities])

  return (
    <Card title="Weekly distance (km)">
      {data.length ? (
        <div className="flex flex-col h-full">
          <div className="flex-1 min-h-0">
            <ResponsiveContainer width="100%" height="100%" minHeight={100}>
              <BarChart data={data} margin={{ top: 4, right: 4, bottom: 4, left: 0 }} barSize={20}
                onClick={e => { const p = e?.activePayload?.[0]?.payload; if (p) navigate(`/activities?from=${p.weekStartISO}&to=${p.weekEndISO}`) }}
                style={{ cursor: 'pointer' }}>
                <CartesianGrid strokeDasharray="3 3" stroke="#1f2937" vertical={false} />
                <XAxis dataKey="week" tick={{ fontSize: 10, fill: '#6b7280' }} axisLine={false} tickLine={false} />
                <YAxis tick={{ fontSize: 10, fill: '#6b7280' }} axisLine={false} tickLine={false} width={28} tickFormatter={v => `${v.toFixed(0)}`} />
                <Tooltip contentStyle={tooltipStyle} cursor={{ fill: 'rgba(255,255,255,0.06)' }}
                  formatter={(v, name) => [`${(+v).toFixed(1)} km`, sportLabel(name)]} />
                {sports.map((s, i) => (
                  <Bar key={s} dataKey={s} stackId="dist" fill={sportColor(s)} isAnimationActive={false}
                    radius={i === sports.length - 1 ? [3, 3, 0, 0] : [0, 0, 0, 0]} />
                ))}
              </BarChart>
            </ResponsiveContainer>
          </div>
          <div className="flex flex-wrap gap-x-3 gap-y-1 mt-2">
            {sports.map(s => (
              <div key={s} className="flex items-center gap-1.5">
                <div className="w-2.5 h-2.5 rounded-sm" style={{ backgroundColor: sportColor(s) }} />
                <span className="text-xs text-gray-400">{sportLabel(s)}</span>
              </div>
            ))}
          </div>
        </div>
      ) : (
        <div className="flex items-center justify-center h-full text-gray-600 text-sm">No activities yet</div>
      )}
    </Card>
  )
}

function FeaturedActivity({ activity, segments }) {
  if (!activity) return (
    <Card title="Latest activity"><div className="flex items-center justify-center h-full text-gray-600 text-sm">No activities yet</div></Card>
  )
  return (
    <div className="bg-gray-900 rounded-xl border border-gray-800 overflow-hidden h-full flex flex-col">
      <div className="flex items-center justify-between px-4 py-3 border-b border-gray-800">
        <div className="flex items-center gap-2 min-w-0">
          <span className="text-xl">{sportIcon(activity.sport_type)}</span>
          <div className="min-w-0">
            <Link to={`/activities/${activity.id}`} className="text-sm font-semibold text-white hover:text-blue-400 transition-colors truncate block">{activity.name}</Link>
            <p className="text-xs text-gray-500">{formatDate(activity.start_time)}</p>
          </div>
        </div>
        <Link to={`/activities/${activity.id}`} className="text-xs text-blue-400 hover:underline flex-shrink-0">Open →</Link>
      </div>
      <div className="grid grid-cols-1 lg:grid-cols-3 flex-1 min-h-0">
        <div className="lg:col-span-2 min-h-[180px] bg-gray-950">
          {activity.polyline
            ? <ActivityMap polyline={activity.polyline} sportType={activity.sport_type} colorMode="solid" mapType="dark" />
            : <div className="flex items-center justify-center h-full text-gray-600 text-sm">No GPS track</div>}
        </div>
        <div className="grid grid-cols-2 lg:grid-cols-1 gap-px bg-gray-800/50 content-start">
          <Stat label="Distance" value={formatDistance(activity.distance_m)} />
          <Stat label="Elevation ↑" value={formatElevation(activity.elevation_gain_m)} />
          <Stat label="Moving time" value={formatDuration(activity.moving_time_s ?? activity.duration_s)} />
          <Stat label="Calories" value={activity.calories ? `${Math.round(activity.calories)} kcal` : '--'} />
        </div>
      </div>
      {segments?.length > 0 && (
        <div className="border-t border-gray-800 px-4 py-3">
          <div className="flex items-center justify-between mb-2">
            <h4 className="text-xs font-medium text-gray-400 uppercase tracking-wide">Segments</h4>
            <Link to={`/activities/${activity.id}`} className="text-xs text-blue-400 hover:underline">Details →</Link>
          </div>
          <div className="grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-1.5">
            {segments.map(seg => {
              const isPodium = seg.rank && seg.rank <= 3
              const delta = seg.best_s != null ? seg.duration_s - seg.best_s : null
              return (
                <div key={seg.segment_id} className="flex items-center gap-2 text-sm">
                  <span className="flex-1 text-gray-300 text-xs truncate">{seg.name}</span>
                  <span className={`font-mono text-xs ${isPodium ? 'text-yellow-400 font-semibold' : 'text-gray-200'}`}>{formatDuration(seg.duration_s)}</span>
                  <span className="w-8 text-right text-xs">
                    {isPodium ? <span title={`#${seg.rank} of ${seg.effort_count}`}>{MEDALS[seg.rank]}</span>
                      : delta != null ? <span className="text-red-400 font-mono">+{formatDuration(delta)}</span>
                      : <span className="text-gray-700">--</span>}
                  </span>
                </div>
              )
            })}
          </div>
        </div>
      )}
    </div>
  )
}

function RecentActivities({ activities }) {
  return (
    <Card title="Recent activities" viewHref="/activities">
      <div className="space-y-2 overflow-auto h-full">
        {activities?.slice(0, 6).map(activity => (
          <Link key={activity.id} to={`/activities/${activity.id}`}
            className="flex items-center gap-3 py-2 border-b border-gray-800/50 hover:bg-gray-800/30 rounded-lg px-2 -mx-2 transition-colors">
            <span className="text-lg">{sportIcon(activity.sport_type)}</span>
            <div className="flex-1 min-w-0">
              <p className="text-sm font-medium text-white truncate">{activity.name}</p>
              {activity.named_route_name && (
                <p className="text-xs text-blue-400 truncate">📍 {activity.named_route_name}</p>
              )}
              <p className="text-xs text-gray-500">{formatDate(activity.start_time)}</p>
            </div>
            <div className="text-right text-sm">
              <p className="text-gray-200">{formatDistance(activity.distance_m)}</p>
              <p className="text-xs text-red-400">{formatHeartRate(activity.avg_heart_rate)}</p>
            </div>
          </Link>
        ))}
        {!activities?.length && (
          <p className="text-gray-600 text-sm text-center py-8">No activities yet — <Link to="/upload" className="text-blue-400 hover:underline">import some data</Link></p>
        )}
      </div>
    </Card>
  )
}

const DASH_PR_LABELS = ['1k', '1 mile', '5k', '10k']

function RunningPRs({ records }) {
  const byLabel = Object.fromEntries((records || []).map(r => [r.distance_label, r]))
  return (
    <Card title="Running PRs" viewHref="/records">
      <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
        {DASH_PR_LABELS.map(label => {
          const rec = byLabel[label]
          return (
            <div key={label} className="bg-gray-800/60 rounded-lg p-3 text-center">
              <p className="text-xs text-gray-500 mb-1">{label}</p>
              <p className="font-mono font-semibold text-yellow-400">{rec ? formatDuration(rec.duration_s) : '--'}</p>
            </div>
          )
        })}
      </div>
    </Card>
  )
}

// ── Page ───────────────────────────────────────────────────────────────────

export default function DashboardPage() {
  const { data: recentActivities } = useQuery({
    queryKey: ['activities-recent'],
    queryFn: () => api.get('/activities/', { params: { per_page: 10 } }).then(r => r.data),
  })
  const { data: allActivities } = useQuery({
    queryKey: ['activities-all-chart'],
    queryFn: () => api.get('/activities/', { params: { per_page: 100, from_date: subDays(new Date(), 60).toISOString() } }).then(r => r.data),
  })
  const { data: recentHealth } = useQuery({
    queryKey: ['health-metrics', 'dash'],
    queryFn: () => api.get('/health-metrics/', { params: { limit: 365 } }).then(r => r.data),
  })
  const { data: profile } = useQuery({
    queryKey: ['profile'],
    queryFn: () => api.get('/profile/').then(r => r.data),
  })

  const health = useMemo(() => {
    const rows = [...(recentHealth || [])].sort((a, b) => new Date(b.date) - new Date(a.date))
    const pick = f => rows.find(d => d[f] != null)?.[f] ?? null
    const latest = rows[0] || {}
    return {
      date: rows[0]?.date ? rows[0].date.slice(0, 10) : null,
      resting_hr: pick('resting_hr'),
      sleep_duration_s: pick('sleep_duration_s'),
      sleep_start: latest.sleep_start ?? null,
      sleep_end: latest.sleep_end ?? null,
      sleep_deep_s: latest.sleep_deep_s ?? null,
      sleep_rem_s: latest.sleep_rem_s ?? null,
      sleep_light_s: latest.sleep_light_s ?? null,
      sleep_awake_s: latest.sleep_awake_s ?? null,
      sleep_score: pick('sleep_score'),
      hrv_nightly_avg: pick('hrv_nightly_avg'),
      hrv_weekly_avg: pick('hrv_weekly_avg'),
      hrv_status: pick('hrv_status'),
      steps: pick('steps'),
      vo2max: pick('vo2max'),
      fitness_age: pick('fitness_age'),
      avg_stress: pick('avg_stress'),
      active_calories: pick('active_calories'),
      floors_climbed: pick('floors_climbed'),
    }
  }, [recentHealth])

  const { data: intraday } = useQuery({
    queryKey: ['health-intraday-dash', health.date],
    queryFn: () => api.get('/health-metrics/intraday', { params: { date: health.date } }).then(r => r.data),
    enabled: !!health.date,
  })
  const { data: records } = useQuery({
    queryKey: ['records-running'],
    queryFn: () => api.get('/records/', { params: { sport_type: 'running' } }).then(r => r.data),
  })
  const { data: ytdStats } = useQuery({
    queryKey: ['ytd-stats'],
    queryFn: () => api.get('/activities/stats/ytd').then(r => r.data),
  })
  const featured = recentActivities?.[0]
  const { data: featuredSegments } = useQuery({
    queryKey: ['activity-segments', featured?.id],
    queryFn: () => api.get(`/segments/by-activity/${featured.id}`).then(r => r.data),
    enabled: !!featured?.id,
  })

  // ── Layout state ──────────────────────────────────────────────────────────
  const isMobile = useIsMobile()
  const [editMode, setEditMode] = useState(false)
  const [addOpen, setAddOpen] = useState(false)
  const [layout, setLayout] = useState(() => buildLayout(null))
  const saveTimer = useRef(null)
  const loadedRef = useRef(false)

  useEffect(() => {
    if (profile && !loadedRef.current) {
      loadedRef.current = true
      setLayout(buildLayout(profile.dashboard_layout))
    }
  }, [profile])

  const qc = useQueryClient()
  const stripLayout = (lay) => lay.map(({ i, x, y, w, h }) => ({ i, x, y, w, h }))
  const saveLayout = useMutation({
    mutationFn: (lay) => api.put('/profile/dashboard-layout', { layout: stripLayout(lay) }),
    // Keep the cached profile in sync so re-mounting the page doesn't revert the layout.
    onSuccess: (_d, lay) => qc.setQueryData(['profile'], p => (p ? { ...p, dashboard_layout: stripLayout(lay) } : p)),
  })

  const persist = (lay) => { clearTimeout(saveTimer.current); saveLayout.mutate(lay) }

  const handleLayoutChange = (next) => {
    const withMins = attachMins(next)
    setLayout(withMins)
    if (editMode) {
      clearTimeout(saveTimer.current)
      saveTimer.current = setTimeout(() => saveLayout.mutate(withMins), 700)
    }
  }

  const addWidget = (id) => {
    if (layout.some(l => l.i === id)) { setAddOpen(false); return }
    const maxY = layout.reduce((m, l) => Math.max(m, l.y + l.h), 0)
    const def = WIDGETS[id]
    const next = attachMins([...layout, { i: id, x: 0, y: maxY, w: def.w, h: def.h }])
    setLayout(next); persist(next); setAddOpen(false)
  }
  const removeWidget = (id) => { const next = layout.filter(l => l.i !== id); setLayout(next); persist(next) }

  // Editing is desktop-only; drop out of edit mode if the viewport shrinks mid-edit.
  useEffect(() => {
    if (isMobile && editMode) { setEditMode(false); setAddOpen(false) }
  }, [isMobile])

  const finishEditing = () => { persist(layout); setEditMode(false); setAddOpen(false) }
  const resetLayout = () => { const def = attachMins(DEFAULT_LAYOUT); setLayout(def); persist(def) }

  const renderWidget = (id) => {
    if (STAT_DEFS[id]) {
      const d = STAT_DEFS[id]
      return <StatCard label={d.label} accent={d.accent} value={d.val(health, ytdStats)}
        sub={typeof d.sub === 'function' ? d.sub(health) : d.sub} />
    }
    switch (id) {
      case 'weekly':      return <WeeklyChart activities={allActivities} />
      case 'bodyBattery': return <BodyBatteryToday bb={intraday?.body_battery} hires={intraday?.body_battery_hires} sleepStart={health.sleep_start} sleepEnd={health.sleep_end} />
      case 'vo2maxTrend': return <Vo2MaxTrend health={health} recentHealth={recentHealth} />
      case 'sleepDetail': return <SleepDetail health={health} sleepStages={intraday?.sleep_stages} />
      case 'weight':      return <WeightMini recentHealth={recentHealth} />
      case 'featured':    return <FeaturedActivity activity={featured} segments={featuredSegments} />
      case 'recent':      return <RecentActivities activities={recentActivities} />
      case 'prs':         return <RunningPRs records={records} />
      default:            return null
    }
  }

  const presentIds = new Set(layout.map(l => l.i))
  const available = Object.keys(WIDGETS).filter(id => !presentIds.has(id))

  // Single-column stack for phones: saved desktop layout read top-to-bottom,
  // left-to-right; consecutive stat cards pair up into a 2-column grid.
  const renderMobileStack = () => {
    const sorted = [...layout].filter(l => WIDGETS[l.i]).sort((a, b) => a.y - b.y || a.x - b.x)
    const groups = []
    for (const l of sorted) {
      const last = groups[groups.length - 1]
      if (STAT_DEFS[l.i] && last?.stats) last.items.push(l)
      else groups.push({ stats: !!STAT_DEFS[l.i], items: [l] })
    }
    // Content-driven widgets size themselves; chart widgets need the explicit
    // height the grid normally provides (rowHeight=80, margin=16) or their
    // ResponsiveContainers collapse.
    const autoHeight = new Set(['sleepDetail', 'prs'])
    return (
      <div className="space-y-4">
        {groups.map((g, idx) =>
          g.stats ? (
            <div key={idx} className="grid grid-cols-2 gap-3">
              {g.items.map(l => <div key={l.i}>{renderWidget(l.i)}</div>)}
            </div>
          ) : (
            <div key={g.items[0].i}
              style={autoHeight.has(g.items[0].i) ? undefined : { height: g.items[0].h * 80 + (g.items[0].h - 1) * 16 }}>
              {renderWidget(g.items[0].i)}
            </div>
          )
        )}
      </div>
    )
  }

  return (
    <div className="p-4 md:p-6">
      <div className="flex items-center justify-between mb-4">
        <h1 className="text-2xl font-bold text-white">Dashboard</h1>
        <div className="flex items-center gap-3">
          {editMode && (
            <div className="relative">
              <button onClick={() => setAddOpen(o => !o)}
                className="text-sm font-medium px-3 py-1.5 rounded-lg bg-gray-800 hover:bg-gray-700 text-gray-200 transition-colors">
                + Add widget
              </button>
              {addOpen && (
                <div className="absolute right-0 mt-1 w-56 max-h-80 overflow-auto bg-gray-900 border border-gray-700 rounded-lg shadow-xl z-50 py-1">
                  {available.length === 0
                    ? <p className="text-xs text-gray-500 px-3 py-2">All widgets are on the dashboard</p>
                    : available.map(id => (
                      <button key={id} onClick={() => addWidget(id)}
                        className="block w-full text-left text-sm text-gray-300 hover:bg-gray-800 px-3 py-1.5 transition-colors">
                        {WIDGETS[id].label}
                      </button>
                    ))}
                </div>
              )}
            </div>
          )}
          {editMode && (
            <button onClick={resetLayout} className="text-xs text-gray-400 hover:text-white transition-colors">Reset layout</button>
          )}
          {!isMobile && (
            <button
              onClick={() => (editMode ? finishEditing() : setEditMode(true))}
              className={`text-sm font-medium px-3 py-1.5 rounded-lg transition-colors ${
                editMode ? 'bg-blue-600 hover:bg-blue-500 text-white' : 'bg-gray-800 hover:bg-gray-700 text-gray-200'
              }`}>
              {editMode ? '✓ Done' : '✎ Edit dashboard'}
            </button>
          )}
          <Link to="/upload" className="text-sm text-blue-400 hover:text-blue-300 transition-colors">+ Import data</Link>
        </div>
      </div>

      {editMode && (
        <p className="text-xs text-gray-500 mb-3">Drag to move, drag a corner to resize, or remove a widget with ✕. Add widgets from the menu. Changes save automatically.</p>
      )}

      {isMobile ? renderMobileStack() : (
      <Grid
        className="layout"
        layout={layout}
        cols={12}
        rowHeight={80}
        margin={[16, 16]}
        isDraggable={editMode}
        isResizable={editMode}
        onLayoutChange={handleLayoutChange}
        compactType="vertical"
        draggableCancel=".widget-delete"
      >
        {layout.filter(l => WIDGETS[l.i]).map(l => (
          <div key={l.i} className={`rounded-xl relative ${editMode ? 'ring-2 ring-blue-500/40 cursor-move' : ''}`}>
            {editMode && (
              <button onClick={() => removeWidget(l.i)}
                className="widget-delete absolute -top-2 -right-2 z-20 w-6 h-6 flex items-center justify-center rounded-full bg-red-600 hover:bg-red-500 text-white text-xs shadow-lg"
                title="Remove widget">✕</button>
            )}
            <div className={`h-full ${editMode ? 'pointer-events-none select-none' : ''}`}>
              {renderWidget(l.i)}
            </div>
          </div>
        ))}
      </Grid>
      )}
    </div>
  )
}
