"""silent-eval is the one tool whose job is catching failures that make no noise.

So its own failure mode is the worst one available: saying OK about a track that
does not work, or saying BROKEN about a track that does. On 2026-07-31 it was
doing BOTH, corpus-wide, and nobody noticed because the 13 setlist tracks — the
only ones anyone ever pointed it at — happened to dodge all three bugs.

These tests pin the three:

  1. a column-0 `$` continuation is a CONTINUATION, not a new declaration.
     Pulsar wraps blocks in GHCi's `:{ … :}`, which suspends layout, so PLN
     writes `d1` on one line and `$ whenmod …` at column 0 on the next. The
     harness emits into a real module, where that is a hard parse error — and
     it reported "THE TRACK DOES NOT COMPILE — it will fail live too" about
     code that has never failed live.
  2. a probe that THREW is not a probe that measured silence.
  3. the exit code answers "is anything wrong", never "is anything silent".

Tests 1 and 2 are structural (no ghc, no rig, fast). Test 3 is the one that
matters most: it is a false-GREEN guard, and a false green from this tool is
indistinguishable from a working set right up until the gig.
"""

from __future__ import annotations

import importlib.util
import os
import sys

TOOLS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, TOOLS)

_spec = importlib.util.spec_from_file_location(
    "silent_eval", os.path.join(TOOLS, "silent-eval.py"))
se = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(se)

from pvlint.core import Track  # noqa: E402


def bindings(text: str):
    return se.orbit_bindings(Track(path="<t>", text=text))


# --- 1. column-0 `$` continuations ------------------------------------------

def test_dollar_at_column_zero_is_indented():
    """PLN's dominant style across the corpus. Emitted un-indented, the `$`
    opens a new top-level declaration and ghc rejects the whole module."""
    src = bindings('d1\n$ whenmod 128 129 (fast 2)\n$ s "bd*4"\n')[0][3]
    lines = src.splitlines()
    assert lines[0].startswith("orb1_1 = idcp")
    for cont in lines[1:]:
        assert cont.startswith("  "), f"continuation at column 0: {cont!r}"


def test_relative_indent_below_a_continuation_is_preserved():
    """Indenting must not flatten structure — a `where`/argument block that was
    indented relative to its parent has to STAY relative, or a track that did
    parse stops parsing."""
    src = bindings('d1\n$ note (scale "major"\n    "0 2 4")\n')[0][3]
    a, b = src.splitlines()[1], src.splitlines()[2]
    assert len(b) - len(b.lstrip()) > len(a) - len(a.lstrip())


def test_the_ordinary_inline_style_still_works():
    """The setlist's style. It dodged the bug entirely, which is exactly why the
    bug survived — so it gets a test too."""
    src = bindings('d1 $ s "bd*4"\n')[0][3]
    assert src == 'orb1_1 = idcp $ s "bd*4"'


# --- 2. a crash is not silence ----------------------------------------------

class _R:
    def __init__(self, rc, out="", err=""):
        self.returncode, self.stdout, self.stderr = rc, out, err


def _verdict(monkeypatch, tmp_path, run_result):
    """Drive check_track with ghc stubbed out, so this stays a unit test."""
    track = tmp_path / "t.tidal"
    track.write_text('d1 $ s "bd*4"\n')
    calls = {"n": 0}

    def fake_run(cmd, **kw):
        calls["n"] += 1
        return _R(0) if calls["n"] == 1 else run_result   # 1st = ghc build

    monkeypatch.setattr(se.subprocess, "run", fake_run)
    return se.check_track(track, False, False)


def test_nonzero_exit_without_a_silent_line_is_a_crash(monkeypatch, tmp_path):
    """The probe sets its failure flag ONLY when it prints SILENT. So this run
    never measured silence — it threw. Reporting it as 'FAIL — 0 orbit(s)
    SILENT' was a verdict about music the run never reached."""
    v, detail = _verdict(monkeypatch, tmp_path,
                         _R(1, "  ok      d1 -> 12 event(s)\n",
                            "run: Syntax error in sequence"))
    assert v == "CRASH"
    assert "Syntax error" in detail, "stderr is the only thing that says WHY"


def test_nonzero_exit_with_a_silent_line_is_still_silence(monkeypatch, tmp_path):
    v, _ = _verdict(monkeypatch, tmp_path,
                    _R(1, "  SILENT  d1 (line 1)  (nothing in 64 cycles)\n"))
    assert v == "SILENT"


def test_zero_exit_is_ok(monkeypatch, tmp_path):
    v, _ = _verdict(monkeypatch, tmp_path, _R(0, "  ok      d1 -> 12 event(s)\n"))
    assert v == "ok"


# --- 3. the false-green guard -----------------------------------------------

def test_exit_code_is_not_keyed_to_silence_alone(monkeypatch, tmp_path, capsys):
    """THE important one. Splitting crashes out of the silence count once left
    the return value keyed to `silent_only`, so a corpus where every track
    failed to compile would print 'OK — every declared orbit emits events' and
    exit 0. A gate may only ever answer 'is anything wrong'."""
    track = tmp_path / "broken.tidal"
    track.write_text('d1 $ s "bd*4"\n')
    monkeypatch.setattr(se, "check_track",
                        lambda *a, **k: ("BROKEN", "  parse error on input '$'"))
    rc = se.main([str(track)])
    out = capsys.readouterr().out
    assert rc == 1, "a corpus that does not compile must NOT exit 0"
    assert "OK — every declared orbit emits events" not in out
