import React, { useState, useEffect, useMemo } from 'react';

// ── Colors ─────────────────────────────────────────────────────────────────────

const BASE_COLORS = {
  Success: { bg: 'bg-green-100', border: 'border-green-300', text: 'text-green-800', dot: 'bg-green-500', badge: 'bg-green-500' },
  Failure: { bg: 'bg-red-100',   border: 'border-red-300',   text: 'text-red-800',   dot: 'bg-red-500',   badge: 'bg-red-500'   },
  Other:   { bg: 'bg-gray-100',  border: 'border-gray-300',  text: 'text-gray-700',  dot: 'bg-gray-400',  badge: 'bg-gray-400'  },
};
const EXTRA_COLORS = [
  { bg: 'bg-indigo-100', border: 'border-indigo-300', text: 'text-indigo-800', dot: 'bg-indigo-500', badge: 'bg-indigo-500' },
  { bg: 'bg-yellow-100', border: 'border-yellow-300', text: 'text-yellow-800', dot: 'bg-yellow-500', badge: 'bg-yellow-500' },
  { bg: 'bg-purple-100', border: 'border-purple-300', text: 'text-purple-800', dot: 'bg-purple-500', badge: 'bg-purple-500' },
  { bg: 'bg-pink-100',   border: 'border-pink-300',   text: 'text-pink-800',   dot: 'bg-pink-500',   badge: 'bg-pink-500'   },
];
function getColors(cat, customCats = []) {
  if (BASE_COLORS[cat]) return BASE_COLORS[cat];
  return EXTRA_COLORS[Math.max(0, customCats.indexOf(cat)) % EXTRA_COLORS.length];
}

const ORDINALS = ['1st', '2nd', '3rd', '4th', '5th'];

// ── Helpers ────────────────────────────────────────────────────────────────────

/** Build display items from a sequence slice, respecting mode + collapsibility. */
function buildItems(seqSlice, mode, collapsible) {
  const categorised = seqSlice.filter(s => s.category);
  if (mode === 'discrete') {
    return categorised.map((s, i) => ({ id: i, category: s.category, count: 1 }));
  }
  // Rebuild runs within this slice, then apply collapsibility
  const runs = [];
  for (const s of categorised) {
    if (runs.length === 0 || runs[runs.length - 1].category !== s.category) {
      runs.push({ category: s.category, length: 1 });
    } else {
      runs[runs.length - 1].length++;
    }
  }
  const items = [];
  for (const run of runs) {
    if (collapsible[run.category] === false) {
      for (let i = 0; i < run.length; i++) items.push({ id: items.length, category: run.category, count: 1 });
    } else {
      items.push({ id: items.length, category: run.category, count: run.length });
    }
  }
  return items;
}

/** Compute per-category counts and total for a sequence slice. */
function segStats(seqSlice) {
  const byCat = {};
  for (const s of seqSlice) {
    if (!s.category) continue;
    byCat[s.category] = (byCat[s.category] || 0) + 1;
  }
  const total = Object.values(byCat).reduce((a, b) => a + b, 0);
  return { byCat, total };
}

// ── Component ──────────────────────────────────────────────────────────────────

/**
 * Props:
 *   runs            {category, length}[]   — global run-length-encoded sequence
 *   sequence        {note, category, timestamp}[]
 *   customCategories string[]
 */
export default function RunSequenceView({ runs = [], sequence = [], customCategories = [], sessionEndTs = null }) {
  const [mode, setMode] = useState('grouped');
  const [collapsible, setCollapsible] = useState({});
  const [splitN, setSplitN] = useState(1);
  const [durationMins, setDurationMins] = useState(20);
  const [selectedSegment, setSelectedSegment] = useState(null);

  // Initialise collapsibility for any new categories
  const allCats = useMemo(() => [...new Set(runs.map(r => r.category))], [runs]);
  useEffect(() => {
    setCollapsible(prev => {
      const next = { ...prev };
      for (const cat of allCats) {
        if (!(cat in next)) next[cat] = cat !== 'Success';
      }
      return next;
    });
  }, [allCats]);

  // Reset selected segment when split changes
  useEffect(() => { setSelectedSegment(null); }, [splitN]);

  // ── Timestamp bounds ─────────────────────────────────────────────────────────

  const { startTs, segSize, windowUnderrun } = useMemo(() => {
    let endT = sessionEndTs != null ? sessionEndTs : null;
    if (endT === null) {
      const ts = sequence.map(s => parseFloat(s.timestamp)).filter(t => !isNaN(t));
      endT = ts.length ? Math.max(...ts) : durationMins * 60;
    }
    const rawStart = endT - durationMins * 60;
    return {
      startTs: rawStart,
      segSize: (durationMins * 60) / Math.max(splitN, 1),
      windowUnderrun: rawStart < 0 ? Math.abs(rawStart) : 0,
    };
  }, [sequence, sessionEndTs, durationMins, splitN]);

  function segIndexOf(timestamp) {
    const t = parseFloat(timestamp);
    if (isNaN(t)) return 0;
    return Math.min(Math.max(Math.floor((t - startTs) / segSize), 0), splitN - 1);
  }

  // ── Segment data (only when split > 1) ───────────────────────────────────────

  const segmentData = useMemo(() => {
    if (splitN === 1) return null;
    const groups = Array.from({ length: splitN }, () => []);
    for (const s of sequence) {
      groups[segIndexOf(s.timestamp)].push(s);
    }
    return groups.map((grp, i) => ({
      index: i,
      items: buildItems(grp, mode, collapsible),
      stats: segStats(grp),
    }));
  }, [splitN, sequence, mode, collapsible, startTs, segSize]);

  // ── Single-segment display items (split = 1) ─────────────────────────────────

  const singleItems = useMemo(() => {
    if (splitN !== 1) return [];
    if (mode === 'discrete') {
      const src = sequence.length > 0
        ? sequence.filter(s => s.category)
        : runs.flatMap(r => Array.from({ length: r.length }, () => ({ category: r.category })));
      return src.map((s, i) => ({ id: i, category: s.category, count: 1 }));
    }
    const items = [];
    for (const run of runs) {
      if (collapsible[run.category] === false) {
        for (let i = 0; i < run.length; i++) items.push({ id: items.length, category: run.category, count: 1 });
      } else {
        items.push({ id: items.length, category: run.category, count: run.length });
      }
    }
    return items;
  }, [splitN, mode, runs, sequence, collapsible]);

  // ── Global totals ─────────────────────────────────────────────────────────────

  const globalStats = useMemo(() => segStats(sequence), [sequence]);

  if (runs.length === 0) return null;

  const hasTimestamps = sequence.some(s => !isNaN(parseFloat(s.timestamp)));
  const activeSegStats = selectedSegment !== null && segmentData ? segmentData[selectedSegment]?.stats ?? null : null;

  // ── Render ────────────────────────────────────────────────────────────────────

  return (
    <div>
      {/* ── Controls row ── */}
      <div className="flex items-center flex-wrap gap-x-4 gap-y-1.5 mb-2">
        {/* Title */}
        <p className="text-xs font-semibold text-gray-500 uppercase tracking-wide">
          Run sequence ({runs.length} runs)
        </p>

        {/* Mode toggle */}
        <div className="flex rounded border border-gray-200 overflow-hidden text-xs">
          <button type="button" onClick={() => setMode('discrete')}
            className={`px-2 py-0.5 transition-colors ${mode === 'discrete' ? 'bg-gray-700 text-white' : 'bg-white text-gray-500 hover:bg-gray-50'}`}>
            Discrete
          </button>
          <button type="button" onClick={() => setMode('grouped')}
            className={`px-2 py-0.5 border-l border-gray-200 transition-colors ${mode === 'grouped' ? 'bg-gray-700 text-white' : 'bg-white text-gray-500 hover:bg-gray-50'}`}>
            Grouped
          </button>
        </div>

        {/* Time split */}
        {hasTimestamps && (
          <div className="flex items-center gap-1.5 text-xs">
            <span className="text-gray-400">Split:</span>
            <select value={splitN} onChange={e => setSplitN(Number(e.target.value))}
              className="border border-gray-200 rounded px-1.5 py-0.5 text-xs bg-white focus:outline-none focus:ring-1 focus:ring-indigo-400">
              <option value={1}>None</option>
              <option value={2}>½</option>
              <option value={3}>⅓</option>
              <option value={4}>¼</option>
              <option value={5}>⅕</option>
            </select>
            <input
              type="number" min={1} max={600} value={durationMins}
              onChange={e => setDurationMins(Math.max(1, Number(e.target.value)))}
              className="border border-gray-200 rounded px-1.5 py-0.5 w-14 text-xs text-right focus:outline-none focus:ring-1 focus:ring-indigo-400"
              title="Experiment duration in minutes"
            />
            <span className="text-gray-400">min</span>
          </div>
        )}
      </div>

      {/* ── Per-category collapse toggles (grouped mode) ── */}
      {mode === 'grouped' && allCats.length > 0 && (
        <div className="flex flex-wrap gap-1.5 mb-2">
          {allCats.map(cat => {
            const c = getColors(cat, customCategories);
            const isGrouped = collapsible[cat] !== false;
            return (
              <button key={cat} type="button"
                onClick={() => setCollapsible(prev => ({ ...prev, [cat]: !prev[cat] }))}
                title={isGrouped ? 'Grouped — click for discrete' : 'Discrete — click to group'}
                className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs border transition-colors
                  ${isGrouped ? `${c.bg} ${c.border} ${c.text}` : `bg-white ${c.border} ${c.text} opacity-70`}`}>
                <span className={`w-1.5 h-1.5 rounded-full ${c.dot}`} />
                {cat}: {isGrouped ? 'grouped' : 'discrete'}
              </button>
            );
          })}
        </div>
      )}

      {/* ── Underrun warning ── */}
      {splitN > 1 && windowUnderrun > 0 && (() => {
        const u = Math.round(windowUnderrun);
        const m = Math.floor(u / 60), s = u % 60;
        return (
          <p className="text-[10px] text-amber-600 bg-amber-50 border border-amber-200 rounded px-2 py-1 mb-2">
            The {durationMins}-min window starts {m}:{String(s).padStart(2,'0')} before the recording — the 1st segment includes unrecorded time.
          </p>
        );
      })()}

      {/* ── Segment buttons (when split > 1) ── */}
      {splitN > 1 && segmentData && (
        <div className="flex flex-wrap gap-1.5 mb-2">
          {segmentData.map((seg, si) => {
            const isActive = selectedSegment === si;
            const segStart = startTs + si * segSize;
            const segEnd   = startTs + (si + 1) * segSize;
            const fmt = s => {
              const t = Math.round(s);
              const clamped = Math.max(0, t);
              const m = Math.floor(clamped / 60);
              const sec = clamped % 60;
              const prefix = t < 0 ? '0:00*' : `${m}:${String(sec).padStart(2, '0')}`;
              return prefix;
            };
            return (
              <button key={si} type="button"
                onClick={() => setSelectedSegment(prev => prev === si ? null : si)}
                title={`${fmt(segStart)} – ${fmt(segEnd)}`}
                className={`inline-flex flex-col items-center px-2.5 py-1 rounded-lg text-xs font-medium border transition-all
                  ${isActive ? 'bg-gray-800 text-white border-gray-800' : 'bg-white text-gray-600 border-gray-300 hover:border-gray-400'}`}>
                <span className="flex items-center gap-1.5">
                  {ORDINALS[si]}
                  <span className={`text-[10px] px-1 py-px rounded-full font-semibold
                    ${isActive ? 'bg-white/20 text-white' : 'bg-gray-100 text-gray-500'}`}>
                    {seg.stats.total}
                  </span>
                </span>
                <span className={`text-[9px] font-normal tabular-nums ${isActive ? 'text-gray-300' : 'text-gray-400'}`}>
                  {fmt(segStart)}–{fmt(segEnd)}
                </span>
              </button>
            );
          })}
          {selectedSegment !== null && (
            <button type="button" onClick={() => setSelectedSegment(null)}
              className="text-xs text-gray-400 hover:text-gray-600 px-1">✕</button>
          )}
        </div>
      )}

      {/* ── Selected segment stats ── */}
      {activeSegStats && (
        <div className="flex flex-wrap items-center gap-3 mb-2 px-3 py-2 bg-gray-50 rounded-lg border border-gray-200 text-xs">
          <span className="font-semibold text-gray-700">{ORDINALS[selectedSegment]} segment</span>
          <span className="text-gray-500">{activeSegStats.total} attempt{activeSegStats.total !== 1 ? 's' : ''}</span>
          {Object.entries(activeSegStats.byCat).sort(([, a], [, b]) => b - a).map(([cat, n]) => {
            const c = getColors(cat, customCategories);
            return (
              <span key={cat} className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full ${c.bg} ${c.text}`}>
                <span className={`w-1.5 h-1.5 rounded-full ${c.dot}`} />
                {cat}: {n}
              </span>
            );
          })}
        </div>
      )}

      {/* ── Chip area ── */}
      <div className="flex flex-wrap gap-1 items-center">
        {splitN === 1
          ? singleItems.map(item => <Chip key={item.id} item={item} customCategories={customCategories} dimmed={false} />)
          : segmentData && segmentData.map((seg, si) => (
              <React.Fragment key={si}>
                {si > 0 && (
                  <div className="flex flex-col items-center mx-0.5 self-stretch" aria-hidden>
                    <div className="w-px flex-1 bg-gray-300" style={{ minHeight: 20 }} />
                  </div>
                )}
                {seg.items.length === 0
                  ? <span className="text-[10px] text-gray-300 italic px-1">empty</span>
                  : seg.items.map(item => (
                      <Chip key={`${si}-${item.id}`} item={item} customCategories={customCategories}
                        dimmed={selectedSegment !== null && selectedSegment !== si} />
                    ))}
              </React.Fragment>
            ))
        }
      </div>

      {/* ── Global totals ── */}
      <div className="flex flex-wrap items-center gap-3 mt-2 pt-2 border-t border-gray-100 text-xs text-gray-500">
        <span className="font-medium text-gray-700">Total: {globalStats.total} attempt{globalStats.total !== 1 ? 's' : ''}</span>
        {Object.entries(globalStats.byCat).sort(([, a], [, b]) => b - a).map(([cat, n]) => {
          const c = getColors(cat, customCategories);
          return (
            <span key={cat} className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full ${c.bg} ${c.text}`}>
              <span className={`w-1.5 h-1.5 rounded-full ${c.dot}`} />
              {cat}: {n}
            </span>
          );
        })}
      </div>
    </div>
  );
}

function Chip({ item, customCategories, dimmed }) {
  const c = getColors(item.category, customCategories);
  return (
    <span
      title={item.count > 1 ? `${item.category} × ${item.count}` : item.category}
      className={`inline-flex items-center px-1.5 py-0.5 rounded text-xs font-mono border transition-opacity
        ${c.bg} ${c.text} ${c.border} ${dimmed ? 'opacity-25' : 'opacity-100'}`}>
      {item.count > 1 ? `${item.count}×` : ''}{item.category.slice(0, 3)}
    </span>
  );
}
