//! Hash-indexed attack computation.
//!
//! Avoids the O(n²) cost of [`PairwiseAttackComputer`](super::PairwiseAttackComputer)
//! by pre-building indexes from the contrariness function. Phase 1 scans
//! all target-side sub-arguments and indexes them by the attacker conclusion
//! that would trigger each attack type. Phase 2 iterates over arguments
//! once, looking up each conclusion in the indexes.

use std::collections::HashMap;

use crate::framework::argument::{Argument, ArgumentId};
use crate::framework::argument_store::ArgumentStore;
use crate::framework::attack::{Attack, AttackKind};
use crate::language::formula::Formula;
use crate::pipeline::traits::AttackComputer;
use crate::system::contrariness::{ContrarinessFunction, ContrarinessRelation};
use crate::system::knowledge::PremiseKind;
use crate::system::rules::{Rule, RuleId};

/// Hash-indexed attack computer.
///
/// Pre-builds indexes mapping potential attacker conclusions to the target
/// positions they would attack, then iterates once over all arguments to
/// collect attacks via hash lookups. Amortised O(n · s) where *n* is the
/// number of arguments and *s* the average sub-argument count.
pub struct IndexedAttackComputer;

impl AttackComputer for IndexedAttackComputer {
    fn compute(&self, store: &ArgumentStore, contrariness: &ContrarinessFunction) -> Vec<Attack> {
        // Phase 1: build target indexes keyed by the ATTACKER conclusion that
        // would trigger each attack type. For each vulnerable target formula,
        // we look up contraries_of(target_formula) to find what attacker
        // conclusions would constitute an attack, then index by those.
        let mut rebut_by_attacker_conc: HashMap<Formula, Vec<(ArgumentId, ArgumentId, bool)>> =
            HashMap::new();
        let mut undermine_by_attacker_conc: HashMap<Formula, Vec<(ArgumentId, ArgumentId, bool)>> =
            HashMap::new();
        let mut undercut_by_attacker_conc: HashMap<Formula, Vec<(ArgumentId, ArgumentId, RuleId)>> =
            HashMap::new();

        for (top_id, top_arg) in store.all_arguments() {
            let sub_ids = top_arg.sub_argument_ids(store);

            for &sub_id in &sub_ids {
                let Some(sub_arg) = store.get(sub_id) else {
                    continue;
                };

                if let Some(top_rule) = sub_arg.top_rule() {
                    if top_rule.is_defeasible() {
                        let sub_conclusion = sub_arg.conclusion();
                        for attacker_conc in contrariness.contraries_of(sub_conclusion) {
                            let contrary = matches!(
                                contrariness.relation_between(sub_conclusion, &attacker_conc),
                                Some(ContrarinessRelation::Contrary)
                            );
                            rebut_by_attacker_conc
                                .entry(attacker_conc)
                                .or_default()
                                .push((sub_id, top_id, contrary));
                        }
                    }
                }

                if let Argument::Premise { premise, .. } = sub_arg {
                    match premise.kind {
                        PremiseKind::Ordinary => {
                            for attacker_conc in contrariness.contraries_of(&premise.formula) {
                                let contrary = matches!(
                                    contrariness.relation_between(&premise.formula, &attacker_conc),
                                    Some(ContrarinessRelation::Contrary)
                                );
                                undermine_by_attacker_conc
                                    .entry(attacker_conc)
                                    .or_default()
                                    .push((sub_id, top_id, contrary));
                            }
                        }
                        PremiseKind::Axiom => {}
                    }
                }

                if let Some(Rule::Defeasible(dr)) = sub_arg.top_rule() {
                    if let Some(rule_name) = &dr.name {
                        for attacker_conc in contrariness.contraries_of(rule_name) {
                            undercut_by_attacker_conc
                                .entry(attacker_conc)
                                .or_default()
                                .push((sub_id, top_id, dr.id.clone()));
                        }
                    }
                }
            }
        }

        // Phase 2: for each argument, look up its conclusion in the indexes.
        let mut attacks = Vec::new();

        for (attacker_id, attacker) in store.all_arguments() {
            let conclusion = attacker.conclusion();

            if let Some(targets) = rebut_by_attacker_conc.get(conclusion) {
                for &(sub_id, top_id, contrary) in targets {
                    attacks.push(Attack {
                        attacker: attacker_id,
                        target: top_id,
                        kind: AttackKind::Rebut {
                            target_sub: sub_id,
                            contrary,
                        },
                    });
                }
            }

            if let Some(targets) = undermine_by_attacker_conc.get(conclusion) {
                for &(premise_id, top_id, contrary) in targets {
                    attacks.push(Attack {
                        attacker: attacker_id,
                        target: top_id,
                        kind: AttackKind::Undermine {
                            target_premise: premise_id,
                            contrary,
                        },
                    });
                }
            }

            if let Some(targets) = undercut_by_attacker_conc.get(conclusion) {
                for (sub_id, top_id, rule_id) in targets {
                    attacks.push(Attack {
                        attacker: attacker_id,
                        target: *top_id,
                        kind: AttackKind::Undercut {
                            target_sub: *sub_id,
                            rule_id: rule_id.clone(),
                        },
                    });
                }
            }
        }

        attacks
    }
}
