function normalizeMoveToken(move) {
  return String(move || '').replace(/\s+/g, '');
}

function moveToQuarterTurns(move) {
  const token = normalizeMoveToken(move);
  const match = token.match(/^([URFDLBMESxyzurfdlb])(?:w)?(2|')?$/);
  if (!match) return null;
  const amount = match[2] === '2' ? 2 : match[2] === "'" ? 3 : 1;
  return { face: match[1], amount };
}

function canonicalTurnUnits(moves) {
  const units = [];
  for (const move of moves) {
    const parsed = moveToQuarterTurns(move);
    if (!parsed) {
      units.push({ face: normalizeMoveToken(move), amount: 1, label: normalizeMoveToken(move) });
      continue;
    }
    const last = units[units.length - 1];
    if (last && last.face === parsed.face) {
      last.amount = (last.amount + parsed.amount) % 4;
      last.label = turnLabel(last.face, last.amount);
      if (last.amount === 0) units.pop();
    } else {
      units.push({ ...parsed, label: turnLabel(parsed.face, parsed.amount) });
    }
  }
  return units;
}

function turnLabel(face, amount) {
  if (amount === 0) return '';
  if (amount === 2) return `${face}2`;
  if (amount === 3) return `${face}'`;
  return face;
}

function ScrambleTrainer({ scramble, scrambleMoves, liveMoves, timerPhase, onResetCubeState, onNewScramble }) {
  const [stateInfo, setStateInfo] = React.useState(null);
  const observed = canonicalTurnUnits(liveMoves.map(m => m.move).filter(Boolean));
  const target = canonicalTurnUnits(scrambleMoves);
  const liveAlg = liveMoves.map(m => m.move).filter(Boolean).join(' ');

  React.useEffect(() => {
    let cancelled = false;
    async function updateStateInfo() {
      if (!window._cubing3x3) {
        setStateInfo(null);
        return;
      }

      try {
        const targetPattern = window._cubing3x3.patternFromAlg(scramble || '');
        const currentPattern = window._cubing3x3.patternFromAlg([scramble, liveAlg].filter(Boolean).join(' '));
        if (!cancelled) {
          setStateInfo({
            atTarget: window._cubing3x3.isSamePattern(currentPattern, targetPattern),
            solved: window._cubing3x3.isSolved(currentPattern),
            enabled: true,
          });
        }
      } catch (err) {
        console.warn('[blindth] cubing.js state comparison failed', err);
        if (!cancelled) setStateInfo(null);
      }
    }

    updateStateInfo();
    return () => { cancelled = true; };
  }, [scramble, liveAlg]);

  let correctPrefix = 0;
  while (
    correctPrefix < observed.length &&
    correctPrefix < target.length &&
    observed[correctPrefix].face === target[correctPrefix].face &&
    observed[correctPrefix].amount === target[correctPrefix].amount
  ) {
    correctPrefix += 1;
  }
  const partialTurn = (
    correctPrefix < observed.length &&
    correctPrefix < target.length &&
    observed.length === correctPrefix + 1 &&
    observed[correctPrefix].face === target[correctPrefix].face &&
    observed[correctPrefix].amount !== target[correctPrefix].amount
  );
  const hasMismatch = correctPrefix < observed.length && !partialTurn;
  const tokenDone = target.length > 0 && correctPrefix === target.length && observed.length === target.length;
  const done = stateInfo?.enabled ? stateInfo.atTarget : tokenDone;
  const remaining = target.slice(correctPrefix, correctPrefix + 10).map(t => t.label).join(' ');
  const extra = hasMismatch ? observed.slice(correctPrefix, correctPrefix + 6).map(t => t.label).join(' ') : '';
  const partialLabel = partialTurn ? observed[correctPrefix].label : '';

  const status = done
    ? 'Scramble complete'
    : partialTurn
      ? 'Finish current turn'
      : hasMismatch
        ? 'Misscramble detected'
        : `Scrambling ${correctPrefix}/${target.length || 0}`;

  return (
    <div style={stStyles.wrap}>
      <div style={stStyles.header}>
        <span style={stStyles.title}>Scramble flow</span>
        <span style={{...stStyles.badge, ...(done ? stStyles.badgeOk : {}), ...(partialTurn ? stStyles.badgeWarn : {}), ...(hasMismatch ? stStyles.badgeBad : {})}}>
          {status}
        </span>
      </div>
      <div style={stStyles.moves}>
        {target.length ? target.map((move, i) => (
          <span key={i} style={{
            ...stStyles.move,
            ...(i < correctPrefix ? stStyles.moveDone : {}),
            ...(i === correctPrefix && !hasMismatch && !partialTurn && !done ? stStyles.moveNext : {}),
            ...(i === correctPrefix && partialTurn ? stStyles.moveWarn : {}),
            ...(i === correctPrefix && hasMismatch ? stStyles.moveBad : {}),
          }}>{move.label}</span>
        )) : <span style={stStyles.empty}>Generate a scramble to begin.</span>}
      </div>
      <div style={stStyles.guidance}>
        {done && <span>Cube is in the target scramble state. Hold Space to get ready.</span>}
        {partialTurn && <span>Finish the current turn: target is <b>{target[correctPrefix]?.label}</b>, currently seen as <b>{partialLabel}</b>.</span>}
        {hasMismatch && <span>Undo or reset to solved, then follow from <b>{target[correctPrefix]?.label || 'start'}</b>. Expected next: {remaining || 'none'}. Seen: {extra || 'none'}.</span>}
        {!done && !hasMismatch && !partialTurn && <span>Next moves: {remaining || 'none'}</span>}
        {stateInfo?.enabled && (
          <span style={stStyles.stateHint}>
            cubing.js state: {stateInfo.atTarget ? 'target scramble reached' : stateInfo.solved ? 'solved' : 'not at target'}
          </span>
        )}
      </div>
      <div style={stStyles.actions}>
        <button style={stStyles.btn} onClick={onResetCubeState}>Reset cube state</button>
        <button style={stStyles.btnGhost} onClick={onNewScramble}>New scramble</button>
        <span style={stStyles.phase}>Timer: {timerPhase.toUpperCase()}</span>
      </div>
    </div>
  );
}

const stStyles = {
  wrap: {
    background: 'oklch(14% 0.01 250)',
    border: '1px solid oklch(20% 0.01 250)',
    borderRadius: '8px',
    padding: '14px 16px',
    display: 'flex',
    flexDirection: 'column',
    gap: '10px',
  },
  header: { display: 'flex', alignItems: 'center', gap: '10px' },
  title: {
    fontSize: '12px', fontWeight: '700', letterSpacing: '0.08em',
    textTransform: 'uppercase', color: 'oklch(60% 0.01 250)',
  },
  badge: {
    marginLeft: 'auto', padding: '3px 8px', borderRadius: '999px',
    background: 'oklch(18% 0.08 200)', color: 'oklch(65% 0.14 200)',
    fontSize: '11px', fontWeight: '700',
  },
  badgeOk: { background: 'oklch(18% 0.10 165)', color: 'oklch(65% 0.18 165)' },
  badgeWarn: { background: 'oklch(20% 0.10 80)', color: 'oklch(72% 0.14 80)' },
  badgeBad: { background: 'oklch(20% 0.08 25)', color: 'oklch(65% 0.14 25)' },
  moves: { display: 'flex', flexWrap: 'wrap', gap: '5px' },
  move: {
    fontFamily: "'JetBrains Mono', monospace", fontSize: '15px',
    padding: '3px 6px', borderRadius: '4px', color: 'oklch(65% 0.01 250)',
    background: 'oklch(10% 0.01 250)',
  },
  moveDone: { color: 'oklch(35% 0.01 250)', textDecoration: 'line-through' },
  moveNext: { color: 'oklch(10% 0.01 250)', background: 'oklch(65% 0.18 165)' },
  moveWarn: { color: 'oklch(10% 0.01 250)', background: 'oklch(72% 0.14 80)' },
  moveBad: { color: 'oklch(10% 0.01 250)', background: 'oklch(65% 0.14 25)' },
  empty: { color: 'oklch(38% 0.01 250)', fontSize: '13px' },
  guidance: { color: 'oklch(55% 0.01 250)', fontSize: '12px', lineHeight: 1.4 },
  stateHint: {
    display: 'block',
    marginTop: '4px',
    color: 'oklch(42% 0.01 250)',
    fontFamily: "'JetBrains Mono', monospace",
    fontSize: '11px',
  },
  actions: { display: 'flex', alignItems: 'center', gap: '8px' },
  btn: {
    padding: '6px 10px', borderRadius: '6px', border: 'none',
    background: 'oklch(65% 0.18 165)', color: 'oklch(10% 0.01 250)',
    fontSize: '12px', fontWeight: '700', cursor: 'pointer',
  },
  btnGhost: {
    padding: '6px 10px', borderRadius: '6px', border: '1px solid oklch(25% 0.01 250)',
    background: 'transparent', color: 'oklch(55% 0.01 250)', fontSize: '12px', cursor: 'pointer',
  },
  phase: { marginLeft: 'auto', color: 'oklch(38% 0.01 250)', fontSize: '11px', fontFamily: "'JetBrains Mono', monospace" },
};

Object.assign(window, { ScrambleTrainer });
