<script lang="ts">
  import { onMount } from 'svelte';
  import type { AthleteJson, BASIndex, ActivitySummary } from '../lib/types';
  import MmpChart from './MmpChart.svelte';
  import RecordsView from './RecordsView.svelte';
  import AthleteDrawer from './AthleteDrawer.svelte';
  import Explore from './Explore.svelte';
  import NerdCorner from './NerdCorner.svelte';
  import { isUnlisted, formatElapsed, formatDistance, sportIcon } from '../lib/format';
  import { loadIndex, loadAthlete } from '../lib/dataloader';

  export let base: string = '/';
  /** Explicit index URL for multi-user per-user pages (user's shard). */
  export let indexUrl: string = '';
  /** Explicit athlete.json URL for multi-user per-user pages. */
  export let athleteUrl: string = '';
  /** Handle whose segment summary to show. Parsed from athleteUrl if blank. */
  export let handle: string = '';

  let athlete: AthleteJson | null = null;
  let activities:    ActivitySummary[] = [];
  let allActivities: ActivitySummary[] = [];
  let loading = true;
  let error: string | null = null;
  let drawerOpen = false;

  type Tab = 'power' | 'records' | 'segments' | 'profile' | 'explore' | 'nerd' | 'gear';
  let activeTab: Tab = 'power';
  let mounted = false;
  let isOwner = false;

  interface SegmentSummaryItem {
    segment: { id: string; name: string; sport: string | null; distance_m: number };
    best_elapsed_s: number;
    best_activity_id: string;
    effort_count: number;
  }
  interface SegmentEffort {
    activity_id: string;
    started_at: string;
    elapsed_s: number;
  }
  let segmentSummary: SegmentSummaryItem[] = [];
  let segmentsLoading = false;
  let segmentsFetched = false;
  let segmentsHandle = '';
  let rescanning = false;
  let rescanMsg: string | null = null;
  let expandedId: string | null = null;
  let effortsBySegment: Record<string, SegmentEffort[]> = {};
  let loadingEfforts: Record<string, boolean> = {};

  // Gear tab state
  interface GearReplacement { id: string; date: string; note?: string }
  interface GearPart { id: string; name: string; threshold_km?: number; replacements: GearReplacement[] }
  interface GearItem { id: string; name: string; type: string; retired: boolean; strava_id?: string; weight_g?: number; parts?: GearPart[] }

  let gearItems: GearItem[] = [];
  let gearLoading = false;
  let gearFetched = false;
  let gearAddName = '';
  let gearAddType = 'bike';
  let gearAdding = false;
  let gearError: string | null = null;
  let gearEditId: string | null = null;
  let gearEditName = '';
  let gearEditType = 'bike';
  let gearEditRetired = false;
  let gearEditWeightG: number | null = null;
  let gearSaving = false;
  let gearExpandedId: string | null = null;

  // Part add form state (keyed by gear item id)
  let partAddName: Record<string, string> = {};
  let partAddThreshold: Record<string, string> = {};
  let partAdding: Record<string, boolean> = {};
  let partEditId: string | null = null;   // "gearId/partId"
  let partEditName = '';
  let partEditThreshold = '';
  let partSaving = false;
  let replacingPartId: string | null = null; // "gearId/partId"
  let replacementNote = '';
  let replacementDate = '';

  const GEAR_ICONS: Record<string, string> = { bike: '🚲', shoes: '👟', skis: '🎿', other: '⚙️' };

  function exportGearCsv() {
    const esc = (v: unknown) => `"${String(v ?? '').replace(/"/g, '""')}"`;
    const rows: string[] = [
      ['gear_name', 'gear_type', 'gear_distance_km', 'part_name', 'part_threshold_km',
       'part_distance_since_last_km', 'replacement_date', 'replacement_note'].join(',')
    ];
    for (const item of gearItems) {
      const gearKm = ((gearDistances[item.name] ?? 0) / 1000).toFixed(1);
      const parts = item.parts ?? [];
      if (parts.length === 0) {
        rows.push([esc(item.name), esc(item.type), gearKm, '', '', '', '', ''].join(','));
        continue;
      }
      for (const part of parts) {
        const partKm = partDistanceKm(item.name, part).toFixed(1);
        const reps = part.replacements.slice().reverse();
        if (reps.length === 0) {
          rows.push([esc(item.name), esc(item.type), gearKm, esc(part.name),
            part.threshold_km ?? '', partKm, '', ''].join(','));
        } else {
          for (const rep of reps) {
            rows.push([esc(item.name), esc(item.type), gearKm, esc(part.name),
              part.threshold_km ?? '', partKm, rep.date, esc(rep.note ?? '')].join(','));
          }
        }
      }
    }
    const blob = new Blob([rows.join('\n')], { type: 'text/csv' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = 'gear.csv';
    a.click();
    URL.revokeObjectURL(a.href);
  }

  function fmtDate(iso: string): string {
    const [y, m, d] = iso.split('-');
    return `${d}/${m}/${y}`;
  }

  const PART_SUGGESTIONS: Record<string, { name: string; threshold_km: number }> = {
    bike:  { name: 'chain',         threshold_km: 3000 },
    shoes: { name: 'running shoes', threshold_km: 750  },
  };

  const PART_DEFAULTS: Record<string, number> = {
    chain: 3000, cassette: 8000,
    'tyre front': 4500, 'tyre rear': 3500,
    'brake pads': 3000, 'running shoes': 750,
  };

  async function gearFetch() {
    if (gearFetched) return;
    gearLoading = true; gearFetched = true;
    try {
      const r = await fetch('/api/gear', { credentials: 'include' });
      if (r.ok) gearItems = (await r.json()).items ?? [];
    } catch { /* ignore */ }
    gearLoading = false;
  }

  async function gearAdd() {
    if (!gearAddName.trim()) return;
    gearAdding = true; gearError = null;
    try {
      const r = await fetch('/api/gear', {
        method: 'POST', credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: gearAddName.trim(), type: gearAddType }),
      });
      const d = await r.json();
      if (r.ok) { gearItems = [...gearItems, d.item]; gearAddName = ''; }
      else gearError = d.detail ?? 'Could not add gear.';
    } catch { gearError = 'Could not reach server.'; }
    gearAdding = false;
  }

  function gearStartEdit(item: GearItem) {
    gearEditId = item.id; gearEditName = item.name; gearEditType = item.type; gearEditRetired = item.retired; gearEditWeightG = item.weight_g ?? null;
  }

  async function gearSaveEdit() {
    if (!gearEditId || !gearEditName.trim()) return;
    gearSaving = true; gearError = null;
    try {
      const r = await fetch(`/api/gear/${gearEditId}`, {
        method: 'PATCH', credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: gearEditName.trim(), type: gearEditType, retired: gearEditRetired, weight_g: gearEditWeightG }),
      });
      const d = await r.json();
      if (r.ok) {
        gearItems = gearItems.map(g => g.id === gearEditId ? d.item : g);
        gearEditId = null;
      } else gearError = d.detail ?? 'Could not save.';
    } catch { gearError = 'Could not reach server.'; }
    gearSaving = false;
  }

  async function gearDelete(id: string) {
    gearError = null;
    try {
      const r = await fetch(`/api/gear/${id}`, { method: 'DELETE', credentials: 'include' });
      if (r.ok) { gearItems = gearItems.filter(g => g.id !== id); if (gearExpandedId === id) gearExpandedId = null; }
      else { const d = await r.json(); gearError = d.detail ?? 'Could not delete.'; }
    } catch { gearError = 'Could not reach server.'; }
  }

  function gearToggle(id: string) {
    gearExpandedId = gearExpandedId === id ? null : id;
  }

  // ── Parts ──────────────────────────────────────────────────────────────────

  function partDistanceKm(gearName: string, part: GearPart): number {
    const lastRep = part.replacements.at(-1);
    if (!lastRep) return (gearDistances[gearName] ?? 0) / 1000;
    return allActivities
      .filter(a => a.gear === gearName && a.started_at > lastRep.date)
      .reduce((s, a) => s + (a.distance_m ?? 0), 0) / 1000;
  }

  function partColor(km: number, threshold: number | undefined): string {
    if (!threshold) return 'text-zinc-400';
    const pct = km / threshold;
    if (pct >= 1)   return 'text-red-400';
    if (pct >= 0.7) return 'text-yellow-400';
    return 'text-green-400';
  }

  function partDots(item: GearItem): string[] {
    return (item.parts ?? []).map(p => {
      const km = partDistanceKm(item.name, p);
      return partColor(km, p.threshold_km).replace('text-', '');
    });
  }

  async function partAddSuggestion(item: GearItem) {
    const s = PART_SUGGESTIONS[item.type];
    if (!s) return;
    await partAdd(item, s.name, s.threshold_km);
  }

  async function partAdd(item: GearItem, nameOverride?: string, thresholdOverride?: number) {
    const gid = item.id;
    const name = (nameOverride ?? partAddName[gid] ?? '').trim();
    if (!name) return;
    const rawThreshold = thresholdOverride ?? (partAddThreshold[gid] ? parseFloat(partAddThreshold[gid]) : PART_DEFAULTS[name.toLowerCase()]);
    partAdding = { ...partAdding, [gid]: true };
    try {
      const body: Record<string, unknown> = { name };
      if (rawThreshold) body.threshold_km = rawThreshold;
      const r = await fetch(`/api/gear/${gid}/parts`, {
        method: 'POST', credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      });
      const d = await r.json();
      if (r.ok) {
        gearItems = gearItems.map(g => g.id === gid ? { ...g, parts: [...(g.parts ?? []), d.part] } : g);
        partAddName = { ...partAddName, [gid]: '' };
        partAddThreshold = { ...partAddThreshold, [gid]: '' };
      } else gearError = d.detail ?? 'Could not add part.';
    } catch { gearError = 'Could not reach server.'; }
    partAdding = { ...partAdding, [gid]: false };
  }

  function partStartEdit(gid: string, part: GearPart) {
    partEditId = `${gid}/${part.id}`;
    partEditName = part.name;
    partEditThreshold = part.threshold_km != null ? String(part.threshold_km) : '';
  }

  async function partSaveEdit(gid: string, pid: string) {
    if (!partEditName.trim()) return;
    partSaving = true;
    try {
      const body: Record<string, unknown> = { name: partEditName.trim() };
      if (partEditThreshold) body.threshold_km = parseFloat(partEditThreshold);
      const r = await fetch(`/api/gear/${gid}/parts/${pid}`, {
        method: 'PATCH', credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      });
      const d = await r.json();
      if (r.ok) {
        gearItems = gearItems.map(g => g.id === gid
          ? { ...g, parts: (g.parts ?? []).map(p => p.id === pid ? d.part : p) }
          : g);
        partEditId = null;
      }
    } catch { /* ignore */ }
    partSaving = false;
  }

  async function partDelete(gid: string, pid: string) {
    try {
      const r = await fetch(`/api/gear/${gid}/parts/${pid}`, { method: 'DELETE', credentials: 'include' });
      if (r.ok) gearItems = gearItems.map(g => g.id === gid ? { ...g, parts: (g.parts ?? []).filter(p => p.id !== pid) } : g);
    } catch { /* ignore */ }
  }

  function startReplacement(gid: string, pid: string) {
    replacingPartId = `${gid}/${pid}`;
    replacementNote = '';
    replacementDate = new Date().toISOString().slice(0, 10);
  }

  async function saveReplacement(gid: string, pid: string) {
    try {
      const body: Record<string, unknown> = { date: replacementDate };
      if (replacementNote.trim()) body.note = replacementNote.trim();
      const r = await fetch(`/api/gear/${gid}/parts/${pid}/replacements`, {
        method: 'POST', credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      });
      const d = await r.json();
      if (r.ok) {
        gearItems = gearItems.map(g => g.id === gid ? {
          ...g, parts: (g.parts ?? []).map(p => p.id === pid
            ? { ...p, replacements: [...p.replacements, d.replacement] }
            : p)
        } : g);
        replacingPartId = null;
      }
    } catch { /* ignore */ }
  }

  async function deleteReplacement(gid: string, pid: string, rid: string) {
    try {
      const r = await fetch(`/api/gear/${gid}/parts/${pid}/replacements/${rid}`, { method: 'DELETE', credentials: 'include' });
      if (r.ok) gearItems = gearItems.map(g => g.id === gid ? {
        ...g, parts: (g.parts ?? []).map(p => p.id === pid
          ? { ...p, replacements: p.replacements.filter(rep => rep.id !== rid) }
          : p)
      } : g);
    } catch { /* ignore */ }
  }

  async function toggleSegment(id: string) {
    if (expandedId === id) { expandedId = null; return; }
    expandedId = id;
    if (!effortsBySegment[id] && !loadingEfforts[id]) {
      loadingEfforts = { ...loadingEfforts, [id]: true };
      try {
        const r = await fetch(`/api/segments/${id}/efforts`, { credentials: 'include' });
        if (r.ok) effortsBySegment = { ...effortsBySegment, [id]: await r.json() };
      } catch { /* ignore */ }
      loadingEfforts = { ...loadingEfforts, [id]: false };
    }
  }

  const editUrl = import.meta.env.PUBLIC_EDIT_URL ?? '';
  const editEnabled = editUrl !== '' || import.meta.env.PUBLIC_EDIT_ENABLED === 'true';

  $: if (mounted) {
    const params = new URLSearchParams(window.location.search);
    if (activeTab === 'power') params.delete('tab'); else params.set('tab', activeTab);
    const qs = params.toString();
    history.replaceState(null, '', qs ? `?${qs}` : window.location.pathname);
  }

  $: if (activeTab === 'gear' && isOwner && !gearFetched && !gearLoading) gearFetch();

  $: gearDistances = Object.fromEntries(
    gearItems.map(g => [
      g.name,
      allActivities.filter(a => a.gear === g.name).reduce((sum, a) => sum + (a.distance_m ?? 0), 0),
    ])
  );

  $: if (activeTab === 'segments' && segmentsHandle && !segmentsFetched && !segmentsLoading) {
    segmentsLoading = true;
    segmentsFetched = true;
    fetch(`/api/users/${segmentsHandle}/segment_summary`)
      .then(r => r.ok ? r.json() : [])
      .then(d => { segmentSummary = d; })
      .catch(() => {})
      .finally(() => { segmentsLoading = false; });
  }

  onMount(async () => {
    const w = window as any;
    if (w.__bincioMe !== undefined) {
      isOwner = w.__bincioMe === handle;
    } else {
      // Multi-user: __bincioMe is set asynchronously by /api/me in Base.astro.
      // Listen for the event so owner-only tabs appear without a hard refresh.
      window.addEventListener('bincio:me', (e: Event) => {
        isOwner = (e as CustomEvent<string>).detail === handle;
      }, { once: true });
    }
    const TABS: Tab[] = ['power', 'records', 'segments', 'profile', 'explore', 'nerd', 'gear'];
    const rawTab = new URLSearchParams(window.location.search).get('tab');
    const resolved = TABS.includes(rawTab as Tab) ? (rawTab as Tab) : 'power';
    activeTab = (resolved === 'explore' && !isOwner) ? 'power' : resolved;
    mounted = true;

    // Resolve handle for the segments endpoint
    if (handle) {
      segmentsHandle = handle;
    } else if (athleteUrl) {
      // Parse from e.g. "/data/ccbas/_merged/athlete.json"
      const m = athleteUrl.match(/\/data\/([^/]+)\//);
      if (m) segmentsHandle = m[1];
    }
    if (!segmentsHandle) {
      // Fall back to /api/me for the self-athlete page
      try {
        const r = await fetch('/api/me', { credentials: 'include' });
        if (r.ok) { const u = await r.json(); segmentsHandle = u.handle ?? ''; }
      } catch { /* ignore */ }
    }
    try {
      const [athleteData, index] = await Promise.all([
        loadAthlete(import.meta.env.BASE_URL, athleteUrl || undefined),
        loadIndex(import.meta.env.BASE_URL, indexUrl || undefined),
      ]);
      // Static file may not exist yet if the background rebuild hasn't finished — fall back to API
      let resolvedAthlete = athleteData as AthleteJson | null;
      if (!resolvedAthlete && editEnabled) {
        try {
          const r = await fetch('/api/athlete', { credentials: 'include' });
          if (r.ok) resolvedAthlete = await r.json() as AthleteJson;
        } catch { /* ignore */ }
      }
      athlete = resolvedAthlete;
      allActivities = index.activities.filter(a => !isUnlisted(a.privacy));
      activities    = allActivities.filter(a => a.mmp);
    } catch (e: any) {
      error = e.message;
    } finally {
      loading = false;
    }
  });

  async function onSaved() {
    // Try static file first; fall back to API (works before the background rebuild finishes)
    const staticUrl = athleteUrl || `${import.meta.env.BASE_URL}data/athlete.json`;
    let res = await fetch(`${staticUrl}?t=${Date.now()}`);
    if (!res.ok) res = await fetch('/api/athlete', { credentials: 'include' });
    if (res.ok) athlete = await res.json() as AthleteJson;
    drawerOpen = false;
  }

  function fmtZone(zones: [number, number][], i: number): string {
    const [lo, hi] = zones[i];
    return hi >= 9000 ? `${lo}+ W` : `${lo}–${hi} W`;
  }
  function fmtHrZone(zones: [number, number][], i: number): string {
    const [lo, hi] = zones[i];
    return hi >= 900 ? `${lo}+ bpm` : `${lo}–${hi} bpm`;
  }

  const ALL_TABS: { key: Tab; label: string; ownerOnly?: boolean }[] = [
    { key: 'power',    label: 'Power Curve' },
    { key: 'records',  label: 'Records'     },
    { key: 'segments', label: 'Segments'    },
    { key: 'profile',  label: 'Profile'     },
    { key: 'explore',  label: 'Explore',    ownerOnly: true },
    { key: 'nerd',     label: 'Nerd Corner', ownerOnly: true },
    { key: 'gear',     label: 'Gear',        ownerOnly: true },
  ];
  $: TABS = ALL_TABS.filter(t => !t.ownerOnly || isOwner);
</script>

{#if loading}
  <p class="text-zinc-400 text-sm">Loading…</p>
{:else if error}
  <p class="text-red-400 text-sm">{error}</p>
{:else if !athlete}
  <div class="text-zinc-400 text-sm space-y-3">
    <p>No athlete profile yet.</p>
    {#if editEnabled}
      <button
        on:click={() => drawerOpen = true}
        class="px-3 py-1.5 text-xs border border-zinc-700 hover:border-zinc-500 text-zinc-400 hover:text-white rounded-md transition-colors"
      >Create profile</button>
    {/if}
  </div>
{:else}

  <!-- Header row: tabs + edit button -->
  <div class="flex items-center justify-between mb-6 border-b border-zinc-800 pb-0">
    <nav class="flex gap-0">
      {#each TABS as tab}
        <button
          on:click={() => activeTab = tab.key}
          class="px-4 py-3 text-sm font-medium border-b-2 transition-colors -mb-px"
          class:border-blue-500={activeTab === tab.key}
          class:text-white={activeTab === tab.key}
          class:border-transparent={activeTab !== tab.key}
          class:text-zinc-500={activeTab !== tab.key}
          class:hover:text-zinc-300={activeTab !== tab.key}
        >{tab.label}</button>
      {/each}
    </nav>
    {#if editEnabled}
      <button
        on:click={() => drawerOpen = true}
        class="mb-2 px-3 py-1.5 text-xs border border-zinc-700 hover:border-zinc-500 text-zinc-400 hover:text-white rounded-md transition-colors"
      >Edit profile</button>
    {/if}
  </div>

  <!-- Power Curve tab -->
  {#if activeTab === 'power'}
    {#if athlete.power_curve.all_time}
      <div class="bg-zinc-900 rounded-xl p-4 border border-zinc-800">
        <MmpChart {athlete} {activities} {base} />
      </div>
    {:else}
      <p class="text-zinc-500 text-sm">No power data found. Make sure your activities include power meter data.</p>
    {/if}

  <!-- Records tab -->
  {:else if activeTab === 'records'}
    <RecordsView {athlete} {base} />

  <!-- Segments tab -->
  {:else if activeTab === 'segments'}
    <div class="flex items-center justify-between mb-4">
      <span></span>
      <div class="flex items-center gap-3">
        {#if rescanMsg}<span class="text-xs text-zinc-400">{rescanMsg}</span>{/if}
        <button
          on:click={async () => {
            rescanning = true; rescanMsg = null;
            try {
              const r = await fetch('/api/me/segment-rescan', { method: 'POST', credentials: 'include' });
              const d = await r.json();
              if (r.ok) {
                rescanMsg = `Found ${d.efforts_found} effort${d.efforts_found !== 1 ? 's' : ''}.`;
                segmentSummary = [];
                segmentsFetched = false;
              } else rescanMsg = d.detail ?? 'Rescan failed.';
            } catch { rescanMsg = 'Could not reach server.'; }
            rescanning = false;
          }}
          disabled={rescanning}
          class="text-xs px-3 py-1.5 rounded-lg border border-zinc-700 text-zinc-400 hover:border-zinc-500 hover:text-white transition-colors disabled:opacity-40"
        >{rescanning ? 'Scanning…' : 'Rescan all activities'}</button>
      </div>
    </div>
    {#if segmentsLoading}
      <p class="text-zinc-500 text-sm">Loading…</p>
    {:else if segmentSummary.length === 0}
      <p class="text-zinc-500 text-sm">No segment efforts yet. Use "Rescan all activities" to detect efforts from existing activities.</p>
    {:else}
      <div class="bg-zinc-900 rounded-xl border border-zinc-800 overflow-hidden">
        <table class="w-full text-sm">
          <thead class="border-b border-zinc-800">
            <tr class="text-left text-zinc-500 text-xs">
              <th class="px-4 py-2">Segment</th>
              <th class="px-4 py-2 hidden sm:table-cell">Distance</th>
              <th class="px-4 py-2">Best time</th>
              <th class="px-4 py-2 text-right pr-4">Efforts</th>
            </tr>
          </thead>
          <tbody>
            {#each segmentSummary as row (row.segment.id)}
              <!-- Summary row -->
              <tr
                class="border-b border-zinc-800/50 hover:bg-zinc-800/40 transition-colors cursor-pointer select-none"
                class:bg-zinc-800={expandedId === row.segment.id}
                on:click={() => toggleSegment(row.segment.id)}
                role="button"
                tabindex="0"
                on:keydown={e => e.key === 'Enter' && toggleSegment(row.segment.id)}
              >
                <td class="px-4 py-2.5">
                  <div class="flex items-center gap-1.5">
                    {#if row.segment.sport}
                      <span>{sportIcon(row.segment.sport as any)}</span>
                    {/if}
                    <a href="{base}segments/{row.segment.id}/"
                       class="text-white hover:text-blue-400 transition-colors font-medium"
                       on:click|stopPropagation>
                      {row.segment.name}
                    </a>
                  </div>
                </td>
                <td class="px-4 py-2.5 text-zinc-400 hidden sm:table-cell">{formatDistance(row.segment.distance_m)}</td>
                <td class="px-4 py-2.5">
                  <a href="{base}activity/{row.best_activity_id}/"
                     class="font-mono text-white hover:text-blue-400 transition-colors"
                     on:click|stopPropagation
                     title="Activity where this PR was set">
                    {formatElapsed(row.best_elapsed_s)}
                  </a>
                </td>
                <td class="px-4 py-2.5 text-zinc-400 text-right pr-4">
                  <span class="inline-flex items-center gap-1.5">
                    {row.effort_count}
                    <span class="text-zinc-600 text-xs">{expandedId === row.segment.id ? '▲' : '▼'}</span>
                  </span>
                </td>
              </tr>

              <!-- Expanded effort list -->
              {#if expandedId === row.segment.id}
                <tr class="border-b border-zinc-800/50">
                  <td colspan="4" class="px-4 pb-3 pt-1">
                    {#if loadingEfforts[row.segment.id]}
                      <p class="text-zinc-500 text-xs py-2">Loading…</p>
                    {:else}
                      {@const efforts = effortsBySegment[row.segment.id] ?? []}
                      {@const pr = efforts.length ? Math.min(...efforts.map(e => e.elapsed_s)) : 0}
                      <div class="rounded-lg overflow-hidden border border-zinc-700/50">
                        <table class="w-full text-xs">
                          <tbody>
                            {#each efforts as e (e.activity_id + e.started_at)}
                              {@const delta = e.elapsed_s - pr}
                              {@const isPR = delta === 0}
                              <tr class="border-b border-zinc-700/30 last:border-0 hover:bg-zinc-700/20 transition-colors"
                                  class:bg-green-950={isPR}>
                                <td class="px-3 py-1.5">
                                  <a href="{base}activity/{e.activity_id}/"
                                     class="text-blue-400 hover:text-blue-300 transition-colors">
                                    {new Date(e.started_at).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' })}
                                  </a>
                                </td>
                                <td class="px-3 py-1.5 font-mono text-white">{formatElapsed(e.elapsed_s)}</td>
                                <td class="px-3 py-1.5 font-medium"
                                    class:text-green-400={isPR}
                                    class:text-zinc-500={!isPR}>
                                  {#if isPR}PR{:else}+{delta < 60 ? `${delta}s` : `${Math.floor(delta/60)}m${(delta%60).toString().padStart(2,'0')}s`}{/if}
                                </td>
                              </tr>
                            {/each}
                          </tbody>
                        </table>
                      </div>
                    {/if}
                  </td>
                </tr>
              {/if}
            {/each}
          </tbody>
        </table>
      </div>
    {/if}

  <!-- Nerd Corner tab -->
  {:else if activeTab === 'nerd'}
    <div class="bg-zinc-900 rounded-xl p-4 border border-zinc-800">
      <h3 class="text-sm font-medium text-zinc-400 uppercase tracking-wide mb-4">Year over year</h3>
      <NerdCorner activities={allActivities} />
    </div>

  <!-- Explore tab -->
  {:else if activeTab === 'explore'}
    <div style="height: calc(100vh - 200px); margin: 0 -1rem -1.5rem;">
      <Explore {handle} {base} embedded={true} />
    </div>

  <!-- Gear tab -->
  {:else if activeTab === 'gear'}
    <div class="space-y-3">
      {#if gearError}
        <p class="text-red-400 text-sm">{gearError}</p>
      {/if}
      {#if gearLoading}
        <p class="text-zinc-400 text-sm">Loading…</p>
      {:else}
        {#if gearItems.length > 0}
          <div class="bg-zinc-900 rounded-xl border border-zinc-800 overflow-hidden">
            {#each gearItems as item (item.id)}
              {#if gearEditId === item.id}
                <!-- Inline edit form -->
                <div class="px-4 py-3 border-b border-zinc-800 flex flex-wrap gap-2 items-center bg-zinc-800/50">
                  <input
                    bind:value={gearEditName}
                    class="flex-1 min-w-32 bg-zinc-700 text-white text-sm px-2 py-1 rounded border border-zinc-600 focus:border-blue-500 outline-none"
                    placeholder="Name"
                  />
                  <select
                    bind:value={gearEditType}
                    class="bg-zinc-700 text-white text-sm px-2 py-1 rounded border border-zinc-600 focus:border-blue-500 outline-none"
                  >
                    <option value="bike">Bike</option>
                    <option value="shoes">Shoes</option>
                    <option value="skis">Skis</option>
                    <option value="other">Other</option>
                  </select>
                  <label class="flex items-center gap-1.5 text-sm text-zinc-400 cursor-pointer">
                    <input type="checkbox" bind:checked={gearEditRetired} class="accent-blue-500" />
                    Retired
                  </label>
                  <label class="flex items-center gap-1.5 text-sm text-zinc-400">
                    <input
                      type="number" min="0" step="1" placeholder="weight (g)"
                      bind:value={gearEditWeightG}
                      class="w-28 bg-zinc-700 text-white text-sm px-2 py-1 rounded border border-zinc-600 focus:border-blue-500 outline-none"
                    />
                    <span class="text-xs">g</span>
                  </label>
                  <button
                    on:click={gearSaveEdit}
                    disabled={gearSaving || !gearEditName.trim()}
                    class="text-xs px-3 py-1 rounded bg-blue-600 hover:bg-blue-500 text-white transition-colors disabled:opacity-40"
                  >{gearSaving ? 'Saving…' : 'Save'}</button>
                  <button
                    on:click={() => gearEditId = null}
                    class="text-xs px-3 py-1 rounded border border-zinc-600 text-zinc-400 hover:text-white transition-colors"
                  >Cancel</button>
                </div>
              {:else}
                <!-- Gear row header -->
                <div class="border-b border-zinc-800 last:border-0">
                  <button
                    class="w-full px-4 py-3 flex items-center gap-3 text-left hover:bg-zinc-800/40 transition-colors"
                    on:click={() => gearToggle(item.id)}
                    aria-expanded={gearExpandedId === item.id}
                  >
                    <span class="text-lg leading-none">{GEAR_ICONS[item.type] ?? '⚙️'}</span>
                    <span class="flex-1 text-sm text-white" class:text-zinc-500={item.retired}>{item.name}</span>
                    <!-- Part status dots -->
                    {#each partDots(item) as dotColor}
                      <span class="w-2 h-2 rounded-full bg-current {dotColor.replace('text-', 'text-')}
                        {dotColor === 'red-400' ? 'text-red-400' : dotColor === 'yellow-400' ? 'text-yellow-400' : 'text-green-400'}"
                        style="display:inline-block;width:.5rem;height:.5rem;border-radius:9999px;background:currentColor"
                      ></span>
                    {/each}
                    {#if gearDistances[item.name] > 0}
                      <span class="text-xs text-zinc-400">{formatDistance(gearDistances[item.name])}</span>
                    {/if}
                    {#if item.weight_g}
                      <span class="text-xs text-zinc-500">{item.weight_g >= 1000 ? (item.weight_g / 1000).toFixed(2).replace(/\.?0+$/, '') + ' kg' : item.weight_g + ' g'}</span>
                    {/if}
                    {#if item.retired}
                      <span class="text-xs text-zinc-600 border border-zinc-700 rounded px-1.5 py-0.5">Retired</span>
                    {/if}
                    <span class="text-zinc-600 text-xs ml-1">{gearExpandedId === item.id ? '▲' : '▼'}</span>
                  </button>

                  <!-- Expanded section -->
                  {#if gearExpandedId === item.id}
                    <div class="px-4 pb-4 pt-1 border-t border-zinc-800 bg-zinc-950/40 space-y-3">

                      <!-- Parts list -->
                      {#if (item.parts ?? []).length > 0}
                        <div class="space-y-1">
                          {#each (item.parts ?? []) as part (part.id)}
                            {#if partEditId === `${item.id}/${part.id}`}
                              <!-- Part edit form -->
                              <div class="flex flex-wrap gap-2 items-center py-1">
                                <input
                                  bind:value={partEditName}
                                  class="flex-1 min-w-28 bg-zinc-800 text-white text-xs px-2 py-1 rounded border border-zinc-600 focus:border-blue-500 outline-none"
                                  placeholder="Part name"
                                />
                                <input
                                  bind:value={partEditThreshold}
                                  type="number" min="0"
                                  class="w-24 bg-zinc-800 text-white text-xs px-2 py-1 rounded border border-zinc-600 focus:border-blue-500 outline-none"
                                  placeholder="km limit"
                                />
                                <button
                                  on:click={() => partSaveEdit(item.id, part.id)}
                                  disabled={partSaving}
                                  class="text-xs px-2 py-1 rounded bg-blue-600 hover:bg-blue-500 text-white transition-colors disabled:opacity-40"
                                >{partSaving ? '…' : 'Save'}</button>
                                <button
                                  on:click={() => partEditId = null}
                                  class="text-xs px-2 py-1 rounded border border-zinc-700 text-zinc-400 hover:text-white transition-colors"
                                >Cancel</button>
                              </div>
                            {:else}
                              <div class="space-y-1">
                                <!-- Part row -->
                                <div class="flex items-center gap-2 text-sm">
                                  <span class="flex-1 text-zinc-300 text-xs">{part.name}</span>
                                  {#if replacingPartId === `${item.id}/${part.id}`}
                                    <!-- Replacement form inline -->
                                    <input
                                      bind:value={replacementDate}
                                      type="date"
                                      class="bg-zinc-800 text-white text-xs px-2 py-0.5 rounded border border-zinc-600 outline-none"
                                    />
                                    <input
                                      bind:value={replacementNote}
                                      placeholder="note (optional)"
                                      class="w-28 bg-zinc-800 text-white text-xs px-2 py-0.5 rounded border border-zinc-600 outline-none placeholder:text-zinc-600"
                                    />
                                    <button
                                      on:click={() => saveReplacement(item.id, part.id)}
                                      class="text-xs px-2 py-0.5 rounded bg-blue-600 hover:bg-blue-500 text-white transition-colors"
                                    >Save</button>
                                    <button
                                      on:click={() => replacingPartId = null}
                                      class="text-xs text-zinc-500 hover:text-white transition-colors"
                                    >Cancel</button>
                                  {:else}
                                    {@const km = partDistanceKm(item.name, part)}
                                    <span class="text-xs font-mono {partColor(km, part.threshold_km)}">
                                      {km.toFixed(0)} km{part.threshold_km ? ` / ${part.threshold_km}` : ''}
                                    </span>
                                    <button
                                      on:click={() => startReplacement(item.id, part.id)}
                                      class="text-xs text-zinc-500 hover:text-blue-400 transition-colors"
                                      title="Log replacement"
                                    >Replaced</button>
                                    <button
                                      on:click={() => partStartEdit(item.id, part)}
                                      class="text-xs text-zinc-600 hover:text-white transition-colors"
                                    >Edit</button>
                                    <button
                                      on:click={() => partDelete(item.id, part.id)}
                                      class="text-xs text-zinc-700 hover:text-red-400 transition-colors"
                                    >✕</button>
                                  {/if}
                                </div>
                                <!-- Recent replacements -->
                                {#if part.replacements.length > 0}
                                  <div class="pl-2 space-y-0.5">
                                    {#each part.replacements.slice().reverse().slice(0, 3) as rep (rep.id)}
                                      <div class="flex items-center gap-2 text-xs text-zinc-600">
                                        <span>{fmtDate(rep.date)}</span>
                                        {#if rep.note}<span class="text-zinc-500">{rep.note}</span>{/if}
                                        <button
                                          on:click={() => deleteReplacement(item.id, part.id, rep.id)}
                                          class="hover:text-red-400 transition-colors ml-auto"
                                          aria-label="Delete log entry"
                                        >✕</button>
                                      </div>
                                    {/each}
                                  </div>
                                {/if}
                              </div>
                            {/if}
                          {/each}
                        </div>
                      {:else if PART_SUGGESTIONS[item.type]}
                        <!-- First-part suggestion -->
                        <p class="text-xs text-zinc-500">
                          Track part wear?
                          <button
                            on:click={() => partAddSuggestion(item)}
                            class="text-blue-400 hover:text-blue-300 underline transition-colors"
                          >Add {PART_SUGGESTIONS[item.type].name}</button>
                        </p>
                      {/if}

                      <!-- Add part form -->
                      <div class="flex flex-wrap gap-2 items-center pt-1 border-t border-zinc-800">
                        <input
                          bind:value={partAddName[item.id]}
                          placeholder="Part name (e.g. chain)"
                          class="flex-1 min-w-28 bg-zinc-800 text-white text-xs px-2 py-1 rounded border border-zinc-700 focus:border-blue-500 outline-none placeholder:text-zinc-600"
                          on:keydown={e => e.key === 'Enter' && partAdd(item)}
                        />
                        <input
                          bind:value={partAddThreshold[item.id]}
                          type="number" min="0"
                          placeholder="km limit"
                          class="w-20 bg-zinc-800 text-white text-xs px-2 py-1 rounded border border-zinc-700 focus:border-blue-500 outline-none placeholder:text-zinc-600"
                        />
                        <button
                          on:click={() => partAdd(item)}
                          disabled={partAdding[item.id] || !partAddName[item.id]?.trim()}
                          class="text-xs px-2 py-1 rounded border border-zinc-700 hover:border-zinc-500 text-zinc-400 hover:text-white transition-colors disabled:opacity-40"
                        >{partAdding[item.id] ? '…' : '+ Part'}</button>
                      </div>

                      <!-- Gear item actions -->
                      <div class="flex gap-3 pt-1">
                        <button
                          on:click={() => gearStartEdit(item)}
                          class="text-xs text-zinc-500 hover:text-white transition-colors"
                        >Edit gear</button>
                        <button
                          on:click={() => gearDelete(item.id)}
                          class="text-xs text-zinc-600 hover:text-red-400 transition-colors"
                        >Delete gear</button>
                      </div>

                    </div>
                  {/if}
                </div>
              {/if}
            {/each}
          </div>
        {:else}
          <p class="text-zinc-500 text-sm">No gear registered yet.</p>
        {/if}

        <!-- Add gear form -->
        <div class="flex flex-wrap gap-2 items-center pt-1">
          <input
            bind:value={gearAddName}
            placeholder="Name (e.g. Rose Backroad)"
            class="flex-1 min-w-40 bg-zinc-800 text-white text-sm px-3 py-1.5 rounded-lg border border-zinc-700 focus:border-blue-500 outline-none placeholder:text-zinc-600"
            on:keydown={e => e.key === 'Enter' && gearAdd()}
          />
          <select
            bind:value={gearAddType}
            class="bg-zinc-800 text-white text-sm px-2 py-1.5 rounded-lg border border-zinc-700 focus:border-blue-500 outline-none"
          >
            <option value="bike">Bike</option>
            <option value="shoes">Shoes</option>
            <option value="skis">Skis</option>
            <option value="other">Other</option>
          </select>
          <button
            on:click={gearAdd}
            disabled={gearAdding || !gearAddName.trim()}
            class="text-xs px-3 py-1.5 rounded-lg border border-zinc-700 hover:border-zinc-500 text-zinc-400 hover:text-white transition-colors disabled:opacity-40"
          >{gearAdding ? 'Adding…' : '+ Add gear'}</button>
          {#if gearItems.length > 0}
            <button
              on:click={exportGearCsv}
              class="text-xs px-3 py-1.5 rounded-lg border border-zinc-700 hover:border-zinc-500 text-zinc-400 hover:text-white transition-colors ml-auto"
            >Export CSV</button>
          {/if}
        </div>
      {/if}
    </div>

  <!-- Profile tab -->
  {:else if activeTab === 'profile'}
    <div class="grid grid-cols-1 md:grid-cols-2 gap-4">

      <div class="bg-zinc-900 rounded-xl p-4 border border-zinc-800 space-y-3">
        <h3 class="text-sm font-medium text-zinc-400 uppercase tracking-wide">Key numbers</h3>
        {#if athlete.max_hr}
          <div class="flex justify-between text-sm">
            <span class="text-zinc-400">Max HR</span>
            <span class="text-white font-medium">{athlete.max_hr} bpm</span>
          </div>
        {/if}
        {#if athlete.ftp_w}
          <div class="flex justify-between text-sm">
            <span class="text-zinc-400">FTP</span>
            <span class="text-white font-medium">{athlete.ftp_w} W</span>
          </div>
        {/if}
        {#if athlete.weight_kg}
          <div class="flex justify-between text-sm">
            <span class="text-zinc-400">Weight</span>
            <span class="text-white font-medium">{athlete.weight_kg} kg</span>
          </div>
        {/if}
        {#if !athlete.max_hr && !athlete.ftp_w && !athlete.weight_kg}
          <p class="text-zinc-500 text-sm">No key numbers set yet. Use Edit profile to add Max HR, FTP, and body weight.</p>
        {/if}
      </div>

      {#if athlete.hr_zones}
        <div class="bg-zinc-900 rounded-xl p-4 border border-zinc-800 space-y-2">
          <h3 class="text-sm font-medium text-zinc-400 uppercase tracking-wide">HR Zones</h3>
          {#each athlete.hr_zones as _zone, i}
            <div class="flex justify-between items-center text-sm">
              <span class="text-zinc-400">Z{i + 1}</span>
              <span class="text-white">{fmtHrZone(athlete.hr_zones!, i)}</span>
            </div>
          {/each}
        </div>
      {/if}

      {#if athlete.power_zones}
        <div class="bg-zinc-900 rounded-xl p-4 border border-zinc-800 space-y-2">
          <h3 class="text-sm font-medium text-zinc-400 uppercase tracking-wide">Power Zones</h3>
          {#each athlete.power_zones as _zone, i}
            <div class="flex justify-between items-center text-sm">
              <span class="text-zinc-400">Z{i + 1}</span>
              <span class="text-white">{fmtZone(athlete.power_zones!, i)}</span>
            </div>
          {/each}
        </div>
      {/if}

    </div>
  {/if}

{/if}

{#if drawerOpen && editEnabled}
  <AthleteDrawer
    {editUrl}
    on:close={() => drawerOpen = false}
    on:saved={onSaved}
  />
{/if}
