"""WS1.4 — PRD FRREP-AR010 indicator-mapping crosswalk.

Single source of truth for:

1. The canonical event → indicator mapping consumed by ``apply_rep_outbox_row``
   (see ``EVENT_INDICATOR_MAP`` below).
2. The AR010 *derived* indicators — those firing on top of canonical events when
   secondary conditions hold (gender, degree level, RIMS award link, quiz pass
   threshold). Their derivation rules are documented as machine-readable
   dataclasses so the indicator-reference page can render them without a
   round-trip through Python code.
3. Thresholds (e.g. digital-competency pass rate) that previously lived as
   ``_AR010_*`` private constants in ``services.py``.

The reference page at ``/mel/tracking/indicators/`` reads these constants
directly; the service layer imports them at module load. Changing a code label
or derivation rule here automatically propagates to both surfaces.
"""
from __future__ import annotations

from dataclasses import dataclass
from typing import Iterator

# ---------------------------------------------------------------------------
# Canonical event → indicator mapping (previously _REP_INDICATOR_BY_EVENT)
# ---------------------------------------------------------------------------

EVENT_INDICATOR_MAP: dict[str, str] = {
    "course_completed": "rep-courses-completed",
    "enrolment_created": "rep-enrolments",
    "certificate_issued": "rep-certificates-issued",
    "assignment_graded": "rep-assignments-graded",
    "quiz_submitted": "rep-quiz-submissions",
    "lesson_completed": "rep-lessons-completed",
    "certificate_revoked": "rep-certificates-revoked",
    "enrolment_withdrawn": "rep-enrolments-withdrawn",
    "forum_post_created": "rep-forum-engagement",
    "job_application_submitted": "rep-job-applications",
    "job_application_decided": "rep-job-application-outcomes",
    "programme_completed": "rep-programme-completions",
    # WS4.1 / FRREP-LD003 / FRREP-LD004 — live virtual classes.
    "live_session_scheduled": "rep-live-sessions-scheduled",
    "live_session_joined": "rep-live-attendance",
}


# ---------------------------------------------------------------------------
# Thresholds (AR010)
# ---------------------------------------------------------------------------

AR010_DIGITAL_COMPETENCY_PASS_THRESHOLD = 70.0
"""Percentage at which a quiz submission counts toward the digital-competency
indicator. Change here propagates to the derivation in ``services.py``."""


# ---------------------------------------------------------------------------
# AR010 derived indicators (documented derivation rules)
# ---------------------------------------------------------------------------


@dataclass(frozen=True)
class AR010Indicator:
    code: str
    label: str
    event_trigger: str
    derivation_rule: str
    source_field: str
    threshold: str
    code_path: str


AR010_INDICATORS: tuple[AR010Indicator, ...] = (
    AR010Indicator(
        code="rep-scholarship-enrolment",
        label="Scholarship enrolment",
        event_trigger="enrolment_created",
        derivation_rule="Fires when the enrolment payload carries an "
        "``award_id`` or ``rims_award_id`` — i.e. the learner enrolled via "
        "a RIMS scholarship rather than self-paying.",
        source_field="payload.award_id OR payload.rims_award_id",
        threshold="any non-empty value",
        code_path="apps/mel/tracking/services.py:_emit_ar010_indicators (enrolment_created branch)",
    ),
    AR010Indicator(
        code="rep-women-participation",
        label="Women participation",
        event_trigger="enrolment_created",
        derivation_rule="Fires when the learner's UserProfile.gender is "
        "FEMALE. Blank gender → no indicator (data-unavailable signal).",
        source_field="UserProfile.gender",
        threshold="== FEMALE",
        code_path="apps/mel/tracking/services.py:_emit_ar010_indicators (enrolment_created branch)",
    ),
    AR010Indicator(
        code="rep-youth-participation",
        label="Youth participation",
        event_trigger="enrolment_created",
        derivation_rule="Fires when UserProfile.is_youth is True. The youth "
        "age cutoff is owned by ``UserProfile.is_youth`` (computed from "
        "date_of_birth); typically learners under 24.",
        source_field="UserProfile.is_youth",
        threshold="== True",
        code_path="apps/core/authentication/models.py:UserProfile.is_youth",
    ),
    AR010Indicator(
        code="rep-postgrad-completion",
        label="Postgraduate completion",
        event_trigger="course_completed",
        derivation_rule="Fires when the completing learner's "
        "UserProfile.degree_level is MASTERS or PHD. Records the AR010 "
        "postgraduate-attainment indicator on top of the canonical "
        "rep-courses-completed.",
        source_field="UserProfile.degree_level",
        threshold="in {MASTERS, PHD}",
        code_path="apps/mel/tracking/services.py:_emit_ar010_indicators (course_completed branch)",
    ),
    AR010Indicator(
        code="rep-digital-competency",
        label="Digital competency",
        event_trigger="quiz_submitted",
        derivation_rule="Fires when the quiz attempt's ``percentage`` field "
        "meets or exceeds the digital-competency pass threshold. Threshold "
        "is editable via AR010_DIGITAL_COMPETENCY_PASS_THRESHOLD in this "
        "module.",
        source_field="payload.percentage",
        threshold=f">= {AR010_DIGITAL_COMPETENCY_PASS_THRESHOLD}%",
        code_path="apps/mel/tracking/services.py:_emit_ar010_indicators (quiz_submitted branch)",
    ),
    AR010Indicator(
        code="rep-enrolment-growth",
        label="Enrolment growth (30-day window)",
        event_trigger="daily snapshot (Celery beat)",
        derivation_rule="Daily beat compares rolling 30-day enrolments to "
        "the prior 30-day window. Emits one indicator per ISO date with "
        "value = current count and payload carrying both windows so the "
        "M&EL dashboard can render the growth ratio.",
        source_field="Enrolment.enrolled_at (rolling window)",
        threshold="windowed delta — value=current_30d",
        code_path="apps/mel/tracking/services.py:compute_rep_enrolment_growth",
    ),
)


# ---------------------------------------------------------------------------
# Reference-page helpers
# ---------------------------------------------------------------------------


@dataclass(frozen=True)
class CanonicalRow:
    event_type: str
    indicator_code: str
    label: str


def _humanize_indicator(code: str) -> str:
    """Render an indicator code as a Title Case label."""
    return code.replace("rep-", "REP · ").replace("-", " ").title().replace("Rep · ", "REP · ")


def iter_canonical_rows() -> Iterator[CanonicalRow]:
    """Yield canonical event→indicator rows for the reference page."""
    for event_type, code in EVENT_INDICATOR_MAP.items():
        yield CanonicalRow(
            event_type=event_type,
            indicator_code=code,
            label=_humanize_indicator(code),
        )


def iter_derived_rows() -> Iterator[AR010Indicator]:
    """Yield AR010 derived indicators for the reference page."""
    for ind in AR010_INDICATORS:
        yield ind
