//! Shared test helpers used across multiple integration test files.
//!
//! Import with `mod common;` and then `use common::*;`.

use pickaxe_rs::framework::argument::{Argument, ArgumentId};
use pickaxe_rs::prelude::*;
use pickaxe_rs::system::knowledge::{Premise, PremiseKind};
use std::collections::BTreeSet;

// ---------------------------------------------------------------------------
// Mock orderings
// ---------------------------------------------------------------------------

/// Argument ordering where all arguments are equally preferred.
#[derive(Debug)]
#[allow(dead_code)]
pub struct EqualOrdering;

impl ArgumentOrdering for EqualOrdering {
    fn compare(&self, _a: ArgumentId, _b: ArgumentId, _store: &ArgumentStore) -> Preference {
        Preference::Equal
    }
}

/// Argument ordering that always returns StrictlyDispreferred.
#[derive(Debug)]
#[allow(dead_code)]
pub struct AlwaysDispreferredOrdering;

impl ArgumentOrdering for AlwaysDispreferredOrdering {
    fn compare(&self, _a: ArgumentId, _b: ArgumentId, _store: &ArgumentStore) -> Preference {
        Preference::StrictlyDispreferred
    }
}

/// Argument ordering based on numeric ID: lower ID = more preferred.
#[derive(Debug)]
#[allow(dead_code)]
pub struct IdBasedOrdering;

impl ArgumentOrdering for IdBasedOrdering {
    fn compare(&self, a: ArgumentId, b: ArgumentId, _store: &ArgumentStore) -> Preference {
        if a.0 < b.0 {
            Preference::StrictlyPreferred
        } else if a.0 > b.0 {
            Preference::StrictlyDispreferred
        } else {
            Preference::Equal
        }
    }
}

// ---------------------------------------------------------------------------
// AF construction
// ---------------------------------------------------------------------------

/// Build a simple abstract framework from dummy premise arguments and
/// defeat pairs `(attacker, target)`.
#[allow(dead_code)]
pub fn build_af(n: usize, defeats: &[(usize, usize)]) -> AbstractFramework {
    let mut store = ArgumentStore::new();
    for i in 0..n {
        store.add(Argument::Premise {
            id: ArgumentId(i),
            premise: Premise {
                formula: Formula::prop(format!("p{}", i)),
                kind: PremiseKind::Ordinary,
            },
        });
    }

    let defeat_structs: Vec<Defeat> = defeats
        .iter()
        .map(|&(a, b)| Defeat {
            attacker: ArgumentId(a),
            target: ArgumentId(b),
            kind: AttackKind::Rebut {
                target_sub: ArgumentId(b),
                contrary: false,
            },
        })
        .collect();

    AbstractFramework::from_defeats(&store, &defeat_structs)
}

// ---------------------------------------------------------------------------
// Running example (Modgil & Prakken 2014, Example 3.7)
// ---------------------------------------------------------------------------

/// Construct the running example from Modgil & Prakken (2014), Example 3.7.
///
/// Reused across many test suites for ordering, defeat, SAF, AF, and
/// pipeline tests.
#[allow(dead_code)]
pub fn running_example() -> AspicSystem {
    let (system, _warnings) = AspicSystemBuilder::new()
        .with_classical_negation()
        .axiom(formula!(p))
        .ordinary_premise(formula!(s))
        .ordinary_premise(formula!(u))
        .ordinary_premise(formula!(x))
        .strict_rule("s1", vec![formula!(p), formula!(q)], formula!(r))
        .strict_rule("s2", vec![formula!(v)], formula!(!s))
        .named_defeasible_rule("d1", formula!(d1), vec![formula!(p)], formula!(q))
        .named_defeasible_rule("d2", formula!(d2), vec![formula!(s)], formula!(t))
        .named_defeasible_rule("d3", formula!(d3), vec![formula!(t)], formula!(!d1))
        .named_defeasible_rule("d4", formula!(d4), vec![formula!(u)], formula!(v))
        .named_defeasible_rule(
            "d5",
            formula!(d5),
            vec![formula!(v), formula!(x)],
            formula!(!t),
        )
        .named_defeasible_rule("d6", formula!(d6), vec![formula!(s)], formula!(!p))
        .build();
    system
}

/// Ground + construct arguments from the running example.
#[allow(dead_code)]
pub fn running_example_store() -> (ArgumentStore, GroundedSystem) {
    let system = running_example();
    let grounder = NaiveGrounder;
    let grounded = grounder.ground(&system).expect("grounding should succeed");
    let constructor = ForwardChainingConstructor;
    let store = constructor.construct(&grounded);
    (store, grounded)
}

/// Named argument IDs for the running example (Example 3.7).
#[allow(dead_code)]
pub struct RunningExampleArgs {
    pub a1: ArgumentId,
    pub b1: ArgumentId,
    pub c1: ArgumentId,
    pub d3_prem: ArgumentId,
    pub a2: ArgumentId,
    pub b2: ArgumentId,
    pub a3: ArgumentId,
    pub b3: ArgumentId,
    pub c2: ArgumentId,
    pub c3: ArgumentId,
    pub d4: ArgumentId,
}

/// Locate named arguments in a store built from the running example.
#[allow(dead_code)]
pub fn find_running_example_args(store: &ArgumentStore) -> RunningExampleArgs {
    RunningExampleArgs {
        a1: find_premise_arg(store, &formula!(p)).expect("A1: premise p"),
        b1: find_premise_arg(store, &formula!(s)).expect("B1: premise s"),
        c1: find_premise_arg(store, &formula!(u)).expect("C1: premise u"),
        d3_prem: find_premise_arg(store, &formula!(x)).expect("D3: premise x"),
        a2: find_arg_by_conclusion_and_rule(store, &formula!(q), "d1").expect("A2: q via d1"),
        b2: find_arg_by_conclusion_and_rule(store, &formula!(t), "d2").expect("B2: t via d2"),
        a3: find_arg_by_conclusion_and_rule(store, &formula!(r), "s1").expect("A3: r via s1"),
        b3: find_arg_by_conclusion_and_rule(store, &formula!(!d1), "d3").expect("B3: ¬d1 via d3"),
        c2: find_arg_by_conclusion_and_rule(store, &formula!(v), "d4").expect("C2: v via d4"),
        c3: find_arg_by_conclusion_and_rule(store, &formula!(!s), "s2").expect("C3: ¬s via s2"),
        d4: find_arg_by_conclusion_and_rule(store, &formula!(!t), "d5").expect("D4: ¬t via d5"),
    }
}

// ---------------------------------------------------------------------------
// Argument lookup helpers
// ---------------------------------------------------------------------------

/// Find a premise argument for the given formula.
#[allow(dead_code)]
pub fn find_premise_arg(store: &ArgumentStore, f: &Formula) -> Option<ArgumentId> {
    store
        .arguments_for(f)
        .iter()
        .copied()
        .find(|&id| matches!(store.get(id), Some(Argument::Premise { .. })))
}

/// Find an inference argument by its conclusion and top-rule ID.
#[allow(dead_code)]
pub fn find_arg_by_conclusion_and_rule(
    store: &ArgumentStore,
    conclusion: &Formula,
    rule_id: &str,
) -> Option<ArgumentId> {
    store.arguments_for(conclusion).iter().copied().find(|&id| {
        store
            .get(id)
            .and_then(|a| a.top_rule())
            .map(|r| r.id().0 == rule_id)
            .unwrap_or(false)
    })
}

/// Find any argument with the given conclusion.
#[allow(dead_code)]
pub fn find_arg_by_conclusion(store: &ArgumentStore, f: &Formula) -> Option<ArgumentId> {
    store.arguments_for(f).first().copied()
}

// ---------------------------------------------------------------------------
// Attack / defeat helpers
// ---------------------------------------------------------------------------

/// Check whether an attack of a specific kind exists between attacker and target.
#[allow(dead_code)]
pub fn attack_exists(
    attacks: &[Attack],
    attacker: ArgumentId,
    target: ArgumentId,
    kind_pred: impl Fn(&AttackKind) -> bool,
) -> bool {
    attacks
        .iter()
        .any(|a| a.attacker == attacker && a.target == target && kind_pred(&a.kind))
}

/// Check whether a defeat exists from attacker to target.
#[allow(dead_code)]
pub fn defeat_exists(defeats: &[Defeat], attacker: ArgumentId, target: ArgumentId) -> bool {
    defeats
        .iter()
        .any(|d| d.attacker == attacker && d.target == target)
}

// ---------------------------------------------------------------------------
// Ordering helpers
// ---------------------------------------------------------------------------

/// Build a last-link ordering with the given rule tiers and premise tiers
/// using the elitist lifting strategy.
#[allow(dead_code)]
pub fn last_link(
    rule_tiers: Vec<Vec<RuleId>>,
    premise_tiers: Vec<Vec<Formula>>,
) -> LastLinkOrdering {
    LastLinkOrdering {
        rule_ordering: Box::new(TieredOrdering::new(rule_tiers)),
        premise_ordering: Box::new(TieredOrdering::new(premise_tiers)),
        lifting: LiftingStrategy::Elitist,
    }
}

// ---------------------------------------------------------------------------
// Convenience helpers
// ---------------------------------------------------------------------------

/// Build a [`BTreeSet<ArgumentId>`] from a slice of raw IDs.
#[allow(dead_code)]
pub fn make_set(ids: &[usize]) -> BTreeSet<ArgumentId> {
    ids.iter().map(|&i| ArgumentId(i)).collect()
}

/// Collect sorted, deduplicated conclusions from an extension.
#[allow(dead_code)]
pub fn conclusions_in(extension: &Extension, store: &ArgumentStore) -> BTreeSet<Formula> {
    extension
        .iter()
        .filter_map(|&aid| store.get(aid))
        .map(|arg| arg.conclusion().clone())
        .collect()
}

/// Sort a vector of extensions for deterministic comparison.
#[allow(dead_code)]
pub fn sorted_exts(mut exts: Vec<Extension>) -> Vec<Extension> {
    exts.sort();
    exts
}
