import type { RewardSpec, RewardValues } from '@/lib/demo-kit';
import rewardSource from '../../../envs/wordle_five/wordle_five/reward.py?raw';

import { scoreGuess, ALL_GREEN } from './engine';

/**
 * The reward, mirrored from `envs/wordle_five/wordle_five/reward.py`.
 *
 * The labels are the point. `consistency` is a fair variable name and a useless
 * thing to put in front of somebody deciding a budget; "guesses that could
 * still have won" is the same quantity said in a way that needs no gloss.
 */
export const reward: RewardSpec = {
  components: [
    {
      key: 'solved',
      label: 'Found the word',
      description: 'Did it win, within six guesses.',
      weight: 0.5,
      role: 'objective',
    },
    {
      key: 'economy',
      label: 'Did it in few guesses',
      description:
        'Turns used, as a ratio against the best player we ship, on the same hidden word. A ratio rather than a count, so a hard draw is not punished as a bad game.',
      weight: 0.3,
      role: 'objective',
    },
    {
      key: 'consistency',
      label: 'Never wasted a turn',
      description:
        'The share of attempts spent on a word that could still have been the answer. This one pulls against the other two on purpose.',
      weight: 0.2,
      role: 'counterweight',
    },
  ],
  metrics: [
    { key: 'guesses_used', label: 'Guesses used', description: 'Rows filled on the board.' },
    {
      key: 'rejected_replies',
      label: 'Replies refused',
      description: 'Not a word, wrong length, or a repeat. Costs a turn, not a row.',
    },
    {
      key: 'inconsistent_guesses',
      label: 'Contradicted itself',
      description: 'Guesses ruled out by feedback the model had already been given.',
    },
    {
      key: 'reference_depth',
      label: 'Reference took',
      description: 'How many guesses our best player needed for this same word.',
    },
  ],
  source: {
    path: 'envs/wordle_five/wordle_five/reward.py',
    code: rewardSource,
    marker: 'reward',
  },
};

const WEIGHTS: Record<string, number> = { solved: 0.5, economy: 0.3, consistency: 0.2 };

/**
 * Re-derive the reward from the moves alone.
 *
 * This is the browser half of the verification: the page does not display the
 * numbers the environment handed it, it recomputes them from the recorded
 * guesses and shows the difference. `referenceDepth` cannot be recomputed here
 * — it comes from a search the browser has no business running — so it is read
 * off the recorded metrics, and its absence makes the run unverifiable rather
 * than wrong.
 */
export function recompute(
  answer: string,
  guesses: readonly string[],
  rejected: number,
  referenceDepth: number | null,
): RewardValues | null {
  if (referenceDepth === null) return null;

  const patterns = guesses.map((g) => scoreGuess(g, answer));
  const won = patterns.length > 0 && patterns[patterns.length - 1] === ALL_GREEN;

  const solved = won ? 1 : 0;
  const economy = won ? Math.min(1, referenceDepth / Math.max(1, guesses.length)) : 0;

  // Scored over turns SPENT, refusals included. Counting only accepted guesses
  // would hand a perfect score to a run that played one word and then jammed
  // the parser: one guess, no contradictions, nothing to contradict.
  const spent = guesses.length + rejected;
  let viable = 0;
  for (let i = 0; i < guesses.length; i += 1) {
    let ok = true;
    for (let k = 0; k < i; k += 1) {
      if (scoreGuess(guesses[k]!, guesses[i]!) !== patterns[k]) {
        ok = false;
        break;
      }
    }
    if (ok) viable += 1;
  }
  const consistency = spent === 0 ? 0 : viable / spent;

  return { solved, economy, consistency };
}

export { WEIGHTS };
