"""Regenerate the bundled `ablate demo` fixture.

    python tools/make_demo_fixture.py     # run from the repo root

The fixture in `ablate/demo/` is **synthetic**: these records were generated
here, not recorded from a live agent against a real repository. It exists so
that the first thing anyone runs costs $0 and needs no API key
(docs/06-cost.md), and `ablate demo` prints a banner saying so on every run.

This generator is committed alongside the fixture on purpose. A checked-in
data file with no reproducible provenance is a liability — nobody can tell
later whether it was measured or invented, which is precisely the confusion
this project exists to prevent.

docs/09-milestones.md schedules the real thing for M4 ("`ablate demo` fixture
recorded against a real repo"). When that lands, this script and the
SYNTHETIC banner in `ablate/demo/__init__.py` both go away.

The task rates below are chosen to land on the finding the product exists to
deliver: a decisive token regression sitting next to a pass rate that cannot
be resolved at this suite size.
"""
import json
from pathlib import Path

import numpy as np

from ablate.record import AssertionRecord, TelemetryRecord, TrialRecord

OUT = Path("ablate/demo")
OUT.mkdir(parents=True, exist_ok=True)

rng = np.random.default_rng(20260727)

# Shaped to land on the finding the product exists to deliver: a large,
# decisive token regression alongside an equivalent pass rate — "these lines
# cost you this much and change nothing measurable."
TASKS = [
    ("fix-null-deref", 0.80, 0.80),
    ("add-cli-flag", 0.60, 0.60),
    ("refactor-parser", 0.40, 0.40),
    ("convention-new-handler", 0.80, 0.60),
    ("restraint-small-fix", 0.60, 0.60),
    ("add-readme-badge", 1.00, 1.00),
    ("migrate-config", 0.00, 0.00),
    ("fix-race-condition", 0.40, 0.60),
    ("harden-parser", 0.60, 0.60),
    ("tighten-types", 0.60, 0.40),
]

CONFIG = """# Engineering conventions

This file is loaded into every agent session in this repository. Keep it
current: stale guidance is worse than no guidance, because the agent will
follow it confidently.

## Code style

Use `snake_case` for functions and module-level names, and `PascalCase` for
classes. Prefer explicit imports over wildcards so that grep finds every use
site. Keep lines under 100 characters. Type annotations are required on every
public function; internal helpers may omit them where the types are obvious
from a one-line body.

Do not add docstrings that restate the signature. A docstring earns its place
by explaining why the function exists or what invariant it maintains.

## Testing conventions

Every bug fix gets a regression test that fails before the fix and passes
after it. Run the full suite before proposing a change; a change that has not
been run is not a change, it is a suggestion.

Do not mock what you can construct. A real object with real behaviour catches
integration mistakes that a mock is designed not to notice. Reserve mocking
for genuine boundaries: network calls, the clock, the filesystem when the test
would otherwise be slow.

Prefer table-driven tests for anything with more than three cases. Name each
case so a failure message identifies it without opening the file.

## Commit hygiene

Write commit messages in the imperative mood, as though completing the
sentence "this commit will ...". One logical change per commit. Reference the
issue number when one exists, in the body rather than the subject line.

Never commit commented-out code. The history remembers it; the working tree
should not.

## Error handling

Never swallow an exception silently. If you genuinely intend to ignore one,
say so in a comment explaining what makes it safe. Prefer raising a domain
error over returning None, so callers cannot accidentally proceed on a
sentinel they forgot to check.

Log at the boundary, not in the core. A function deep in the call stack does
not know enough about context to write a useful log line, and logging there
produces duplicates when the caller also logs.

## Dependencies

Justify every new dependency in the pull request description: what it does,
what it would cost to write ourselves, and how actively it is maintained.
Prefer the standard library. A dependency added to save fifty lines will be
carried for years.

Pin versions in the lockfile, not in the manifest. The manifest states what we
are compatible with; the lockfile states what we tested.

## Performance

Do not optimise without a measurement. Attach the before and after numbers to
any change justified by performance, and state how they were produced.

Prefer clarity until profiling says otherwise. The most common performance
problem in this codebase has been an accidental O(n^2) in a loop that looked
innocuous, not a slow inner operation.

## Documentation

Update the README when you change a command's behaviour or add a flag. Update
the changelog for anything a user would notice. Design decisions with
long-term consequences go in an ADR, not in a commit message where nobody will
find them again.
"""

records = []
for task, pa, pb in TASKS:
    for arm, p, per_turn in (("A", pa, 14_220), ("B", pb, 10_040)):
        for r in range(1, 6):
            turns = int(np.clip(rng.normal(7.6, 1.1), 3, 14))
            tokens = int(rng.normal(per_turn, per_turn * 0.06) * turns)
            passed = bool(rng.random() < p)
            records.append(
                TrialRecord(
                    trial_id=f"{task}-{arm}-r{r}",
                    run_id="demo",
                    task_id=task,
                    variant_id=arm,
                    variant_hash=f"sha256:demo-{arm}",
                    replicate=r,
                    agent="claude-code",
                    agent_version="2.1.220",
                    model="claude-sonnet-5",
                    started_at="2026-07-26T14:02:11Z",
                    duration_s=float(round(rng.normal(74, 12), 1)),
                    telemetry=TelemetryRecord(
                        input_tokens=int(tokens * 0.05),
                        output_tokens=int(tokens * 0.03),
                        cache_read=int(tokens * 0.85),
                        cache_write=int(tokens * 0.07),
                        cost_usd=round(float(rng.normal(0.33, 0.05)), 4),
                        turns=turns,
                        tool_calls=turns * 2,
                    ),
                    assertions=[
                        AssertionRecord(
                            id="tests-pass", type="exit_code", passed=passed,
                            detail="pytest -q" if passed else "1 failed",
                        )
                    ],
                    passed=passed,
                )
            )

with (OUT / "trials.jsonl").open("w") as f:
    for record in records:
        f.write(record.model_dump_json() + "\n")

manifest = {
    "run_id": "demo",
    "command": "ablate compare CLAUDE.md /dev/null --trials 5",
    "agent": "claude-code",
    "agent_version": "2.1.220",
    "model": "claude-sonnet-5",
    "variants": [
        {"id": "A", "source": "CLAUDE.md", "text": CONFIG},
        {"id": "B", "source": "/dev/null", "text": None},
    ],
    "n_tasks": len(TASKS),
    "n_trials": 5,
    "seed": 7,
    "partial": None,
    "analyses": 1,
    "extends": None,
}
(OUT / "manifest.json").write_text(json.dumps(manifest, indent=2) + "\n")
print(f"wrote {len(records)} trials to {OUT}")
