//! Export formats for abstract argumentation frameworks.
//!
//! These methods extend [`AbstractFramework`] with serialisation to
//! standard interchange formats used by AF solvers and visualisation
//! tools:
//!
//! | Method                                        | Format |
//! |-----------------------------------------------|--------|
//! | [`to_tgf`](AbstractFramework::to_tgf)        | Trivial Graph Format |
//! | [`to_aspartix`](AbstractFramework::to_aspartix) | ASPARTIX (`.apx`) |
//! | [`to_dot`](AbstractFramework::to_dot)         | Graphviz DOT |
//! | [`to_iccma`](AbstractFramework::to_iccma)     | ICCMA competition format |
//!
//! # Example
//!
//! ```
//! use pickaxe_rs::prelude::*;
//!
//! let (system, _) = AspicSystemBuilder::new()
//!     .ordinary_premise(formula!(a))
//!     .defeasible_rule("d1", vec![formula!(a)], formula!(b))
//!     .with_classical_negation()
//!     .build();
//!
//! let pipeline = AspicPipeline::classical(system, Box::new(FlatOrdering));
//! let result = pipeline.evaluate(Semantics::Grounded).unwrap();
//!
//! let af = AbstractFramework::from_defeats(&result.store, &[]);
//! let tgf = af.to_tgf();
//! let dot = af.to_dot(&result.store);
//! assert!(dot.contains("digraph AF"));
//! ```

use crate::framework::abstract_af::AbstractFramework;
use crate::framework::argument_store::ArgumentStore;

impl AbstractFramework {
    /// Trivial Graph Format (.tgf).
    #[must_use]
    pub fn to_tgf(&self) -> String {
        use std::fmt::Write;
        let mut out = String::new();
        for arg in &self.arguments {
            writeln!(out, "{}", arg.0).unwrap();
        }
        out.push_str("#\n");
        for (a, b) in &self.defeats {
            writeln!(out, "{} {}", a.0, b.0).unwrap();
        }
        out
    }

    /// ASPARTIX format (for interop with existing AF solvers).
    #[must_use]
    pub fn to_aspartix(&self) -> String {
        use std::fmt::Write;
        let mut out = String::new();
        for arg in &self.arguments {
            writeln!(out, "arg(a{}).", arg.0).unwrap();
        }
        for (a, b) in &self.defeats {
            writeln!(out, "att(a{},a{}).", a.0, b.0).unwrap();
        }
        out
    }

    /// DOT format (for Graphviz visualisation).
    #[must_use]
    pub fn to_dot(&self, store: &ArgumentStore) -> String {
        use std::fmt::Write;
        let mut out = String::new();
        out.push_str("digraph AF {\n");
        out.push_str("  rankdir=LR;\n");
        out.push_str("  node [shape=box];\n");
        for arg_id in &self.arguments {
            let label = if let Some(arg) = store.get(*arg_id) {
                format!("A{}: {}", arg_id.0, arg.conclusion())
            } else {
                format!("A{}", arg_id.0)
            };
            let escaped = label.replace('"', "\\\"");
            writeln!(out, "  a{} [label=\"{escaped}\"];", arg_id.0).unwrap();
        }
        for (a, b) in &self.defeats {
            writeln!(out, "  a{} -> a{};", a.0, b.0).unwrap();
        }
        out.push_str("}\n");
        out
    }

    /// ICCMA format (competition format for AF solvers).
    ///
    /// Builds a bijection from argument IDs to sequential 1-based indices,
    /// ensuring valid output even when internal IDs are non-contiguous
    /// (e.g. sub-frameworks with IDs {2, 5, 7} map to {1, 2, 3}).
    #[must_use]
    pub fn to_iccma(&self) -> String {
        use std::collections::HashMap;
        use std::fmt::Write;
        let id_map: HashMap<crate::framework::argument::ArgumentId, usize> = self
            .arguments
            .iter()
            .enumerate()
            .map(|(i, &arg)| (arg, i + 1))
            .collect();

        let mut out = String::new();
        writeln!(out, "p af {}", self.arguments.len()).unwrap();
        for (a, b) in &self.defeats {
            writeln!(out, "{} {}", id_map[a], id_map[b]).unwrap();
        }
        out
    }
}
