"""Meta-Harness Coding-Agent PromptBuilder — the reference ``render_task_prompt`` for the coding
agent that BROWSES the on-disk archive D.

One module per component (see scaffold.py). For ``proposer.mode: "coding_agent"`` the proposer is a
headless coding agent (claude_code / codex / cursor_agent) navigating the archive D, so — unlike
the llm-mode PromptBuilder, which serializes a slice of D into the user prompt — this builder
produces:

* **system** — a filesystem-native domain SKILL.md (the paper's primary hyperparameter: constraints,
  exploitation axes, anti-overfitting, archive analysis and prototyping workflow), loaded once
  and injected verbatim under the reference banner. The proposer's adapter passes ``prompt.system``
  to the agent (a real ``--append-system-prompt`` for Claude Code, or prepended to the prompt for
  agents without such a flag). The ``{candidates_per_proposal}`` / ``{exploitation_axes}`` tokens
  are substituted exactly as in llm mode.

* **user** — the reference's ``render_task_prompt``: which proposal round this is, where archive D
  lives (the agent's cwd), the files to browse, and the candidate contract (write
  ``candidates/<name>.py`` + a ``pending_eval.json`` manifest). The run state and candidate output
  are files rather than chat-serialized approximations.
"""
from __future__ import annotations

from ...components.prompt import PromptBuilder
from ...models.base import Prompt
from ...records import LoopContext, Selection
from .prompt_builder import render_meta_skill

DEFAULT_CODING_AGENT_SKILL = "meta_harness/text_classification_filesystem"


def render_task_prompt(iteration: int, d_dir: str, candidates_per_proposal: int) -> str:
    """The reference ``render_task_prompt`` for galapagos: iteration header + where D is + the
    filesystem override + the candidate/``pending_eval.json`` contract."""
    return (
        f"Run iteration {iteration} of the evolution loop. Produce {candidates_per_proposal} new "
        "candidate program(s) this round.\n\n"
        "## FILESYSTEM ARCHIVE\n"
        "The full run state is NOT inlined in this prompt. It lives as FILES in the archive D at "
        "your working directory:\n\n"
        f"    {d_dir}\n\n"
        "Browse it with Read / Glob / Grep / Bash (grep, cat). The files:\n"
        "- `evolution_summary.jsonl` — one JSON row per evaluated proposed candidate (Phase-0 seed "
        "is intentionally excluded; name, iteration, "
        "combined_score, delta, cost, outcome, axis, hypothesis) across the FULL history.\n"
        "- `frontier_val.json` — the current frontier (`_pareto`: system, combined_score, and cost "
        "only when the task configured a minimize objective).\n"
        "- `candidates/<name>.py` — the full source of every prior program (the copy-then-edit pool).\n"
        "- `current_best.py` — the current best program (the frontier top).\n"
        "- `logs/<name>/result.json` — the complete baseline/candidate evaluation record (valid, metrics, artifacts, "
        "per-instance results, text feedback).\n"
        "- `logs/<name>/trace.txt` plus `metrics.json`, `artifacts.json`, and optional "
        "`per_instance.json` — queryable per-candidate evidence (deep-read failures AND successes).\n"
        "- `reports/<name>.md` — prior <=30-line candidate reports.\n"
        "- `TASK.md` — the task description and objective.\n\n"
        "## OUTPUT CONTRACT\n"
        "For each candidate, write a COMPLETE program file at `candidates/<snake_case_name>.py` "
        "(copy a frontier program from `candidates/` as your starting point; keep the EVOLVE-BLOCK "
        "markers and the fixed interface outside them intact — evolve only between the markers). "
        "Then declare ALL of this round's candidates in `pending_eval.json` at the archive root:\n\n"
        "    {\n"
        '      "iteration": ' + str(iteration) + ",\n"
        '      "candidates": [\n'
        '        {"name": "<snake_case_name>", "file": "candidates/<name>.py",\n'
        '         "hypothesis": "<falsifiable claim>", "axis": "<exploitation axis, or - >",\n'
        '         "changes": "<what changed>"}\n'
        "      ]\n"
        "    }\n\n"
        "Do NOT evaluate the candidates yourself — the outer loop scores each one with the task's "
        "evaluator. Do not write a report for an unevaluated new candidate; at the start of the next "
        "round, write its <=30-line post-eval report after inspecting its result. Each "
        "`candidates/<name>.py` is compile()-checked, then evaluated; invalid files never run."
    )


class MetaHarnessCodingAgentPromptBuilder(PromptBuilder):
    """system = the shared SKILL.md steering (agent-injected); user = render_task_prompt."""

    def __init__(self, candidates_per_proposal: int = 1,
                 skill: str = DEFAULT_CODING_AGENT_SKILL):
        self.candidates_per_proposal = max(1, int(candidates_per_proposal))
        # same shared-loader rendering as the llm builder (skill="none" -> empty steering) — fail
        # fast at construction
        (self.skill_path, self.skill_name, self.skill_description,
         self._system_text) = render_meta_skill(skill, self.candidates_per_proposal)

    def build(self, selection: Selection, memory=None, ctx: LoopContext | None = None) -> Prompt:
        sig = (ctx.blackboard.get("meta_harness", {}) if ctx is not None else {}) or {}
        iteration = sig.get("iteration", ctx.iteration if ctx else 0)
        d_dir = str(sig.get("d_dir") or "the archive directory (your working directory)")
        user = render_task_prompt(iteration, d_dir, self.candidates_per_proposal)
        return Prompt(system=self._system_text, user=user)
