//! Predicate explanation pass.
//!
//! Mirrors [`Predicate::evaluate`] but builds an [`ExplainNode`] tree that
//! records the pass/fail decision and resolved operand values at every node.
//! Used by the editor to render "this gate failed because X."
//!
//! See `core/sdk/src/predicate/mod.rs` for module-level docs and
//! `docs/superpowers/specs/2026-04-29-gated-trait-design.md` for design intent.
//!
//! ## Helper sharing
//!
//! `apply_op`, `resolve_ref`, `set_resolve`, the `Read` enum, and `EvalFrames`
//! are promoted to `pub(super)` in [`super::eval`] so the explain pass mirrors
//! the evaluator's semantics exactly. `read_value` is *not* reused — explain
//! needs to retain the resolved operand fields (entity, key, mode, value)
//! after the read for diagnostic display, which the evaluator's collapsed
//! `Option<Read>` return type does not preserve.

use super::eval::{apply_op, resolve_ref, set_resolve, EvalContext, EvalFrames, Read};
use super::types::{
    EntityRef, EntitySet, MetricKind, Op, PoolMode, Predicate, SettingKey, SourceKey, Value,
};
use crate::{EntityId, EntityTypeId, PoolId};

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// Tree mirror of a [`Predicate`] decorated with the resolved-value/pass-fail
/// data necessary to explain *why* the predicate produced its boolean result.
///
/// Each node carries the boolean it would have contributed to
/// `Predicate::evaluate`, plus enough resolution data for the editor to render
/// a human-readable trace.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "schema", schema(no_recursion))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ExplainNode {
    All {
        children: Vec<ExplainNode>,
        result: bool,
    },
    Any {
        children: Vec<ExplainNode>,
        result: bool,
    },
    Not {
        child: Box<ExplainNode>,
        result: bool,
    },
    Exists {
        ref_: EntityRef,
        resolved_to: Option<EntityId>,
        result: bool,
    },
    AnyOf {
        set: ResolvedSet,
        body_evals: Vec<ExplainNode>,
        result: bool,
    },
    AllOf {
        set: ResolvedSet,
        body_evals: Vec<ExplainNode>,
        result: bool,
    },
    Cmp {
        lhs: ResolvedValue,
        op: Op,
        rhs: ResolvedValue,
        result: bool,
    },
    RefEq {
        lhs_ref: EntityRef,
        lhs_resolved: Option<EntityId>,
        rhs_ref: EntityRef,
        rhs_resolved: Option<EntityId>,
        result: bool,
    },
    /// WI1-FU3: per-seat visibility check.
    VisibleTo {
        observer_ref: EntityRef,
        observer_resolved: Option<EntityId>,
        target_ref: EntityRef,
        target_resolved: Option<EntityId>,
        result: bool,
    },
    /// UC-29-E1: ownership match between Self_'s owner seat and the
    /// recipient (observer) seat. Either side `None` means the check
    /// short-circuited (no observer seat, or Self_ not Ownable).
    OwnerSeatMatchesObserver {
        self_owner: Option<crate::types::SeatId>,
        observer:   Option<crate::types::SeatId>,
        result: bool,
    },
    /// UC-29-E1: role check on the recipient (observer) seat.
    /// `actual_role` is `None` when there's no observer seat.
    ObserverHasRole {
        required: crate::config::SeatRole,
        observer: Option<crate::types::SeatId>,
        actual_role: Option<crate::config::SeatRole>,
        result: bool,
    },
}

impl ExplainNode {
    /// Boolean this node contributed to the overall predicate result.
    pub fn result(&self) -> bool {
        match self {
            ExplainNode::All { result, .. }
            | ExplainNode::Any { result, .. }
            | ExplainNode::Not { result, .. }
            | ExplainNode::Exists { result, .. }
            | ExplainNode::AnyOf { result, .. }
            | ExplainNode::AllOf { result, .. }
            | ExplainNode::Cmp { result, .. }
            | ExplainNode::RefEq { result, .. }
            | ExplainNode::VisibleTo { result, .. }
            | ExplainNode::OwnerSeatMatchesObserver { result, .. }
            | ExplainNode::ObserverHasRole { result, .. } => *result,
        }
    }
}

/// Resolved view of an [`EntitySet`]: the original set descriptor plus the
/// concrete members it expanded to at evaluation time.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResolvedSet {
    pub set: EntitySet,
    pub members: Vec<EntityId>,
}

/// A [`Value`] paired with the data it resolved to during evaluation.
///
/// `None` fields encode the NULL operands that cause [`Predicate::Cmp`] to
/// short-circuit to `false`.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ResolvedValue {
    Const(i64),
    Id(Option<EntityId>),
    Type(Option<EntityTypeId>),
    Pool {
        entity: Option<EntityId>,
        key: PoolId,
        mode: PoolMode,
        value: Option<i64>,
    },
    /// `Value::TypeOf(ref)` resolves to a Type variant; this is only here to
    /// preserve the [`EntityRef`] for diagnostic display if needed.
    TypeOf {
        ref_: EntityRef,
        resolved: Option<EntityTypeId>,
    },
    /// Resolved view of a [`Value::Setting`] read. `value` is `None` when the
    /// setting is not configured (mirrors the evaluator's NULL → Cmp-false rule).
    Setting {
        key: SettingKey,
        value: Option<i64>,
    },
    /// Resolved view of a [`Value::SourceLoaded`] read. Always defined
    /// (true/false). The diagnostic surface uses `loaded` to render
    /// "(active)" / "(not active)" in source-gated explanations.
    SourceLoaded {
        key: SourceKey,
        loaded: bool,
    },
    /// Resolved view of a [`Value::Distance`] read. `from_entity` /
    /// `to_entity` are NULL when the corresponding [`EntityRef`] did not
    /// resolve at evaluation time; `value` is NULL when either ref is NULL
    /// or when the cache had no distance for the (from, to, kind) tuple.
    Distance {
        kind: MetricKind,
        from_entity: Option<EntityId>,
        to_entity: Option<EntityId>,
        value: Option<i64>,
    },
}

impl Predicate {
    /// Build an [`ExplainNode`] tree mirroring this predicate's evaluation
    /// against `ctx`, recording pass/fail and resolved operand values at every
    /// node.
    ///
    /// Agrees with [`Predicate::evaluate`]: `self.explain(ctx).result()` is
    /// always equal to `self.evaluate(ctx)`.
    pub fn explain<C: EvalContext>(&self, ctx: &C) -> ExplainNode {
        let mut frames = EvalFrames::default();
        explain_node(self, ctx, &mut frames)
    }
}

fn explain_node<C: EvalContext>(
    p: &Predicate,
    ctx: &C,
    fr: &mut EvalFrames,
) -> ExplainNode {
    match p {
        Predicate::All(xs) => {
            let children: Vec<ExplainNode> =
                xs.iter().map(|q| explain_node(q, ctx, fr)).collect();
            let result = children.iter().all(|c| c.result());
            ExplainNode::All { children, result }
        }
        Predicate::Any(xs) => {
            let children: Vec<ExplainNode> =
                xs.iter().map(|q| explain_node(q, ctx, fr)).collect();
            let result = children.iter().any(|c| c.result());
            ExplainNode::Any { children, result }
        }
        Predicate::Not(q) => {
            let child = explain_node(q, ctx, fr);
            let result = !child.result();
            ExplainNode::Not {
                child: Box::new(child),
                result,
            }
        }
        Predicate::Exists(r) => {
            let resolved_to = resolve_ref(r, ctx, fr);
            ExplainNode::Exists {
                ref_: r.clone(),
                resolved_to,
                result: resolved_to.is_some(),
            }
        }
        Predicate::AnyOf(set, body) => {
            let members = set_resolve(set, ctx, fr);
            let mut body_evals = Vec::with_capacity(members.len());
            for id in &members {
                fr.it_stack.push(*id);
                body_evals.push(explain_node(body, ctx, fr));
                fr.it_stack.pop();
            }
            let result = body_evals.iter().any(|n| n.result());
            ExplainNode::AnyOf {
                set: ResolvedSet {
                    set: set.clone(),
                    members,
                },
                body_evals,
                result,
            }
        }
        Predicate::AllOf(set, body) => {
            let members = set_resolve(set, ctx, fr);
            let mut body_evals = Vec::with_capacity(members.len());
            for id in &members {
                fr.it_stack.push(*id);
                body_evals.push(explain_node(body, ctx, fr));
                fr.it_stack.pop();
            }
            let result = body_evals.iter().all(|n| n.result());
            ExplainNode::AllOf {
                set: ResolvedSet {
                    set: set.clone(),
                    members,
                },
                body_evals,
                result,
            }
        }
        Predicate::Cmp(a, op, b) => {
            let lhs = resolve_value(a, ctx, fr);
            let rhs = resolve_value(b, ctx, fr);
            let result = match (read_from(&lhs), read_from(&rhs)) {
                (Some(av), Some(bv)) => apply_op(av, *op, bv),
                _ => false,
            };
            ExplainNode::Cmp {
                lhs,
                op: *op,
                rhs,
                result,
            }
        }
        Predicate::RefEq(a, b) => {
            let lhs_resolved = resolve_ref(a, ctx, fr);
            let rhs_resolved = resolve_ref(b, ctx, fr);
            let result = matches!(
                (lhs_resolved, rhs_resolved),
                (Some(av), Some(bv)) if av == bv,
            );
            ExplainNode::RefEq {
                lhs_ref: a.clone(),
                lhs_resolved,
                rhs_ref: b.clone(),
                rhs_resolved,
                result,
            }
        }
        Predicate::VisibleTo { observer, target } => {
            let observer_resolved = resolve_ref(observer, ctx, fr);
            let target_resolved = resolve_ref(target, ctx, fr);
            let result = match (observer_resolved, target_resolved) {
                (Some(obs), Some(tgt)) => ctx.visible(obs, tgt),
                _ => false,
            };
            ExplainNode::VisibleTo {
                observer_ref: observer.clone(),
                observer_resolved,
                target_ref: target.clone(),
                target_resolved,
                result,
            }
        }
        Predicate::OwnerSeatMatchesObserver => {
            let observer = ctx.observer_seat();
            let self_owner = ctx.resolve_self().and_then(|e| ctx.owner_seat_of(e));
            let result = matches!(
                (self_owner, observer),
                (Some(a), Some(b)) if a == b,
            );
            ExplainNode::OwnerSeatMatchesObserver { self_owner, observer, result }
        }
        Predicate::ObserverHasRole(role) => {
            let observer = ctx.observer_seat();
            let actual_role = observer.map(|s| ctx.role_of(s));
            let result = actual_role.map(|r| r == *role).unwrap_or(false);
            ExplainNode::ObserverHasRole {
                required: *role,
                observer,
                actual_role,
                result,
            }
        }
    }
}

/// Resolve a [`Value`] into its diagnostic [`ResolvedValue`] companion.
///
/// Mirrors `super::eval::read_value` but retains the input descriptors
/// (entity, key, mode, ref_) so the editor can render the original expression
/// alongside its concrete evaluation.
fn resolve_value<C: EvalContext>(v: &Value, ctx: &C, fr: &EvalFrames) -> ResolvedValue {
    match v {
        Value::Const(n) => ResolvedValue::Const(*n),
        Value::ConstId(id) => ResolvedValue::Id(Some(*id)),
        Value::ConstType(t) => ResolvedValue::Type(Some(*t)),
        Value::TypeOf(r) => {
            let resolved = resolve_ref(r, ctx, fr).and_then(|id| ctx.type_of(id));
            ResolvedValue::TypeOf {
                ref_: r.clone(),
                resolved,
            }
        }
        Value::Pool { entity, key, mode } => {
            let resolved_entity = resolve_ref(entity, ctx, fr);
            let value = resolved_entity.and_then(|id| ctx.pool(id, key, *mode));
            ResolvedValue::Pool {
                entity: resolved_entity,
                key: key.clone(),
                mode: *mode,
                value,
            }
        }
        Value::Setting(key) => ResolvedValue::Setting {
            key: key.clone(),
            value: ctx.setting(key),
        },
        Value::SourceLoaded(key) => ResolvedValue::SourceLoaded {
            key: key.clone(),
            loaded: ctx.source_loaded(key),
        },
        Value::Distance { from, to, kind } => {
            let from_entity = resolve_ref(from, ctx, fr);
            let to_entity = resolve_ref(to, ctx, fr);
            // Mirror the evaluator's NULL-propagation: only call
            // `ctx.distance` when both refs resolved.
            let value = match (from_entity, to_entity) {
                (Some(a), Some(b)) => ctx.distance(a, b, *kind),
                _ => None,
            };
            ResolvedValue::Distance {
                kind: *kind,
                from_entity,
                to_entity,
                value,
            }
        }
    }
}

/// Convert a [`ResolvedValue`] back into the evaluator's `Read` form for
/// `apply_op`. Returns `None` when any operand sub-field is NULL — matching
/// the evaluator's NULL → false rule.
fn read_from(rv: &ResolvedValue) -> Option<Read> {
    match rv {
        ResolvedValue::Const(n) => Some(Read::Int(*n)),
        ResolvedValue::Id(Some(id)) => Some(Read::Id(*id)),
        ResolvedValue::Id(None) => None,
        ResolvedValue::Type(Some(t)) => Some(Read::Type(*t)),
        ResolvedValue::Type(None) => None,
        ResolvedValue::Pool {
            value: Some(n), ..
        } => Some(Read::Int(*n)),
        ResolvedValue::Pool { value: None, .. } => None,
        ResolvedValue::TypeOf {
            resolved: Some(t), ..
        } => Some(Read::Type(*t)),
        ResolvedValue::TypeOf { resolved: None, .. } => None,
        ResolvedValue::Setting {
            value: Some(n), ..
        } => Some(Read::Int(*n)),
        ResolvedValue::Setting { value: None, .. } => None,
        // SourceLoaded is always defined — true/false → 1/0.
        ResolvedValue::SourceLoaded { loaded, .. } => {
            Some(Read::Int(if *loaded { 1 } else { 0 }))
        }
        ResolvedValue::Distance {
            value: Some(n), ..
        } => Some(Read::Int(*n)),
        ResolvedValue::Distance { value: None, .. } => None,
    }
}

// ---------------------------------------------------------------------------
// UC-29 Phase A — human-readable summary of a failing ExplainNode.
// ---------------------------------------------------------------------------

/// Produce a one-line human-readable summary of why the predicate evaluated
/// to false. Returns an empty string when the predicate passed.
///
/// Strategy is kind-driven (UC-29 Phase A):
/// - `All` / `AllOf` recurses into the first failing child.
/// - `Any` / `AnyOf` returns `"needs one of: <m1>, <m2>, ..."` over the
///   per-branch messages of *failing* children (since the node only fails
///   when every branch failed, those messages are the disjunction).
/// - `Not(child)` returns `"must not: <child-message>"` — the child must
///   have passed for `Not` to fail, so we recurse as though the child were
///   the failure we want to describe.
/// - Leaves render their resolved values directly.
pub fn flatten_explain(node: &ExplainNode) -> String {
    if node.result() {
        return String::new();
    }
    render_failure(node)
}

/// Render a failing node. Caller must ensure `!node.result()`.
fn render_failure(node: &ExplainNode) -> String {
    match node {
        ExplainNode::All { children, .. } => children
            .iter()
            .find(|c| !c.result())
            .map(render_failure)
            .unwrap_or_default(),
        ExplainNode::AllOf { body_evals, .. } => body_evals
            .iter()
            .find(|c| !c.result())
            .map(render_failure)
            .unwrap_or_default(),
        ExplainNode::Any { children, .. } => render_disjunction(children),
        ExplainNode::AnyOf { body_evals, .. } => render_disjunction(body_evals),
        ExplainNode::Not { child, .. } => format!("must not: {}", render_passing_child(child)),
        ExplainNode::Cmp { lhs, op, rhs, .. } => {
            format!("{} {} {}", render_value(lhs), render_op(*op), render_value(rhs))
        }
        ExplainNode::Exists { ref_, .. } => {
            format!("entity {} must exist", render_entity_ref(ref_))
        }
        ExplainNode::RefEq { lhs_ref, rhs_ref, .. } => format!(
            "{} must equal {}",
            render_entity_ref(lhs_ref),
            render_entity_ref(rhs_ref)
        ),
        ExplainNode::VisibleTo {
            observer_ref,
            target_ref,
            ..
        } => format!(
            "{} must be visible to {}",
            render_entity_ref(target_ref),
            render_entity_ref(observer_ref)
        ),
        ExplainNode::OwnerSeatMatchesObserver { self_owner, observer, .. } => {
            // UC-29-E1 — "you must own this." Phrased from the recipient's POV.
            match (self_owner, observer) {
                (None, _) => "you must own this (no owner)".to_string(),
                (_, None) => "you must own this (no recipient seat)".to_string(),
                _ => "you must own this".to_string(),
            }
        }
        ExplainNode::ObserverHasRole { required, actual_role, .. } => match actual_role {
            None => format!("requires {required:?} role (no recipient seat)"),
            Some(actual) => format!("requires {required:?} role (you are {actual:?})"),
        },
    }
}

/// `Not(child)` only fails when its child *passed*. We still want a useful
/// message describing what the child asserted, so we render the child as
/// though we were describing it directly.
fn render_passing_child(node: &ExplainNode) -> String {
    // Negating a passed node: render its assertion verbatim. For an
    // AND/OR/exists/etc., reuse render_failure's logic by flipping the
    // expected result — but we only care about Cmp/leaf for now; nested
    // boolean negations should be rare in plugin-authored `when` predicates.
    match node {
        ExplainNode::Cmp { lhs, op, rhs, .. } => {
            format!("{} {} {}", render_value(lhs), render_op(*op), render_value(rhs))
        }
        ExplainNode::Exists { ref_, .. } => format!("entity {} exists", render_entity_ref(ref_)),
        ExplainNode::RefEq { lhs_ref, rhs_ref, .. } => format!(
            "{} equals {}",
            render_entity_ref(lhs_ref),
            render_entity_ref(rhs_ref)
        ),
        ExplainNode::VisibleTo {
            observer_ref,
            target_ref,
            ..
        } => format!(
            "{} is visible to {}",
            render_entity_ref(target_ref),
            render_entity_ref(observer_ref)
        ),
        // For nested logic inside Not, fall back to render_failure's body
        // (the recursive nature still produces a readable string).
        ExplainNode::All { children, .. } => children
            .iter()
            .map(render_passing_child)
            .collect::<Vec<_>>()
            .join(" and "),
        ExplainNode::AllOf { body_evals, .. } => body_evals
            .iter()
            .map(render_passing_child)
            .collect::<Vec<_>>()
            .join(" and "),
        ExplainNode::Any { children, .. } => children
            .iter()
            .filter(|c| c.result())
            .map(render_passing_child)
            .collect::<Vec<_>>()
            .join(" or "),
        ExplainNode::AnyOf { body_evals, .. } => body_evals
            .iter()
            .filter(|c| c.result())
            .map(render_passing_child)
            .collect::<Vec<_>>()
            .join(" or "),
        ExplainNode::Not { child, .. } => format!("not ({})", render_failure(child)),
        ExplainNode::OwnerSeatMatchesObserver { .. } => "you own this".to_string(),
        ExplainNode::ObserverHasRole { required, .. } => format!("has {required:?} role"),
    }
}

fn render_disjunction(children: &[ExplainNode]) -> String {
    let parts: Vec<String> = children
        .iter()
        .filter(|c| !c.result())
        .map(render_failure)
        .collect();
    if parts.is_empty() {
        // Should not happen for a failing Any/AnyOf, but be defensive.
        return String::from("no available option");
    }
    format!("needs one of: {}", parts.join(", "))
}

fn render_value(v: &ResolvedValue) -> String {
    match v {
        ResolvedValue::Const(n) => n.to_string(),
        ResolvedValue::Id(Some(id)) => format!("#{}", id.raw()),
        ResolvedValue::Id(None) => String::from("?"),
        ResolvedValue::Type(Some(t)) => format!("type #{}", t.raw()),
        ResolvedValue::Type(None) => String::from("?type"),
        ResolvedValue::Pool {
            key, value, mode, ..
        } => {
            let suffix = match mode {
                crate::predicate::types::PoolMode::Base => " (base)",
                crate::predicate::types::PoolMode::Effective => "",
            };
            match value {
                Some(n) => format!("pool {} ({}){}", key.key(), n, suffix),
                None => format!("pool {} (?){}", key.key(), suffix),
            }
        }
        ResolvedValue::TypeOf { ref_, resolved } => match resolved {
            Some(t) => format!("typeof({}) = #{}", render_entity_ref(ref_), t.raw()),
            None => format!("typeof({}) = ?", render_entity_ref(ref_)),
        },
        ResolvedValue::Setting { key, value } => match value {
            Some(n) => format!("setting {} ({})", key.key(), n),
            None => format!("setting {} (unset)", key.key()),
        },
        ResolvedValue::SourceLoaded { key, loaded } => format!(
            "source {} ({})",
            key.key(),
            if *loaded { "active" } else { "not active" }
        ),
        ResolvedValue::Distance {
            kind, value, ..
        } => match value {
            Some(n) => format!("distance[{kind:?}] ({n})"),
            None => format!("distance[{kind:?}] (?)"),
        },
    }
}

fn render_op(op: super::types::Op) -> &'static str {
    use super::types::Op;
    match op {
        Op::Lt => "<",
        Op::Le => "<=",
        Op::Eq => "==",
        Op::Ne => "!=",
        Op::Ge => ">=",
        Op::Gt => ">",
    }
}

fn render_entity_ref(r: &super::types::EntityRef) -> String {
    use super::types::EntityRef;
    match r {
        EntityRef::Self_ => "self".into(),
        EntityRef::Owner => "owner".into(),
        EntityRef::Observer => "observer".into(),
        EntityRef::Target => "target".into(),
        EntityRef::It => "it".into(),
        EntityRef::ById(id) => format!("#{}", id.raw()),
    }
}
