"""Single-shot mode: one prompt, model returns files, we grade what it wrote.

The model answers with one JSON object {"files": {"path": "content", ...}}
(optionally inside a ```json fence). No feedback loop, no test access.
"""

from __future__ import annotations

import json
import re
from pathlib import Path

from . import client, workspace
from .challenges import Challenge
from .config import Model
from .session import Session

PROMPT = """You are completing a coding task in one shot. Write complete files that satisfy the spec below. Tests (pytest) will be run against your files; you cannot run them yourself.

Reply with ONE JSON object and nothing else:
{{"files": {{"<relative path>": "<complete file content>", ...}}}}

Required deliverable files: {expected}

SPEC:
{spec}
{starter}"""


def run(model: Model, challenge: Challenge, root: Path, session: Session, retries: int = 3) -> None:
    starter = ""
    if challenge.starter_files:
        listing = "\n".join(
            f"--- {rel} ---\n{content}" for rel, content in challenge.starter_files.items()
        )
        starter = f"\nSTARTER FILES (already in the workspace):\n{listing}"
    prompt = PROMPT.format(
        expected=", ".join(challenge.expected_files),
        spec=challenge.spec,
        starter=starter,
    )
    session.log("prompt", mode="oneshot", content=prompt)
    completion = client.chat(model, [{"role": "user", "content": prompt}], retries=retries)
    session.log("completion", content=completion.content,
                tokens=completion.completion_tokens,
                tokens_in=completion.prompt_tokens)

    files = _parse_files(completion.content)
    for rel, content in files.items():
        workspace.write_file(root, rel, content)
        session.log("write_file", path=rel, bytes=len(content))


def _parse_files(content: str) -> dict[str, str]:
    fenced = re.search(r"```(?:json)?\s*(\{.*)```", content, re.DOTALL)
    text = fenced.group(1) if fenced else content
    start = text.find("{")
    if start < 0:
        raise client.DegenerateOutput("one-shot reply contains no JSON object")
    try:
        obj, _ = json.JSONDecoder().raw_decode(text[start:])
    except json.JSONDecodeError as err:
        raise client.DegenerateOutput(f"one-shot reply is not valid JSON: {err}") from err
    files = obj.get("files")
    if not isinstance(files, dict) or not files:
        raise client.DegenerateOutput("one-shot reply JSON has no files")
    return {str(k): str(v) for k, v in files.items()}
