//! Structured output types for pipeline evaluation results.
//!
//! These types provide a serialisable representation of the pipeline's
//! output — extensions, their components, and per-stage timing data.
//! All types derive [`Serialize`] and
//! [`Deserialize`] so they can be emitted as JSON
//! or consumed from it.

use serde::{Deserialize, Serialize};

use crate::framework::argument_store::ArgumentStore;
use crate::pipeline::traits::EvaluationResult;
use crate::system::knowledge::PremiseKind;

// ---------------------------------------------------------------------------
// Timing instrumentation
// ---------------------------------------------------------------------------

/// Per-stage timing data from a pipeline run (milliseconds).
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Timings {
    pub grounding_ms: f64,
    pub construction_ms: f64,
    pub attacks_ms: f64,
    pub evaluation_ms: f64,
    pub total_ms: f64,
    pub num_attacks: usize,
}

// ---------------------------------------------------------------------------
// Extension output
// ---------------------------------------------------------------------------

/// A single extension in the output format, with its derived components.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExtensionOutput {
    /// Sorted, deduplicated conclusion formulae.
    pub conclusions: Vec<String>,
    /// Sorted, deduplicated ordinary premise formulae.
    pub ordinary_premises: Vec<String>,
    /// Sorted, deduplicated defeasible rule IDs.
    pub defeasible_rules: Vec<String>,
}

// ---------------------------------------------------------------------------
// Pipeline output
// ---------------------------------------------------------------------------

/// Structured output from a pipeline evaluation, suitable for JSON
/// serialisation.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PipelineOutput {
    pub num_arguments: usize,
    pub num_attacks: usize,
    pub num_extensions: usize,
    pub extensions: Vec<ExtensionOutput>,
    pub timings_ms: TimingsOutput,
}

/// The timing sub-object within [`PipelineOutput`].
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TimingsOutput {
    pub grounding: f64,
    pub construction: f64,
    pub attacks: f64,
    pub evaluation: f64,
    pub total: f64,
}

impl PipelineOutput {
    /// Build a [`PipelineOutput`] from a full evaluation result.
    pub fn from_evaluation(
        store: &ArgumentStore,
        eval: &EvaluationResult,
        timings: Timings,
    ) -> Self {
        let extensions: Vec<ExtensionOutput> = eval
            .extensions
            .iter()
            .map(|ext| Self::build_extension(store, ext))
            .collect();

        Self {
            num_arguments: store.len(),
            num_attacks: timings.num_attacks,
            num_extensions: extensions.len(),
            extensions,
            timings_ms: TimingsOutput::from_timings(&timings),
        }
    }

    /// Build a [`PipelineOutput`] for count-only mode (no extension details).
    pub fn from_count(store: &ArgumentStore, count: usize, timings: Timings) -> Self {
        Self {
            num_arguments: store.len(),
            num_attacks: timings.num_attacks,
            num_extensions: count,
            extensions: Vec::new(),
            timings_ms: TimingsOutput::from_timings(&timings),
        }
    }

    fn build_extension(
        store: &ArgumentStore,
        ext: &crate::framework::semantics::Extension,
    ) -> ExtensionOutput {
        let mut conclusions: Vec<String> = ext
            .iter()
            .filter_map(|&aid| store.get(aid))
            .map(|arg| arg.conclusion().to_string())
            .collect();
        conclusions.sort();
        conclusions.dedup();

        let mut ordinary_premises: Vec<String> = ext
            .iter()
            .filter_map(|&aid| store.get(aid))
            .flat_map(|arg| arg.premises(store))
            .filter(|p| p.kind == PremiseKind::Ordinary)
            .map(|p| p.formula.to_string())
            .collect();
        ordinary_premises.sort();
        ordinary_premises.dedup();

        let mut defeasible_rules: Vec<String> = ext
            .iter()
            .filter_map(|&aid| store.get(aid))
            .flat_map(|arg| arg.defeasible_rules(store))
            .map(|r| r.id.0.clone())
            .collect();
        defeasible_rules.sort();
        defeasible_rules.dedup();

        ExtensionOutput {
            conclusions,
            ordinary_premises,
            defeasible_rules,
        }
    }
}

impl TimingsOutput {
    fn from_timings(t: &Timings) -> Self {
        TimingsOutput {
            grounding: t.grounding_ms,
            construction: t.construction_ms,
            attacks: t.attacks_ms,
            evaluation: t.evaluation_ms,
            total: t.total_ms,
        }
    }
}
