"""
z_ev_specs.py — pure gosling spec builders for z_external_validation_fork.py.

Sibling companion to `z_gb_specs.py`. Same contract: depends only on the `gosling` API object
passed in (no pandas, no marimo), so every builder can be schema-validated headlessly
(`gos.<spec>.validate()`). The notebook does all the pandas shaping and hands these builders plain
lists-of-dicts + the imported `gos` module + a colour dict `C`.

The external-validation fork's three cross-checks each have their OWN coordinate-bearing evidence
table, so each gets a faithful *genomic* view (the parent notebook only ever showed them as summary
bars / a confusion matrix — this adds the "where on the genome" layer, the way the genome-browser
fork did for the arcs):

  build_crispr   the EPCrisprBenchmark element→gene pairs as whole-genome ARCS, coloured by
                 confusion-matrix category (TP / FP / FN / TN) with a CRISPRi-effect-size SKYLINE —
                 so the ~37× odds ratio becomes a genomic picture of *which* perturbation-validated
                 pairs our map recovers vs misses. Queryable by CRISPR cell type.

The eQTL genetic-support view and the ρ link-strength / metadata explorer REUSE the genome-browser
fork's already-perfected builders (`z_gb_specs.build_locus` for the fine-mapped-variant lollipops,
`z_gb_specs.build_program` for the whole-genome ρ arc-skyline), so the two forks render identically
and there is one place to maintain each idiom. This module only adds what the browser fork lacked:
the CRISPR confusion-category genomic view.

Rendering facts (identical to z_gb_specs — kept in sync):
  1. At whole-genome scale a cis link is sub-pixel, so an arc track is paired with a point SKYLINE
     that stays legible at any zoom; the arcs resolve as you zoom into an arm (shared linkingId).
  2. A thin `withinLink` shows only its STROKE, and an unset stroke defaults to black — so links are
     coloured on `stroke`, not `color`.
  3. A `type:nominal` colour channel takes a custom `domain`/`range`; a `type:quantitative` one is
     given gosling's own viridis ramp (custom ranges are ignored) — so categories get explicit
     colours, ρ keeps the shared viridis domain from z_gb_specs.
"""
from __future__ import annotations

# reuse the browser fork's shared genome constants / helpers so the two forks stay pixel-identical
# and there is a single source of truth for the ideogram, chromosome bands and ρ colour scale.
from z_gb_specs import (
    HG38, CHR_ORDER, RHO_DOMAIN, _present, _ideo_data, _chr_bands, _rho_color,
    build_locus, build_program, build_atlas,
)

# the four confusion-matrix categories, ordered recovered→missed, with the notebook's palette roles.
# (Kept here, not in C, so the legend order is stable regardless of dict iteration order.)
CRISPR_CATS = ["TP", "FP", "FN", "TN"]
CRISPR_CAT_LONG = {
    "TP": "TP · Regulated & we link",
    "FP": "FP · not Regulated but we link",
    "FN": "FN · Regulated, we miss",
    "TN": "TN · not Regulated & we don't link",
}


def _cat_color(gos, C, legend):
    """The one confusion-category colour scale, shared by the CRISPR arcs and effect-size skyline so
    a single legend reads for both. GREEN=TP, RED=FP, GREY=FN, SLATE=TN — matching §2's matrix."""
    long = [CRISPR_CAT_LONG[c] for c in CRISPR_CATS]
    rng = [C["GREEN"], C["RED"], C["GREY"], C["SLATE"]]
    return gos.Color("cat_long:N", domain=long, range=rng, legend=legend)


# ------------------------------------------------------------------------------------------------
# CRISPR — the EPCrisprBenchmark pairs, genomic (arcs by confusion category · effect-size skyline)
# ------------------------------------------------------------------------------------------------
def build_crispr(gos, C, *, pairs, y_domain, width=860):
    """
    pairs : [{chrom, e_mid, tss, cat, cat_long, effect}]   one CRISPR-tested element→gene pair each.
            cat ∈ {TP,FP,FN,TN}; cat_long is the legend label; effect is the CRISPRi EffectSize.
    y_domain : [lo, hi]  the observed EffectSize spread, pinned so the skyline resolves it.

    Whole-genome, LINKED (shared linkingId → ideogram + arcs + skyline pan/zoom together):
      · chromosome ideogram
      · a real ARC track (element→gene), coloured by confusion category on the STROKE (fact #2)
      · an effect-size SKYLINE (one point per pair, y = CRISPRi EffectSize), same category colours —
        the always-legible overview; the arcs (sub-pixel zoomed out, fact #1) resolve as you zoom in.
    Answers, genomically: *where do the perturbation-validated pairs sit, and which does our map
    recover (TP) vs miss (FN) vs fire on wrongly (FP)?*
    """
    present = _present(a["chrom"] for a in pairs)
    LID = "crisprwg"

    ideo = (gos.Track(_ideo_data(gos, present)).mark_rect()
            .encode(x=gos.X("s:G", axis="top", linkingId=LID), xe="e:G",
                    color=_chr_bands(gos, present))
            .properties(width=width, height=12))

    data = gos.Data(type="json", values=pairs, chromosomeField="chrom",
                    genomicFields=["e_mid", "tss"])
    arcs = (gos.Track(data).mark_withinLink()
            .encode(x=gos.X("e_mid:G", axis="none", linkingId=LID), xe="tss:G",
                    stroke=_cat_color(gos, C, False), strokeWidth=gos.value(1.1),
                    opacity=gos.value(0.55))
            .properties(width=width, height=120))
    sky = (gos.Track(data).mark_point()
           .encode(x=gos.X("e_mid:G", axis="none", linkingId=LID),
                   y=gos.Y("effect:Q", domain=list(y_domain), axis="left"),
                   color=_cat_color(gos, C, True), size=gos.value(5.0), opacity=gos.value(0.8),
                   stroke=gos.value("white"), strokeWidth=gos.value(0.3))
           .properties(width=width, height=190))
    return gos.vertical(ideo, arcs, sky).properties(spacing=4).to_dict()


# thin, self-documenting re-exports so the notebook imports one module. These ARE the browser fork's
# builders — the eQTL lollipop locus and the ρ arc-skyline are already perfected there; the external-
# validation fork feeds them its own (eQTL-supported / metadata-filtered) rows.
build_eqtl_locus = build_locus       # cCRE rects + fine-mapped-variant lollipops on a PIP axis
build_links = build_program          # whole-genome ρ arc + skyline, or circular ρ chords
build_links_atlas = build_atlas      # every arc, circular, coloured by chromosome
