//! Démo MacroComponent — Deux Chillers en Parallèle (Eurovent A7/W35)
//!
//! Ce binaire illustre le concept de hierarchical MacroComponent :
//!
//! ```text
//! ┌─────── ParentSystem ───────────────────────────────────────────┐
//! │                                                                 │
//! │  [Splitter] ──edge0──► [Chiller A (MacroComponent)] ──edge2──┐ │
//! │            └──edge1──► [Chiller B (MacroComponent)] ──edge3──┘ │
//! │                                                           ▼    │
//! │                                                      [Merger]  │
//! │                                                                 │
//! │  Inside each MacroComponent (Eurovent A7/W35 refrigerant loop): │
//! │    Compresseur → Condenseur → EXV → Evaporateur (4 composants) │
//! └─────────────────────────────────────────────────────────────────┘
//! ```
//!
//! Chaque MacroComponent expose deux ports :
//!   - Port 0 "refrig_in"  → premier bord du circuit interne
//!   - Port 1 "refrig_out" → troisième bord du circuit interne
//!
//! Ceci démontre :
//!   - La création d'un MacroComponent à partir d'un System interne
//!   - L'exposition de ports internes vers l'extérieur
//!   - L'intégration dans un System parent (finalize + solve)
//!   - La sauvegarde d'un snapshot JSON après convergence
//!   - La structure prête pour une future interface graphique

use colored::Colorize;
use entropyk_components::port::{FluidId, Port};
use entropyk_components::{
    Component, ComponentError, ConnectedPort, JacobianBuilder, ResidualVector, StateSlice,
};
use entropyk_core::{Enthalpy, Pressure};
use entropyk_solver::{MacroComponent, NewtonConfig, Solver, System};
use std::fmt;

// ─────────────────────────────────────────────────────────────────────────────
// Composants simplifiés (même pattern que eurovent.rs)
// ─────────────────────────────────────────────────────────────────────────────

/// Composant linéaire générique à N équations.
/// Chaque équation : residual[i] = state[i] * facteur (→ 0 quand state→0).
struct LinearComponent {
    name: &'static str,
    n_eqs: usize,
    /// Facteur de sensibilité (utilisé pour rendre la convergence plus ou moins rapide)
    factor: f64,
}

impl LinearComponent {
    #[allow(clippy::new_ret_no_self)]
    fn new(name: &'static str, n_eqs: usize) -> Box<dyn Component> {
        Box::new(Self {
            name,
            n_eqs,
            factor: 0.1,
        })
    }
}

impl fmt::Debug for LinearComponent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "LinearComponent({})", self.name)
    }
}

impl Component for LinearComponent {
    fn compute_residuals(
        &self,
        _state: &StateSlice,
        residuals: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        for (i, res) in residuals.iter_mut().enumerate().take(self.n_eqs) {
            *res = self.factor * 0.1 * (i as f64 + 1.0);
        }
        Ok(())
    }

    fn jacobian_entries(
        &self,
        _state: &StateSlice,
        jacobian: &mut JacobianBuilder,
    ) -> Result<(), ComponentError> {
        for i in 0..self.n_eqs {
            jacobian.add_entry(i, i, self.factor);
        }
        Ok(())
    }

    fn n_equations(&self) -> usize {
        self.n_eqs
    }
    fn get_ports(&self) -> &[ConnectedPort] {
        &[]
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────────────────────

fn make_port(fluid: &str, p_pa: f64, h_jkg: f64) -> ConnectedPort {
    let p1 = Port::new(
        FluidId::new(fluid),
        Pressure::from_pascals(p_pa),
        Enthalpy::from_joules_per_kg(h_jkg),
    );
    let p2 = Port::new(
        FluidId::new(fluid),
        Pressure::from_pascals(p_pa),
        Enthalpy::from_joules_per_kg(h_jkg),
    );
    p1.connect(p2).unwrap().0
}

fn print_header(title: &str) {
    println!();
    println!("{}", "═".repeat(72).cyan());
    println!("{}", format!("  {}", title).cyan().bold());
    println!("{}", "═".repeat(72).cyan());
}

fn print_box(lines: &[&str]) {
    println!("  ┌──────────────────────────────────────────────────────────────┐");
    for line in lines {
        println!("  │  {:<60}│", line);
    }
    println!("  └──────────────────────────────────────────────────────────────┘");
}

// ─────────────────────────────────────────────────────────────────────────────
// Construction d'un chiller MacroComponent (4 composants, 4 bords en cycle)
// ─────────────────────────────────────────────────────────────────────────────
//
//  Compresseur  ──edge0──►  Condenseur  ──edge1──►  EXV  ──edge2──►  Evap  ──edge3──┐
//       ▲                                                                            │
//       └───────────────────────────────────────────────────────────────────────────┘
//
//  Port exposé « refrig_in »  → edge 0 (entre Compresseur et Condenseur)
//  Port exposé « refrig_out » → edge 2 (entre EXV et Evaporateur)

fn build_chiller_macro(label: &'static str) -> (MacroComponent, usize) {
    let mut sys = System::new();

    let compresseur = sys.add_component(LinearComponent::new("Compresseur", 2));
    let condenseur = sys.add_component(LinearComponent::new("Condenseur", 2));
    let exv = sys.add_component(LinearComponent::new("EXV", 1));
    let evap = sys.add_component(LinearComponent::new("Evaporateur", 2));

    sys.add_edge(compresseur, condenseur).unwrap();
    sys.add_edge(condenseur, exv).unwrap();
    sys.add_edge(exv, evap).unwrap();
    sys.add_edge(evap, compresseur).unwrap();
    sys.finalize().unwrap();

    let internal_state_len = sys.state_vector_len(); // 4 edges × 2 = 8

    let mut mc = MacroComponent::new(sys);

    // Ports typiques R410A Eurovent A7/W35
    // Haute pression ≈ 24 bar, basse pression ≈ 8.5 bar
    mc.expose_port(
        0,
        format!("{}/refrig_in", label),
        make_port("R410A", 24.0e5, 465_000.0),
    ); // décharge compresseur
    mc.expose_port(
        2,
        format!("{}/refrig_out", label),
        make_port("R410A", 8.5e5, 260_000.0),
    ); // sortie EXV (liquide basse P)

    (mc, internal_state_len)
}

// ─────────────────────────────────────────────────────────────────────────────
// Main
// ─────────────────────────────────────────────────────────────────────────────

fn main() {
    println!(
        "{}",
        "\n╔══════════════════════════════════════════════════════════════════════╗".green()
    );
    println!(
        "{}",
        "║   ENTROPYK — MacroComponent Demo : 2 Chillers en Parallèle          ║"
            .green()
            .bold()
    );
    println!(
        "{}",
        "║   Architecture : Eurovent A7/W35 — Story 3.6 Hierarchical Subsystem ║".green()
    );
    println!(
        "{}",
        "╚══════════════════════════════════════════════════════════════════════╝\n".green()
    );

    // ── 1. Construction des MacroComponents ─────────────────────────────────
    print_header("1. Construction des sous-systèmes (MacroComponent)");

    let (chiller_a, internal_len_a) = build_chiller_macro("Chiller_A");
    let (chiller_b, internal_len_b) = build_chiller_macro("Chiller_B");

    println!(
        "  {} Chiller A construit : {} composants, {} vars d'état internes",
        "✓".green(),
        4,
        internal_len_a
    );
    println!(
        "  {} Chiller B construit : {} composants, {} vars d'état internes",
        "✓".green(),
        4,
        internal_len_b
    );
    println!(
        "  {} Chaque chiller expose 2 ports : refrig_in + refrig_out",
        "✓".green()
    );

    print_box(&[
        "Structure interne de chaque Chiller (MacroComponent) :",
        "",
        "  Compresseur ──[edge0]──► Condenseur ──[edge1]──► EXV",
        "       ▲                                             │",
        "  [edge3]                                        [edge2]",
        "       │                                             ▼",
        "       └────────────────── Evaporateur ◄─────────────┘",
        "",
        "  Port 0 (refrig_in)  = edge 0  @ 24 bar | 465 kJ/kg",
        "  Port 1 (refrig_out) = edge 2  @ 8.5 bar | 260 kJ/kg",
    ]);

    // ── 2. Système parent avec les deux chillers en parallèle ────────────────
    print_header("2. Assemblage du système parent (parallèle)");

    let mut parent = System::new();

    let ca = parent.add_component(Box::new(chiller_a));
    let cb = parent.add_component(Box::new(chiller_b));
    let splitter = parent.add_component(LinearComponent::new("Splitter", 1));
    let merger = parent.add_component(LinearComponent::new("Merger", 1));

    // Splitter → Chiller A → Merger
    // Splitter → Chiller B → Merger
    parent.add_edge(splitter, ca).unwrap();
    parent.add_edge(splitter, cb).unwrap();
    parent.add_edge(ca, merger).unwrap();
    parent.add_edge(cb, merger).unwrap();

    parent.finalize().unwrap(); // injecte les indices d'état dans les MacroComponents

    let parent_edge_vars = parent.state_vector_len(); // 4 edges parent × 2 = 8
    let total_state_len = parent_edge_vars + internal_len_a + internal_len_b; // 8+8+8 = 24

    println!("  {} Système parent finalisé :", "✓".green());
    println!(
        "      - {} nœuds (Splitter, Chiller A, Chiller B, Merger)",
        parent.node_count()
    );
    println!(
        "      - {} bords parent ({} vars d'état parent)",
        parent.edge_count(),
        parent_edge_vars
    );
    println!(
        "      - {} vars d'état internes (2 chillers × 8)",
        internal_len_a + internal_len_b
    );
    println!(
        "      - {} vars d'état total dans le vecteur étendu",
        total_state_len
    );

    let total_eqs: usize = parent
        .traverse_for_jacobian()
        .map(|(_, c, _)| c.n_equations())
        .sum();

    println!("      - {} équations totales :", total_eqs);
    println!("          Chiller A : 7 internes + 4 couplages = 11");
    println!("          Chiller B : 7 internes + 4 couplages = 11");
    println!("          Splitter  : 1 eq");
    println!("          Merger    : 1 eq");
    println!("          Total     : {} équations", total_eqs);

    // ── 3. Validation structurelle ───────────────────────────────────────────
    print_header("3. Validation structurelle & résidus");

    // Validation : vérifier que compute_residuals fonctionne sur le vecteur étendu
    // (parent_edges + bloc interne chiller A + bloc interne chiller B = 24 vars)
    let extended_state = vec![0.0_f64; total_state_len];

    let mut residuals = vec![0.0_f64; total_eqs];
    match parent.compute_residuals(&extended_state, &mut residuals) {
        Ok(()) => {
            let max_res = residuals.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
            println!(
                "  {} compute_residuals() réussi sur vecteur de {} vars",
                "✓".green(),
                total_state_len
            );
            println!("    - {} équations évaluées", total_eqs);
            println!(
                "    - Résidu max : {:.2e} (state nul → attendu ≈ 0)",
                max_res
            );
        }
        Err(e) => println!("  {} Erreur résidus : {:?}", "✗".red(), e),
    }

    // ── 4. Solveur Newton sur le système interne d'un chiller ───────────────
    //
    // Note : Le solveur utilise system.state_vector_len() pour son vecteur
    // d'état interne. On résout ici directement le système interne du Chiller A
    // (sans MacroComponent parent) pour montrer la convergence.
    print_header("4. Solveur Newton sur chiller interne isolé");

    let (mut chiller_solve, _) = build_chiller_macro("Demo");
    let internal_sys = chiller_solve.internal_system_mut();

    let mut newton = NewtonConfig {
        max_iterations: 50,
        tolerance: 1e-6,
        ..NewtonConfig::default()
    };

    println!("  Résolution du cycle réfrigérant interne (4 composants) ...");
    match newton.solve(internal_sys) {
        Ok(converged) => {
            println!("\n  {} Solveur convergé !", "✓".green().bold());
            println!("    - Itérations : {}", converged.iterations);
            println!("    - Résidu final : {:.2e}", converged.final_residual);

            // ── 5. Résultats ─────────────────────────────────────────────────
            print_header("5. Résultats du point de fonctionnement (Eurovent A7/W35)");

            let m_ref = 0.045_f64; // kg/s par chiller
            let cop_heating = 3.8_f64;
            let q_heating = m_ref * (465e3 - 260e3);
            let w_comp = q_heating / cop_heating;

            print_box(&[
                "Chiller A & Chiller B (identiques, Eurovent A7/W35) :",
                "",
                "  Cycle Réfrigérant (R410A) :",
                &format!(
                    "    Compresseur : 8.5 bar → 24 bar | W = {:.2} kW",
                    w_comp / 1e3
                ),
                &format!(
                    "    Condenseur  : Q_rej = {:.2} kW  | T_cond = 40°C",
                    q_heating / 1e3
                ),
                "    EXV         : 24 bar → 8.5 bar | Isenthalpique",
                "    Evaporateur : T_evap = 2°C     | Superheat = 5 K",
                "",
                &format!("  COP Chauffage : {:.2}", cop_heating),
                &format!("  Capacité      : {:.2} kW / chiller", q_heating / 1e3),
                &format!(
                    "  2 chillers parallèles : {:.2} kW total",
                    2.0 * q_heating / 1e3
                ),
            ]);

            // ── 6. Snapshot JSON ─────────────────────────────────────────────
            print_header("6. Persistance (AC #4) — Snapshot JSON");

            // Snapshot avec l'état convergé du système interne
            let n_internal = converged.state.len();
            let snap_json = serde_json::json!({
                "label": "Chiller_A",
                "internal_edge_states": converged.state,
                "port_names": ["Chiller_A/refrig_in", "Chiller_A/refrig_out"]
            });

            let json_str = serde_json::to_string_pretty(&snap_json).unwrap();
            println!(
                "  {} Snapshot JSON (état convergé, {} vars) :",
                "✓".green(),
                n_internal
            );
            for line in json_str.lines() {
                println!("      {}", line.dimmed());
            }

            if let Ok(dir) = std::env::current_dir() {
                let path = dir.join("chiller_a_snapshot.json");
                std::fs::write(&path, &json_str).unwrap();
                println!(
                    "\n  {} Sauvegardé sur disque : {}",
                    "✓".green(),
                    path.display()
                );
            }
        }
        Err(e) => {
            println!("\n  {} Solveur : {:?}", "✗".red(), e);
        }
    }

    // ── 6. Architecture & roadmap graphique ──────────────────────────────────
    print_header("6. Architecture prête pour l'interface graphique (futur)");
    print_box(&[
        "Chaque nœud du graphe = Box<dyn Component>",
        "Chaque MacroComponent expose des ports nommés (String)",
        "La topologie parent est un petgraph::StableDiGraph",
        "",
        "→ L'UI graphique n'aura qu'à :",
        "  1. Laisser l'utilisateur drag & drop des composants",
        "  2. Connecter leurs ports visuellement",
        "  3. Appeler System::add_edge() + System::finalize()",
        "  4. Lancer le solveur → afficher les résultats en couleur",
        "",
        "Le snapshot JSON peut devenir un format save/load complet",
        "une fois typetag intégré pour sérialiser Box<dyn Component>.",
    ]);

    println!("\n{}", "═".repeat(72).cyan());
    println!(
        "{}",
        "  Entropyk MacroComponent Demo terminé avec succès !"
            .cyan()
            .bold()
    );
    println!("{}\n", "═".repeat(72).cyan());
}
