#
#   Imandra Inc.
#
#   speclogician/presentation/builders/state_diff.py
#

from __future__ import annotations

from typing import Any

from speclogician.presentation.ctx import RenderCtx
from speclogician.presentation.models.state_diff import (
    StateDiffPM,
    DiffSectionPM,
    DiffRowPM,
    ProgressMetricsPM,
)
from speclogician.state.diff import StateDiff, ValueDiff, ComparisonOutcome


def _outcome(vd: ValueDiff[Any]) -> ComparisonOutcome:
    if vd.comp_func:
        return vd.comp_func(vd.before, vd.after)
    if vd.before == vd.after:
        return ComparisonOutcome.NO_CHANGE
    return ComparisonOutcome.UNKNOWN


def _message(vd: ValueDiff[Any]) -> str:
    oc = _outcome(vd)
    b = vd.before
    a = vd.after

    match oc:
        case ComparisonOutcome.UNKNOWN:
            return f"{vd.label}: Unknown: {b} -> {a}"
        case ComparisonOutcome.NO_CHANGE:
            return f"{vd.label}: No change: {b} -> {a}"
        case ComparisonOutcome.NO_CHANGE_GOOD:
            return f"{vd.label}: No change (good): {b} -> {a}"
        case ComparisonOutcome.NO_CHANGE_BAD:
            return f"{vd.label}: No change (bad): {b} -> {a}"
        case ComparisonOutcome.IMPROVED:
            return f"{vd.label}: Improvement: {b} -> {a}"
        case ComparisonOutcome.DECLINED:
            return f"{vd.label}: Decline: {b} -> {a}"
        case _:
            return f"{vd.label}: {b} -> {a}"


def _row(vd: ValueDiff[Any]) -> DiffRowPM:
    return DiffRowPM(
        label=vd.label,
        before=vd.before,
        after=vd.after,
        outcome=_outcome(vd),
        message=_message(vd),
    )


def _pct(numerator: int | float, denominator: int | float) -> float | None:
    if denominator <= 0:
        return None
    return round(100.0 * numerator / denominator, 1)


def _build_progress_metrics(sd: StateDiff) -> ProgressMetricsPM:
    """Compute percentage-based progress metrics from the current (new) state.

    The ``after`` field of each ValueDiff holds the *new* state's values
    (calc_diff(new, old) convention).
    """

    # scenarios_valid_pct: scenarios that are neither missing nor inconsistent
    sc_total = sd.num_sc_total.after
    sc_bad = sd.num_sc_missing.after + sd.num_sc_inconsistent.after
    scenarios_valid_pct = _pct(max(sc_total - sc_bad, 0), sc_total) if sc_total else None

    # reachability_pct: reachable / total scenario nodes
    reach_nodes = sd.reach_num_nodes.after
    reach_reachable = sd.reach_num_reachable.after
    reachability_pct = _pct(reach_reachable, reach_nodes)

    # components_valid_pct: (valid predicates + valid transitions) / total
    comps_total = sd.num_preds_total.after + sd.num_trans_total.after
    comps_valid = sd.num_preds_valid_logic.after + sd.num_trans_valid_logic.after
    components_valid_pct = _pct(comps_valid, comps_total)

    # linked_artifacts_pct: matched components / total components
    comps_matched = sd.num_preds_matched.after + sd.num_trans_matched.after
    linked_artifacts_pct = _pct(comps_matched, comps_total)

    # complement_regions: raw count (lower is better, 0 = full coverage)
    comp_regions = sd.num_comp_regions_total.after

    return ProgressMetricsPM(
        scenarios_valid_pct=scenarios_valid_pct,
        reachability_pct=reachability_pct,
        components_valid_pct=components_valid_pct,
        linked_artifacts_pct=linked_artifacts_pct,
        complement_regions=comp_regions,
    )


def build_state_diff_pm(sd: StateDiff, ctx: RenderCtx) -> StateDiffPM:
    sections: list[DiffSectionPM] = []

    def add(title: str, vds: list[ValueDiff[Any]]) -> None:
        rows = [_row(vd) for vd in vds if vd.is_different()]
        if rows:
            sections.append(DiffSectionPM(title=title, rows=rows))

    # ---- Domain model ----
    add("Domain Model: Base", [sd.base_status, sd.base_has_state, sd.base_has_action])

    add(
        "Domain Model: State predicates",
        [
            sd.num_state_preds_total,
            sd.num_state_preds_matched,
            sd.num_state_preds_valid_logic,
            sd.num_state_preds_errored,
        ],
    )

    add(
        "Domain Model: Action predicates",
        [
            sd.num_action_preds_total,
            sd.num_action_preds_matched,
            sd.num_action_preds_valid_logic,
            sd.num_action_preds_errored,
        ],
    )

    add(
        "Domain Model: Predicates (total)",
        [
            sd.num_preds_total,
            sd.num_preds_matched,
            sd.num_preds_valid_logic,
            sd.num_preds_errored,
        ],
    )

    add(
        "Domain Model: Transitions",
        [
            sd.num_trans_total,
            sd.num_trans_matched,
            sd.num_trans_valid_logic,
            sd.num_trans_errored,
        ],
    )

    # ---- Scenarios + conflicts ----
    add(
        "Scenarios",
        [
            sd.num_sc_total,
            sd.num_sc_missing,
            sd.num_sc_matched,
            sd.num_sc_inconsistent,
        ],
    )

    add(
        "Scenario conflicts",
        [
            sd.num_sc_conflicted,
            sd.num_sc_overlap,
            sd.num_sc_consumed,
        ],
    )

    # ---- Artifacts ----
    add(
        "Test traces",
        [
            sd.num_test_traces_total,
            sd.num_test_traces_logic_good,
            sd.num_test_traces_matched,
        ],
    )

    add(
        "Log traces",
        [
            sd.num_log_traces_total,
            sd.num_log_traces_logic_good,
            sd.num_log_traces_matched,
        ],
    )

    # ---- Progress metrics (computed from "after" values) ----
    progress = _build_progress_metrics(sd)

    return StateDiffPM(has_changes=bool(sections), sections=sections, progress=progress)