//! Declarative pipeline configuration and materialisation.
//!
//! [`PipelineConfig`] is a serialisable description of which implementation
//! to use at each pipeline stage. It bridges the gap between external
//! interfaces (CLI flags, JSON/TOML config files, REST APIs) and the
//! trait-object-based [`AspicPipeline`].
//!
//! Use one of the presets ([`PipelineConfig::classical`],
//! [`PipelineConfig::optimised`], [`PipelineConfig::accrual`]) or build a
//! custom configuration, then call [`PipelineConfig::build`] to materialise
//! it into a live pipeline.

use std::collections::{BTreeSet, HashMap};

use serde::{Deserialize, Serialize};

use crate::io::simple::RulePreference;
use crate::pipeline::attack::{IndexedAttackComputer, PairwiseAttackComputer};
use crate::pipeline::construction::ForwardChainingConstructor;
use crate::pipeline::evaluation::accrual::AccrualEvaluator;
use crate::pipeline::evaluation::classical::ClassicalEvaluator;
use crate::pipeline::evaluation::naive_solver::NaiveEnumerationSolver;
use crate::pipeline::grounding::{NaiveGrounder, SemiNaiveGrounder};
use crate::pipeline::traits::PreferenceConfig;
use crate::pipeline::AspicPipeline;
use crate::system::ordering::flat::FlatOrdering;
use crate::system::ordering::last_link::LastLinkOrdering;
use crate::system::ordering::weakest_link::WeakestLinkOrdering;
use crate::system::ordering::{
    ArgumentOrdering, LiftingStrategy, NoPreference, RuleOrdering, TieredOrdering,
};
use crate::system::rules::RuleId;
use crate::system::AspicSystem;

/// Declarative pipeline configuration that can be serialised,
/// deserialised, transmitted over a REST API, or selected by CLI flag.
///
/// Each field selects the implementation for one pipeline stage. Use
/// [`build`](Self::build) to materialise into a live [`AspicPipeline`].
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PipelineConfig {
    /// Human-readable name for this configuration (e.g. `"classical"`, `"optimised"`).
    pub name: String,
    /// Grounding strategy selector (stage 1).
    pub grounder: GrounderConfig,
    /// Argument construction strategy selector (stage 2).
    pub arg_constructor: ArgConstructorConfig,
    /// Attack computation strategy selector (stage 3).
    pub attack_computer: AttackComputerConfig,
    /// Evaluator and solver backend selector (stage 4).
    pub evaluator: EvaluatorConfig,
}

/// Stage 1 implementation selector — grounding strategy.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum GrounderConfig {
    /// Full Herbrand enumeration ([`NaiveGrounder`]).
    Naive,
    /// Bottom-up fixpoint with semi-naive optimisation
    /// ([`SemiNaiveGrounder`]).
    SemiNaive,
}

/// Stage 2 implementation selector — argument construction strategy.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ArgConstructorConfig {
    /// Forward-chaining saturation
    /// ([`ForwardChainingConstructor`]).
    ForwardChaining,
}

/// Stage 3 implementation selector — attack computation strategy.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum AttackComputerConfig {
    /// O(n²) brute-force enumeration
    /// ([`PairwiseAttackComputer`]).
    Pairwise,
    /// Hash-indexed lookup
    /// ([`IndexedAttackComputer`]).
    Indexed,
}

/// Stage 4 implementation selector — evaluator and its inner solver.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EvaluatorConfig {
    /// Standard ASPIC+ evaluation via [`ClassicalEvaluator`]
    /// with a configurable [`SemanticsSolver`](crate::pipeline::traits::SemanticsSolver) backend.
    Classical { solver: SolverConfig },
    /// Accrual-based evaluation (Prakken 2019) via
    /// [`AccrualEvaluator`].
    Accrual,
}

/// Semantics solver backend selector, used inside [`EvaluatorConfig::Classical`].
///
/// Feature-gated variants are only available when the corresponding Cargo
/// feature is enabled.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum SolverConfig {
    /// Labelling-based naive enumeration (no feature flags required).
    NaiveEnumeration,
    /// SAT-based solver using `BatSat` (pure Rust).
    #[cfg(feature = "sat")]
    Sat,
    /// SCC-decomposing solver wrapping a `BatSat` SAT backend.
    #[cfg(feature = "sat")]
    SccSat,
    /// SAT-based solver using `CaDiCaL` (FFI, higher performance).
    #[cfg(feature = "cadical")]
    SatCadical,
    /// SCC-decomposing solver wrapping a `CaDiCaL` SAT backend.
    #[cfg(feature = "cadical")]
    SccSatCadical,
    /// External ASPARTIX solver (delegates to clingo).
    #[cfg(feature = "aspartix")]
    Aspartix {
        clingo_path: String,
        encodings_dir: String,
    },
    /// External ICCMA-compliant solver (e.g. mu-toksia).
    #[cfg(feature = "iccma")]
    Mutoksia { solver_path: String },
}

impl PipelineConfig {
    /// Preset: standard classical pipeline with naive implementations.
    pub fn classical() -> Self {
        PipelineConfig {
            name: "classical".into(),
            grounder: GrounderConfig::Naive,
            arg_constructor: ArgConstructorConfig::ForwardChaining,
            attack_computer: AttackComputerConfig::Pairwise,
            evaluator: EvaluatorConfig::Classical {
                solver: SolverConfig::NaiveEnumeration,
            },
        }
    }

    /// Preset: optimised pipeline with indexed attacks and SCC+SAT solving.
    ///
    /// Requires the `sat` feature.
    #[cfg(feature = "sat")]
    pub fn optimised() -> Self {
        PipelineConfig {
            name: "optimised".into(),
            grounder: GrounderConfig::Naive,
            arg_constructor: ArgConstructorConfig::ForwardChaining,
            attack_computer: AttackComputerConfig::Indexed,
            evaluator: EvaluatorConfig::Classical {
                solver: SolverConfig::SccSat,
            },
        }
    }

    /// Preset: accrual pipeline (Prakken 2019).
    pub fn accrual() -> Self {
        PipelineConfig {
            name: "accrual".into(),
            grounder: GrounderConfig::Naive,
            arg_constructor: ArgConstructorConfig::ForwardChaining,
            attack_computer: AttackComputerConfig::Pairwise,
            evaluator: EvaluatorConfig::Accrual,
        }
    }

    /// Materialise this configuration into a live [`AspicPipeline`].
    ///
    /// The `preference` parameter supplies the ordering or set preference
    /// used by the evaluator stage.
    pub fn build(self, system: AspicSystem, preference: PreferenceConfig) -> AspicPipeline {
        let grounder: Box<dyn crate::pipeline::Grounder> = match self.grounder {
            GrounderConfig::Naive => Box::new(NaiveGrounder),
            GrounderConfig::SemiNaive => Box::new(SemiNaiveGrounder),
        };

        let arg_constructor: Box<dyn crate::pipeline::ArgumentConstructor> =
            match self.arg_constructor {
                ArgConstructorConfig::ForwardChaining => Box::new(ForwardChainingConstructor),
            };

        let attack_computer: Box<dyn crate::pipeline::AttackComputer> = match self.attack_computer {
            AttackComputerConfig::Pairwise => Box::new(PairwiseAttackComputer),
            AttackComputerConfig::Indexed => Box::new(IndexedAttackComputer),
        };

        let evaluator: Box<dyn crate::pipeline::Evaluator> = match self.evaluator {
            EvaluatorConfig::Classical { solver } => {
                let semantics_solver: Box<dyn crate::pipeline::SemanticsSolver> = match solver {
                    SolverConfig::NaiveEnumeration => Box::new(NaiveEnumerationSolver),
                    #[cfg(feature = "sat")]
                    SolverConfig::Sat => {
                        Box::new(crate::pipeline::evaluation::sat_solver::SatSolver::<
                            rustsat_batsat::BasicSolver,
                        >::default())
                    }
                    #[cfg(feature = "sat")]
                    SolverConfig::SccSat => Box::new(
                        crate::pipeline::evaluation::scc_solver::SccDecomposingSolver {
                            inner: Box::new(crate::pipeline::evaluation::sat_solver::SatSolver::<
                                rustsat_batsat::BasicSolver,
                            >::default()),
                        },
                    ),
                    #[cfg(feature = "cadical")]
                    SolverConfig::SatCadical => {
                        Box::new(crate::pipeline::evaluation::sat_solver::SatSolver::<
                            rustsat_cadical::CaDiCaL,
                        >::default())
                    }
                    #[cfg(feature = "cadical")]
                    SolverConfig::SccSatCadical => Box::new(
                        crate::pipeline::evaluation::scc_solver::SccDecomposingSolver {
                            inner: Box::new(crate::pipeline::evaluation::sat_solver::SatSolver::<
                                rustsat_cadical::CaDiCaL,
                            >::default()),
                        },
                    ),
                    #[cfg(feature = "aspartix")]
                    SolverConfig::Aspartix {
                        clingo_path,
                        encodings_dir,
                    } => Box::new(
                        crate::pipeline::evaluation::aspartix_solver::AspartixSolver {
                            clingo_path: clingo_path.into(),
                            encodings_dir: encodings_dir.into(),
                        },
                    ),
                    #[cfg(feature = "iccma")]
                    SolverConfig::Mutoksia { solver_path } => {
                        Box::new(crate::pipeline::evaluation::iccma_solver::IccmaSolver {
                            solver_path: solver_path.into(),
                            solver_name: "µ-toksia".into(),
                        })
                    }
                };
                Box::new(ClassicalEvaluator { semantics_solver })
            }
            EvaluatorConfig::Accrual => Box::new(AccrualEvaluator),
        };

        AspicPipeline {
            system,
            grounder,
            arg_constructor,
            attack_computer,
            evaluator,
            preference,
        }
    }
}

// ---------------------------------------------------------------------------
// Preference / ordering configuration
// ---------------------------------------------------------------------------

/// Preference mode for CLI / external configuration.
///
/// Selects the argument ordering strategy used for defeat computation.
/// Used by [`build_ordering`] to materialise a trait-object ordering.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum PreferenceMode {
    /// No preference ordering; all attacks become defeats.
    None,
    /// Flat ordering: arguments compared by their top-rule priority alone.
    Flat,
    /// Last-link with elitist lifting: compare only the last rule applied.
    LastLinkElitist,
    /// Last-link with democratic lifting: compare the weakest last rule.
    LastLinkDemocratic,
    /// Weakest-link with elitist lifting: compare by the weakest element.
    WeakestLinkElitist,
    /// Weakest-link with democratic lifting.
    WeakestLinkDemocratic,
}

/// Build an [`ArgumentOrdering`] from a [`PreferenceMode`] and pairwise
/// rule preferences.
pub fn build_ordering(
    mode: &PreferenceMode,
    prefs: &[RulePreference],
) -> Box<dyn ArgumentOrdering> {
    match mode {
        PreferenceMode::None => Box::new(LastLinkOrdering {
            rule_ordering: Box::new(NoPreference),
            premise_ordering: Box::new(NoPreference),
            lifting: LiftingStrategy::Elitist,
        }),
        PreferenceMode::Flat => Box::new(FlatOrdering),
        PreferenceMode::LastLinkElitist => {
            let rule_ordering = build_tiered_rule_ordering(prefs);
            Box::new(LastLinkOrdering {
                rule_ordering,
                premise_ordering: Box::new(NoPreference),
                lifting: LiftingStrategy::Elitist,
            })
        }
        PreferenceMode::LastLinkDemocratic => {
            let rule_ordering = build_tiered_rule_ordering(prefs);
            Box::new(LastLinkOrdering {
                rule_ordering,
                premise_ordering: Box::new(NoPreference),
                lifting: LiftingStrategy::Democratic,
            })
        }
        PreferenceMode::WeakestLinkElitist => {
            let rule_ordering = build_tiered_rule_ordering(prefs);
            Box::new(WeakestLinkOrdering {
                rule_ordering,
                premise_ordering: Box::new(NoPreference),
                lifting: LiftingStrategy::Elitist,
            })
        }
        PreferenceMode::WeakestLinkDemocratic => {
            let rule_ordering = build_tiered_rule_ordering(prefs);
            Box::new(WeakestLinkOrdering {
                rule_ordering,
                premise_ordering: Box::new(NoPreference),
                lifting: LiftingStrategy::Democratic,
            })
        }
    }
}

/// Topological sort of pairwise rule preferences into a [`TieredOrdering`].
///
/// Builds a DAG from the preference pairs (superior -> inferior) and
/// partitions it into depth tiers via [`topological_layers`](crate::algorithms::kahn::topological_layers).  Each
/// tier becomes a group of equally-ranked rules in the resulting ordering.
pub fn build_tiered_rule_ordering(prefs: &[RulePreference]) -> Box<dyn RuleOrdering> {
    if prefs.is_empty() {
        return Box::new(NoPreference);
    }

    let mut all_rules: BTreeSet<&str> = BTreeSet::new();
    let mut successors: HashMap<&str, Vec<&str>> = HashMap::new();

    for p in prefs {
        all_rules.insert(&p.superior);
        all_rules.insert(&p.inferior);
        successors.entry(&p.superior).or_default().push(&p.inferior);
    }

    let layers = crate::algorithms::kahn::topological_layers(&all_rules, &successors);

    let tiers: Vec<Vec<RuleId>> = layers
        .into_iter()
        .map(|layer| layer.into_iter().map(|r| RuleId(r.to_string())).collect())
        .collect();

    Box::new(TieredOrdering::new(tiers))
}
