"""Turning model text into a move, and a move into text.

The one rule: **never raise**. A malformed reply is a thing the model did, not
an error in the harness, and it must be scored rather than crash the rollout.
A parse failure becomes a rejected guess, which costs the model a turn of its
budget and shows up in `rejected_replies`.
"""

from __future__ import annotations

import re

from .engine import MAX_GUESSES, WORD_LENGTH

SYSTEM_PROMPT = """You are playing a word-guessing game.

A secret {length}-letter word has been chosen. You have {turns} attempts.
After each guess you are told, for every letter:
  G — correct letter, correct position
  Y — the letter is in the word, but somewhere else
  X — the letter is not in the word

Think it through, then give your guess as the only square-bracketed token in
your reply, like [crane]. The game reads the first bracketed token and ignores
everything else, so do not put any other words in brackets."""

_BRACKETED = re.compile(r"\[([A-Za-z]+)\]")


def system_prompt() -> str:
    return SYSTEM_PROMPT.format(length=WORD_LENGTH, turns=MAX_GUESSES)


def parse_guess(reply: str | None) -> str | None:
    """The first bracketed alphabetic token, lowercased. None if there is none.

    Deliberately does NOT check length or membership — that is the game's job,
    so that 'the model guessed a six-letter word' and 'the model produced no
    guess at all' stay distinguishable in the metrics.
    """
    if not reply:
        return None
    match = _BRACKETED.search(reply)
    return match.group(1).lower() if match else None


def render_feedback(guess: str, pattern: str, turns_left: int) -> str:
    """The feedback block, in the format the system prompt promised.

    Letters and tiles are space-separated and positionally aligned. Most
    tokenizers give a space-separated character its own token, which is the
    difference between the model reading position 3 and guessing at it.
    """
    letters = " ".join(guess.upper())
    tiles = " ".join(pattern)
    plural = "" if turns_left == 1 else "es"
    return f"{letters}\n{tiles}\n\nYou have {turns_left} guess{plural} left."


def render_rejection(reason: str, turns_left: int) -> str:
    plural = "" if turns_left == 1 else "es"
    return (
        f"That guess was not accepted: {reason}.\n"
        f"It did not use up a turn. You have {turns_left} guess{plural} left."
    )
