#
#   Imandra Inc.
#
#   speclogician/presentation/api.py
#

from __future__ import annotations

# --- Controls how we print things out (and also contains some data)
from speclogician.presentation.ctx import RenderCtx

# --- Underlying models
from speclogician.modeling.predicates import ActionPredicate, StatePredicate, Transition
from speclogician.modeling.domain import DomainModel
from speclogician.modeling.spec import Spec
from speclogician.modeling.complement import ScenarioComplement
from speclogician.data.traces import TestTrace, LogTrace
from speclogician.data.refs import DocRef, SrcCodeRef
from speclogician.data.container import ArtifactContainer, SmartFindResults
from speclogician.state.inst import StateInstance
from speclogician.state.diff import StateDiff
from speclogician.modeling.scenario import Scenario
from speclogician.state.recommender import Recommendation
from speclogician.modeling.trace_reachability import TraceReachabilityAnalysis

# --- Builders
from speclogician.presentation.builders.predicate import (
    build_predicate_pm,
    build_transition_pm,
)
from speclogician.presentation.builders.domain import build_domain_model_pm
from speclogician.presentation.builders.trace import build_test_trace_pm, build_log_trace_pm
from speclogician.presentation.builders.spec import build_spec_pm
from speclogician.presentation.builders.scenario import build_scenario_pm
from speclogician.presentation.builders.data_artifact import build_doc_ref_pm, build_src_code_ref_pm
from speclogician.presentation.builders.container import build_artifact_container_pm
from speclogician.presentation.builders.smart_find import build_smart_find_pm
from speclogician.presentation.builders.state_instance import build_state_instance_pm
from speclogician.presentation.builders.state_diff import build_state_diff_pm
from speclogician.presentation.builders.instances_list import build_instances_list_pm
from speclogician.presentation.builders.recommendations import build_recommendations_pm
from speclogician.presentation.builders.scenario_complement import build_scenario_complement_pm
from speclogician.presentation.builders.trace_reachability import build_trace_reachability_pm

# --- Renderers
from speclogician.presentation.renderers.predicate import (
    render_predicate,
    render_transition,
)
from speclogician.presentation.renderers.trace import render_test_trace, render_log_trace
from speclogician.presentation.renderers.domain import render_domain_model
from speclogician.presentation.renderers.spec import render_spec
from speclogician.presentation.renderers.scenario import render_scenario
from speclogician.presentation.renderers.data_artifact import render_doc_ref, render_src_code_ref
from speclogician.presentation.renderers.container import render_artifact_container
from speclogician.presentation.renderers.smart_find import render_smart_find
from speclogician.presentation.renderers.state_instance import render_state_instance
from speclogician.presentation.renderers.state_diff import render_state_diff
from speclogician.presentation.renderers.instances_list import render_instances_list
from speclogician.presentation.renderers.recommendations import render_recommendations
from speclogician.presentation.renderers.scenario_complement import render_scenario_complement
from speclogician.presentation.renderers.trace_reachability import render_trace_reachability


def present_predicate(
    p: StatePredicate | ActionPredicate,
    *,
    ctx: RenderCtx,
    json_only: bool,
):
    pm = build_predicate_pm(p, ctx)
    if json_only:
        return pm.model_dump()
    return render_predicate(pm, ctx=ctx)


def present_transition(
    t: Transition,
    *,
    ctx: RenderCtx,
    json_only: bool,
):
    pm = build_transition_pm(t, ctx)
    if json_only:
        return pm.model_dump()
    return render_transition(pm, ctx=ctx)


def present_domain_model(
    dm: DomainModel,
    *,
    ctx: RenderCtx,
    json_only: bool,
):
    pm = build_domain_model_pm(dm, ctx)
    if json_only:
        return pm.model_dump()
    return render_domain_model(pm, ctx=ctx)


def present_spec(
    spec: Spec,
    *,
    ctx: RenderCtx,
    json_only: bool,
):
    pm = build_spec_pm(spec, ctx)
    if json_only:
        return pm.model_dump()
    return render_spec(pm, ctx=ctx)


def present_scenario_complement(
    comp: ScenarioComplement,
    *,
    ctx: RenderCtx,
    json_only: bool,
):
    pm = build_scenario_complement_pm(comp, ctx)
    if json_only:
        return pm.model_dump()
    return render_scenario_complement(pm, ctx=ctx)


def present_trace(
    tr: TestTrace | LogTrace,
    *,
    ctx: RenderCtx,
    json_only: bool,
):
    if isinstance(tr, TestTrace):
        pm = build_test_trace_pm(tr, ctx)
        if json_only:
            return pm.model_dump()
        return render_test_trace(pm, ctx=ctx)

    if isinstance(tr, LogTrace):
        pm = build_log_trace_pm(tr, ctx)
        if json_only:
            return pm.model_dump()
        return render_log_trace(pm, ctx=ctx)

    raise TypeError(f"Unsupported trace type: {type(tr).__name__}")


def present_data_artifact(
    a: DocRef | SrcCodeRef,
    *,
    ctx: RenderCtx,
    json_only: bool,
):
    if isinstance(a, DocRef):
        pm = build_doc_ref_pm(a, ctx)
        if json_only:
            return pm.model_dump()
        return render_doc_ref(pm, ctx=ctx)

    if isinstance(a, SrcCodeRef):
        pm = build_src_code_ref_pm(a, ctx)
        if json_only:
            return pm.model_dump()
        return render_src_code_ref(pm, ctx=ctx)

    raise TypeError(f"Unsupported data artifact type: {type(a).__name__}")


def present_artifact_container(
    cont: ArtifactContainer,
    *,
    ctx: RenderCtx,
    json_only: bool,
):
    pm = build_artifact_container_pm(cont, ctx)
    if json_only:
        return pm.model_dump()
    return render_artifact_container(pm, ctx=ctx)


def present_smart_find(
    res: SmartFindResults,
    *,
    ctx: RenderCtx,
    json_only: bool,
):
    pm = build_smart_find_pm(res, ctx)
    if json_only:
        return pm.model_dump()
    return render_smart_find(pm, ctx=ctx)


def present_state_diff(
    sd: StateDiff,
    *,
    ctx: RenderCtx,
    json_only: bool,
):
    pm = build_state_diff_pm(sd, ctx)
    if json_only:
        return pm.model_dump()
    return render_state_diff(pm, ctx=ctx)


def present_state_instance(
    si: StateInstance,
    *,
    ctx: RenderCtx,
    json_only: bool,
):
    pm = build_state_instance_pm(si, ctx)
    if json_only:
        return pm.model_dump()
    return render_state_instance(pm, ctx=ctx)


def present_scenario(
    sc: Scenario,
    *,
    ctx: RenderCtx,
    json_only: bool,
):
    pm = build_scenario_pm(sc, ctx)
    if json_only:
        return pm.model_dump()
    return render_scenario(pm, ctx=ctx)


def present_instances_list(
    states: list[StateInstance],
    *,
    ctx: RenderCtx,
    json_only: bool,
):
    pm = build_instances_list_pm(states, ctx)
    if json_only:
        return pm.model_dump()
    return render_instances_list(pm, ctx=ctx)


def present_recommendations(
    recs: list[Recommendation],
    *,
    ctx: RenderCtx,
    json_only: bool,
):
    pm = build_recommendations_pm(recs, ctx)
    return pm.model_dump() if json_only else render_recommendations(pm, ctx=ctx)

def present_trace_reachability(
    ana: TraceReachabilityAnalysis | None,
    *,
    art_cont: ArtifactContainer,
    ctx: RenderCtx,
    json_only: bool,
):
    """
    Present the trace-reachability analysis.

    NOTE: Needs `art_cont` to derive human-friendly trace labels (test name / log filename).
    """
    pm = build_trace_reachability_pm(ana, art_cont, ctx)
    if pm is None:
        return None if json_only else None
    return pm.model_dump() if json_only else render_trace_reachability(pm, ctx=ctx)