reasonkit/thinktool/modules/
laserlogic.rs

1//! # LaserLogic Module - Precision Deductive Reasoning
2//!
3//! Performs rigorous logical analysis with:
4//! - First-order logic (FOL) translation and validation
5//! - Formal fallacy detection (10+ types)
6//! - Argument structure validation
7//! - Syllogism analysis
8//! - Contradiction detection
9//!
10//! ## Scientific Foundation
11//!
12//! Based on classical deductive logic and NL2FOL research:
13//! - Modus ponens, modus tollens, hypothetical syllogism
14//! - 78-80% F1 on fallacy detection with FOL translation
15//! - Formal verification through constraint satisfaction
16//!
17//! ## Usage
18//!
19//! ```rust,ignore
20//! use reasonkit::thinktool::modules::{LaserLogic, LaserLogicConfig};
21//!
22//! // Quick validation
23//! let laser = LaserLogic::new();
24//! let result = laser.analyze_argument(
25//!     &["All humans are mortal", "Socrates is human"],
26//!     "Socrates is mortal"
27//! )?;
28//!
29//! // With custom configuration
30//! let laser = LaserLogic::with_config(LaserLogicConfig {
31//!     detect_fallacies: true,
32//!     check_validity: true,
33//!     check_soundness: true,
34//!     max_premise_depth: 10,
35//!     ..Default::default()
36//! });
37//! ```
38
39use super::{ThinkToolContext, ThinkToolModule, ThinkToolModuleConfig, ThinkToolOutput};
40use crate::error::{Error, Result};
41use serde::{Deserialize, Serialize};
42use std::collections::HashSet;
43
44// =============================================================================
45// Configuration
46// =============================================================================
47
48/// Configuration for LaserLogic analysis depth and features
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct LaserLogicConfig {
51    /// Enable fallacy detection
52    pub detect_fallacies: bool,
53    /// Enable validity checking (conclusion follows from premises)
54    pub check_validity: bool,
55    /// Enable soundness checking (valid + true premises)
56    pub check_soundness: bool,
57    /// Maximum number of premises to analyze
58    pub max_premise_depth: usize,
59    /// Enable syllogism detection and analysis
60    pub analyze_syllogisms: bool,
61    /// Enable contradiction detection
62    pub detect_contradictions: bool,
63    /// Minimum confidence threshold for conclusions
64    pub confidence_threshold: f64,
65    /// Enable verbose output with reasoning steps
66    pub verbose_output: bool,
67}
68
69impl Default for LaserLogicConfig {
70    fn default() -> Self {
71        Self {
72            detect_fallacies: true,
73            check_validity: true,
74            check_soundness: true,
75            max_premise_depth: 10,
76            analyze_syllogisms: true,
77            detect_contradictions: true,
78            confidence_threshold: 0.7,
79            verbose_output: false,
80        }
81    }
82}
83
84impl LaserLogicConfig {
85    /// Quick check mode - validity and basic fallacy detection only
86    pub fn quick() -> Self {
87        Self {
88            detect_fallacies: true,
89            check_validity: true,
90            check_soundness: false,
91            max_premise_depth: 5,
92            analyze_syllogisms: false,
93            detect_contradictions: false,
94            confidence_threshold: 0.6,
95            verbose_output: false,
96        }
97    }
98
99    /// Deep analysis mode - all features enabled
100    pub fn deep() -> Self {
101        Self {
102            detect_fallacies: true,
103            check_validity: true,
104            check_soundness: true,
105            max_premise_depth: 20,
106            analyze_syllogisms: true,
107            detect_contradictions: true,
108            confidence_threshold: 0.8,
109            verbose_output: true,
110        }
111    }
112
113    /// Paranoid mode - strictest validation
114    pub fn paranoid() -> Self {
115        Self {
116            detect_fallacies: true,
117            check_validity: true,
118            check_soundness: true,
119            max_premise_depth: 50,
120            analyze_syllogisms: true,
121            detect_contradictions: true,
122            confidence_threshold: 0.9,
123            verbose_output: true,
124        }
125    }
126}
127
128// =============================================================================
129// Logical Structures
130// =============================================================================
131
132/// A logical argument with premises and conclusion
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct Argument {
135    /// The premises (supporting statements)
136    pub premises: Vec<Premise>,
137    /// The conclusion being argued
138    pub conclusion: String,
139    /// Argument form (modus ponens, syllogism, etc.)
140    pub form: Option<ArgumentForm>,
141}
142
143impl Argument {
144    /// Create a new argument from premises and conclusion
145    pub fn new(premises: Vec<&str>, conclusion: &str) -> Self {
146        Self {
147            premises: premises.iter().map(|p| Premise::new(p)).collect(),
148            conclusion: conclusion.to_string(),
149            form: None,
150        }
151    }
152
153    /// Create with typed premises
154    pub fn with_premises(premises: Vec<Premise>, conclusion: &str) -> Self {
155        Self {
156            premises,
157            conclusion: conclusion.to_string(),
158            form: None,
159        }
160    }
161}
162
163/// A single premise in an argument
164#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct Premise {
166    /// The premise statement
167    pub statement: String,
168    /// Premise type (universal, particular, conditional)
169    pub premise_type: PremiseType,
170    /// Confidence in the premise truth (0.0 - 1.0)
171    pub confidence: f64,
172    /// Evidence supporting this premise
173    pub evidence: Vec<String>,
174}
175
176impl Premise {
177    /// Create a new premise
178    pub fn new(statement: &str) -> Self {
179        let premise_type = Self::infer_type(statement);
180        Self {
181            statement: statement.to_string(),
182            premise_type,
183            confidence: 1.0,
184            evidence: Vec::new(),
185        }
186    }
187
188    /// Create with explicit type and confidence
189    pub fn with_type(statement: &str, premise_type: PremiseType, confidence: f64) -> Self {
190        Self {
191            statement: statement.to_string(),
192            premise_type,
193            confidence,
194            evidence: Vec::new(),
195        }
196    }
197
198    /// Infer premise type from statement
199    fn infer_type(statement: &str) -> PremiseType {
200        let lower = statement.to_lowercase();
201
202        // Universal quantifiers
203        if lower.starts_with("all ")
204            || lower.starts_with("every ")
205            || lower.starts_with("each ")
206            || lower.contains(" always ")
207            || lower.starts_with("no ")
208        {
209            return PremiseType::Universal;
210        }
211
212        // Particular quantifiers
213        if lower.starts_with("some ")
214            || lower.starts_with("most ")
215            || lower.starts_with("many ")
216            || lower.starts_with("few ")
217            || lower.contains(" sometimes ")
218        {
219            return PremiseType::Particular;
220        }
221
222        // Conditional
223        if lower.starts_with("if ")
224            || lower.contains(" then ")
225            || lower.contains(" implies ")
226            || lower.contains(" only if ")
227        {
228            return PremiseType::Conditional;
229        }
230
231        // Disjunctive
232        if lower.contains(" or ") || lower.starts_with("either ") {
233            return PremiseType::Disjunctive;
234        }
235
236        // Negative
237        if lower.starts_with("not ") || lower.contains(" not ") || lower.starts_with("no ") {
238            return PremiseType::Negative;
239        }
240
241        // Default to singular
242        PremiseType::Singular
243    }
244
245    /// Add evidence for this premise
246    pub fn with_evidence(mut self, evidence: &str) -> Self {
247        self.evidence.push(evidence.to_string());
248        self
249    }
250}
251
252/// Types of premises in logical arguments
253#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
254pub enum PremiseType {
255    /// Universal (All X are Y)
256    Universal,
257    /// Particular (Some X are Y)
258    Particular,
259    /// Singular (X is Y)
260    Singular,
261    /// Conditional (If X then Y)
262    Conditional,
263    /// Disjunctive (X or Y)
264    Disjunctive,
265    /// Negative (X is not Y)
266    Negative,
267}
268
269/// Common argument forms
270#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
271pub enum ArgumentForm {
272    /// Modus Ponens: P->Q, P |- Q
273    ModusPonens,
274    /// Modus Tollens: P->Q, ~Q |- ~P
275    ModusTollens,
276    /// Hypothetical Syllogism: P->Q, Q->R |- P->R
277    HypotheticalSyllogism,
278    /// Disjunctive Syllogism: P v Q, ~P |- Q
279    DisjunctiveSyllogism,
280    /// Categorical Syllogism: All M are P, All S are M |- All S are P
281    CategoricalSyllogism,
282    /// Constructive Dilemma: (P->Q) ^ (R->S), P v R |- Q v S
283    ConstructiveDilemma,
284    /// Destructive Dilemma: (P->Q) ^ (R->S), ~Q v ~S |- ~P v ~R
285    DestructiveDilemma,
286    /// Reductio Ad Absurdum (proof by contradiction)
287    ReductioAdAbsurdum,
288    /// Unknown or complex form
289    Unknown,
290}
291
292impl ArgumentForm {
293    /// Get the description of this argument form
294    pub fn description(&self) -> &'static str {
295        match self {
296            Self::ModusPonens => "Modus Ponens (affirming the antecedent)",
297            Self::ModusTollens => "Modus Tollens (denying the consequent)",
298            Self::HypotheticalSyllogism => "Hypothetical Syllogism (chain reasoning)",
299            Self::DisjunctiveSyllogism => "Disjunctive Syllogism (process of elimination)",
300            Self::CategoricalSyllogism => "Categorical Syllogism (term-based reasoning)",
301            Self::ConstructiveDilemma => "Constructive Dilemma (complex conditional)",
302            Self::DestructiveDilemma => "Destructive Dilemma (complex conditional)",
303            Self::ReductioAdAbsurdum => "Reductio Ad Absurdum (proof by contradiction)",
304            Self::Unknown => "Unknown or complex argument form",
305        }
306    }
307
308    /// Check if this form is always valid
309    pub fn is_valid_form(&self) -> bool {
310        !matches!(self, Self::Unknown)
311    }
312}
313
314// =============================================================================
315// Fallacy Types
316// =============================================================================
317
318/// Logical fallacies detectable by LaserLogic
319#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
320pub enum Fallacy {
321    // Formal Fallacies (invalid argument structure)
322    /// Affirming the Consequent: P->Q, Q |- P (INVALID)
323    AffirmingConsequent,
324    /// Denying the Antecedent: P->Q, ~P |- ~Q (INVALID)
325    DenyingAntecedent,
326    /// Undistributed Middle: All A are B, All C are B |- All A are C (INVALID)
327    UndistributedMiddle,
328    /// Illicit Major: Distribution error in major term
329    IllicitMajor,
330    /// Illicit Minor: Distribution error in minor term
331    IllicitMinor,
332    /// Four-Term Fallacy: Equivocation in syllogism
333    FourTerms,
334    /// Existential Fallacy: Assuming existence from universal
335    ExistentialFallacy,
336    /// Affirming a Disjunct: P v Q, P |- ~Q (INVALID for inclusive or)
337    AffirmingDisjunct,
338
339    // Semi-Formal Fallacies
340    /// Circular Reasoning: Conclusion appears in premises
341    CircularReasoning,
342    /// Non Sequitur: Conclusion doesn't follow
343    NonSequitur,
344    /// Composition: Parts to whole error
345    Composition,
346    /// Division: Whole to parts error
347    Division,
348
349    // Causal Fallacies
350    /// Post Hoc: A then B, therefore A caused B
351    PostHoc,
352    /// Slippery Slope: Unwarranted chain of consequences
353    SlipperySlope,
354    /// False Cause: Incorrectly identifying causation
355    FalseCause,
356
357    // Relevance Fallacies (informal but detectable through structure)
358    /// Straw Man: Misrepresenting the argument
359    StrawMan,
360    /// False Dichotomy: Only two options when more exist
361    FalseDichotomy,
362    /// Equivocation: Using term with different meanings
363    Equivocation,
364}
365
366impl Fallacy {
367    /// Get a description of this fallacy
368    pub fn description(&self) -> &'static str {
369        match self {
370            Self::AffirmingConsequent => {
371                "Inferring P from P->Q and Q. Just because the outcome occurred doesn't mean the specific cause happened."
372            }
373            Self::DenyingAntecedent => {
374                "Inferring ~Q from P->Q and ~P. The consequent might be true for other reasons."
375            }
376            Self::UndistributedMiddle => {
377                "The middle term is not distributed in at least one premise of the syllogism."
378            }
379            Self::IllicitMajor => {
380                "The major term is distributed in the conclusion but not in the major premise."
381            }
382            Self::IllicitMinor => {
383                "The minor term is distributed in the conclusion but not in the minor premise."
384            }
385            Self::FourTerms => {
386                "Four distinct terms used where a syllogism requires exactly three."
387            }
388            Self::ExistentialFallacy => {
389                "Drawing an existential conclusion from purely universal premises."
390            }
391            Self::AffirmingDisjunct => {
392                "Concluding ~Q from (P v Q) and P. With inclusive OR, both can be true."
393            }
394            Self::CircularReasoning => {
395                "The conclusion is essentially restated in the premises."
396            }
397            Self::NonSequitur => {
398                "The conclusion does not logically follow from the premises."
399            }
400            Self::Composition => {
401                "Assuming what's true of parts must be true of the whole."
402            }
403            Self::Division => {
404                "Assuming what's true of the whole must be true of the parts."
405            }
406            Self::PostHoc => {
407                "Assuming A caused B simply because A preceded B."
408            }
409            Self::SlipperySlope => {
410                "Claiming that one event will inevitably lead to a chain of negative events."
411            }
412            Self::FalseCause => {
413                "Incorrectly identifying something as the cause."
414            }
415            Self::StrawMan => {
416                "Misrepresenting someone's argument to make it easier to attack."
417            }
418            Self::FalseDichotomy => {
419                "Presenting only two options when more exist."
420            }
421            Self::Equivocation => {
422                "Using the same term with different meanings in different parts of the argument."
423            }
424        }
425    }
426
427    /// Get the logical pattern of this fallacy
428    pub fn pattern(&self) -> &'static str {
429        match self {
430            Self::AffirmingConsequent => "P->Q, Q |- P (INVALID)",
431            Self::DenyingAntecedent => "P->Q, ~P |- ~Q (INVALID)",
432            Self::UndistributedMiddle => "All A are B, All C are B |- All A are C (INVALID)",
433            Self::IllicitMajor => "Major term undistributed in premise, distributed in conclusion",
434            Self::IllicitMinor => "Minor term undistributed in premise, distributed in conclusion",
435            Self::FourTerms => "A-B, C-D |- invalid (4 terms, not 3)",
436            Self::ExistentialFallacy => "All P are Q |- Some P are Q (INVALID if no P exist)",
437            Self::AffirmingDisjunct => "P v Q, P |- ~Q (INVALID for inclusive or)",
438            Self::CircularReasoning => "P |- P (trivially valid but uninformative)",
439            Self::NonSequitur => "Premises do not entail conclusion",
440            Self::Composition => "Part(x) is P |- Whole(x) is P (INVALID)",
441            Self::Division => "Whole(x) is P |- Part(x) is P (INVALID)",
442            Self::PostHoc => "A then B |- A caused B (INVALID)",
443            Self::SlipperySlope => "A -> B -> C -> ... -> Z (chain not established)",
444            Self::FalseCause => "Correlation or sequence |- Causation (INVALID)",
445            Self::StrawMan => "Argument(A') attacked instead of Argument(A)",
446            Self::FalseDichotomy => "P v Q presented where P v Q v R v ... exists",
447            Self::Equivocation => "Term T used as T1 and T2 (different meanings)",
448        }
449    }
450
451    /// Get severity (1-5, where 5 is most severe)
452    pub fn severity(&self) -> u8 {
453        match self {
454            Self::AffirmingConsequent | Self::DenyingAntecedent => 5,
455            Self::UndistributedMiddle | Self::IllicitMajor | Self::IllicitMinor => 5,
456            Self::FourTerms | Self::ExistentialFallacy => 4,
457            Self::CircularReasoning | Self::NonSequitur => 5,
458            Self::Composition | Self::Division => 3,
459            Self::PostHoc | Self::FalseCause | Self::SlipperySlope => 4,
460            Self::StrawMan | Self::FalseDichotomy | Self::Equivocation => 4,
461            Self::AffirmingDisjunct => 4,
462        }
463    }
464
465    /// Is this a formal (structural) fallacy?
466    pub fn is_formal(&self) -> bool {
467        matches!(
468            self,
469            Self::AffirmingConsequent
470                | Self::DenyingAntecedent
471                | Self::UndistributedMiddle
472                | Self::IllicitMajor
473                | Self::IllicitMinor
474                | Self::FourTerms
475                | Self::ExistentialFallacy
476                | Self::AffirmingDisjunct
477        )
478    }
479}
480
481/// A detected fallacy with evidence
482#[derive(Debug, Clone, Serialize, Deserialize)]
483pub struct DetectedFallacy {
484    /// The type of fallacy detected
485    pub fallacy: Fallacy,
486    /// Confidence in the detection (0.0 - 1.0)
487    pub confidence: f64,
488    /// Evidence explaining why this is a fallacy
489    pub evidence: String,
490    /// Which premises are involved (indices)
491    pub involved_premises: Vec<usize>,
492    /// Suggested fix
493    pub suggestion: String,
494}
495
496impl DetectedFallacy {
497    /// Create a new detected fallacy
498    pub fn new(fallacy: Fallacy, confidence: f64, evidence: &str) -> Self {
499        Self {
500            fallacy,
501            confidence,
502            evidence: evidence.to_string(),
503            involved_premises: Vec::new(),
504            suggestion: String::new(),
505        }
506    }
507
508    /// Add involved premises
509    pub fn with_premises(mut self, premises: Vec<usize>) -> Self {
510        self.involved_premises = premises;
511        self
512    }
513
514    /// Add a suggestion for fixing
515    pub fn with_suggestion(mut self, suggestion: &str) -> Self {
516        self.suggestion = suggestion.to_string();
517        self
518    }
519}
520
521// =============================================================================
522// Analysis Results
523// =============================================================================
524
525/// Validity status of an argument
526#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
527pub enum ValidityStatus {
528    /// Argument is logically valid (conclusion follows from premises)
529    Valid,
530    /// Argument is logically invalid (conclusion does not follow)
531    Invalid,
532    /// Validity could not be determined
533    Undetermined,
534}
535
536/// Soundness status of an argument
537#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
538pub enum SoundnessStatus {
539    /// Argument is sound (valid with true premises)
540    Sound,
541    /// Argument is unsound (invalid or false premises)
542    Unsound,
543    /// Soundness could not be determined
544    Undetermined,
545}
546
547/// Complete analysis result from LaserLogic
548#[derive(Debug, Clone, Serialize, Deserialize)]
549pub struct LaserLogicResult {
550    /// The argument that was analyzed
551    pub argument: Argument,
552    /// Validity status
553    pub validity: ValidityStatus,
554    /// Validity explanation
555    pub validity_explanation: String,
556    /// Soundness status
557    pub soundness: SoundnessStatus,
558    /// Soundness explanation
559    pub soundness_explanation: String,
560    /// Detected fallacies
561    pub fallacies: Vec<DetectedFallacy>,
562    /// Detected argument form
563    pub argument_form: Option<ArgumentForm>,
564    /// Overall confidence in the analysis (0.0 - 1.0)
565    pub confidence: f64,
566    /// Reasoning steps taken
567    pub reasoning_steps: Vec<String>,
568    /// Suggestions for improvement
569    pub suggestions: Vec<String>,
570    /// Detected contradictions
571    pub contradictions: Vec<Contradiction>,
572}
573
574impl LaserLogicResult {
575    /// Create a new result
576    fn new(argument: Argument) -> Self {
577        Self {
578            argument,
579            validity: ValidityStatus::Undetermined,
580            validity_explanation: String::new(),
581            soundness: SoundnessStatus::Undetermined,
582            soundness_explanation: String::new(),
583            fallacies: Vec::new(),
584            argument_form: None,
585            confidence: 0.0,
586            reasoning_steps: Vec::new(),
587            suggestions: Vec::new(),
588            contradictions: Vec::new(),
589        }
590    }
591
592    /// Get overall verdict
593    pub fn verdict(&self) -> &'static str {
594        match (self.validity, self.soundness) {
595            (ValidityStatus::Valid, SoundnessStatus::Sound) => {
596                "SOUND: Argument is valid with true premises"
597            }
598            (ValidityStatus::Valid, SoundnessStatus::Unsound) => {
599                "VALID BUT UNSOUND: Logic correct, but premises questionable"
600            }
601            (ValidityStatus::Valid, SoundnessStatus::Undetermined) => {
602                "VALID: Logic correct, premises unverified"
603            }
604            (ValidityStatus::Invalid, _) => "INVALID: Conclusion does not follow from premises",
605            (ValidityStatus::Undetermined, _) => "UNDETERMINED: Could not fully analyze",
606        }
607    }
608
609    /// Check if any fallacies were detected
610    pub fn has_fallacies(&self) -> bool {
611        !self.fallacies.is_empty()
612    }
613
614    /// Get the most severe fallacy
615    pub fn most_severe_fallacy(&self) -> Option<&DetectedFallacy> {
616        self.fallacies
617            .iter()
618            .max_by_key(|f| (f.fallacy.severity(), (f.confidence * 100.0) as u32))
619    }
620
621    /// Check if argument is logically valid
622    pub fn is_valid(&self) -> bool {
623        self.validity == ValidityStatus::Valid
624    }
625
626    /// Check if argument is sound
627    pub fn is_sound(&self) -> bool {
628        self.soundness == SoundnessStatus::Sound
629    }
630}
631
632/// A detected contradiction between statements
633#[derive(Debug, Clone, Serialize, Deserialize)]
634pub struct Contradiction {
635    /// First conflicting statement (premise index or "conclusion")
636    pub statement_a: String,
637    /// Second conflicting statement
638    pub statement_b: String,
639    /// Type of contradiction
640    pub contradiction_type: ContradictionType,
641    /// Explanation
642    pub explanation: String,
643}
644
645/// Types of contradictions
646#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
647pub enum ContradictionType {
648    /// Direct negation (P and ~P)
649    DirectNegation,
650    /// Mutual exclusion (cannot both be true)
651    MutualExclusion,
652    /// Implicit contradiction (follows from premises)
653    Implicit,
654}
655
656// =============================================================================
657// LaserLogic Module Implementation
658// =============================================================================
659
660/// LaserLogic reasoning module for precision deductive analysis.
661///
662/// Provides formal logical analysis including:
663/// - Argument structure validation
664/// - Fallacy detection
665/// - Validity and soundness checking
666/// - Syllogism analysis
667pub struct LaserLogic {
668    /// Module configuration
669    config: ThinkToolModuleConfig,
670    /// Analysis configuration
671    analysis_config: LaserLogicConfig,
672}
673
674impl Default for LaserLogic {
675    fn default() -> Self {
676        Self::new()
677    }
678}
679
680impl LaserLogic {
681    /// Create a new LaserLogic module with default configuration.
682    pub fn new() -> Self {
683        Self {
684            config: ThinkToolModuleConfig {
685                name: "LaserLogic".to_string(),
686                version: "3.0.0".to_string(),
687                description: "Precision deductive reasoning with fallacy detection".to_string(),
688                confidence_weight: 0.25,
689            },
690            analysis_config: LaserLogicConfig::default(),
691        }
692    }
693
694    /// Create with custom configuration
695    pub fn with_config(analysis_config: LaserLogicConfig) -> Self {
696        Self {
697            config: ThinkToolModuleConfig {
698                name: "LaserLogic".to_string(),
699                version: "3.0.0".to_string(),
700                description: "Precision deductive reasoning with fallacy detection".to_string(),
701                confidence_weight: 0.25,
702            },
703            analysis_config,
704        }
705    }
706
707    /// Get the analysis configuration
708    pub fn analysis_config(&self) -> &LaserLogicConfig {
709        &self.analysis_config
710    }
711
712    /// Analyze an argument given premises and conclusion
713    pub fn analyze_argument(
714        &self,
715        premises: &[&str],
716        conclusion: &str,
717    ) -> Result<LaserLogicResult> {
718        let argument = Argument::new(premises.to_vec(), conclusion);
719        self.analyze(argument)
720    }
721
722    /// Analyze a structured argument
723    pub fn analyze(&self, argument: Argument) -> Result<LaserLogicResult> {
724        // Validate input
725        if argument.premises.is_empty() {
726            return Err(Error::validation("Argument must have at least one premise"));
727        }
728
729        if argument.premises.len() > self.analysis_config.max_premise_depth {
730            return Err(Error::validation(format!(
731                "Too many premises ({} > {})",
732                argument.premises.len(),
733                self.analysis_config.max_premise_depth
734            )));
735        }
736
737        let mut result = LaserLogicResult::new(argument.clone());
738
739        // Step 1: Detect argument form
740        result
741            .reasoning_steps
742            .push("Step 1: Identifying argument form...".to_string());
743        result.argument_form = self.detect_argument_form(&argument);
744        if let Some(form) = result.argument_form {
745            result
746                .reasoning_steps
747                .push(format!("  Detected: {}", form.description()));
748        }
749
750        // Step 2: Check validity
751        if self.analysis_config.check_validity {
752            result
753                .reasoning_steps
754                .push("Step 2: Checking logical validity...".to_string());
755            let (validity, explanation) = self.check_validity(&argument);
756            result.validity = validity;
757            result.validity_explanation = explanation.clone();
758            result.reasoning_steps.push(format!("  {}", explanation));
759        }
760
761        // Step 3: Detect fallacies
762        if self.analysis_config.detect_fallacies {
763            result
764                .reasoning_steps
765                .push("Step 3: Scanning for fallacies...".to_string());
766            let fallacies = self.detect_fallacies(&argument);
767            for fallacy in &fallacies {
768                result.reasoning_steps.push(format!(
769                    "  Found: {} (confidence: {:.2})",
770                    fallacy.fallacy.description(),
771                    fallacy.confidence
772                ));
773            }
774            result.fallacies = fallacies;
775        }
776
777        // Step 4: Detect contradictions
778        if self.analysis_config.detect_contradictions {
779            result
780                .reasoning_steps
781                .push("Step 4: Checking for contradictions...".to_string());
782            result.contradictions = self.detect_contradictions(&argument);
783            if result.contradictions.is_empty() {
784                result
785                    .reasoning_steps
786                    .push("  No contradictions found.".to_string());
787            } else {
788                for contradiction in &result.contradictions {
789                    result.reasoning_steps.push(format!(
790                        "  Contradiction: {} vs {} - {}",
791                        contradiction.statement_a,
792                        contradiction.statement_b,
793                        contradiction.explanation
794                    ));
795                }
796            }
797        }
798
799        // Step 5: Check soundness
800        if self.analysis_config.check_soundness {
801            result
802                .reasoning_steps
803                .push("Step 5: Evaluating soundness...".to_string());
804            let (soundness, explanation) = self.check_soundness(&argument, result.validity);
805            result.soundness = soundness;
806            result.soundness_explanation = explanation.clone();
807            result.reasoning_steps.push(format!("  {}", explanation));
808        }
809
810        // Calculate overall confidence
811        result.confidence = self.calculate_confidence(&result);
812
813        // Generate suggestions
814        result.suggestions = self.generate_suggestions(&result);
815
816        Ok(result)
817    }
818
819    /// Clean a logical term by removing punctuation and extra whitespace
820    fn clean_term(term: &str) -> String {
821        term.trim()
822            .trim_matches(|c: char| c.is_ascii_punctuation())
823            .trim()
824            .to_string()
825    }
826
827    /// Detect the argument form
828    fn detect_argument_form(&self, argument: &Argument) -> Option<ArgumentForm> {
829        // Look for conditional patterns
830        let has_conditional = argument
831            .premises
832            .iter()
833            .any(|p| p.premise_type == PremiseType::Conditional);
834
835        let has_disjunctive = argument
836            .premises
837            .iter()
838            .any(|p| p.premise_type == PremiseType::Disjunctive);
839
840        let has_universal = argument
841            .premises
842            .iter()
843            .any(|p| p.premise_type == PremiseType::Universal);
844
845        // Check for modus ponens pattern: P->Q, P |- Q
846        if has_conditional && argument.premises.len() >= 2 {
847            // Simplified detection - look for "if...then" followed by affirming antecedent
848            let conditional = argument
849                .premises
850                .iter()
851                .find(|p| p.premise_type == PremiseType::Conditional);
852
853            if let Some(cond) = conditional {
854                let cond_lower = cond.statement.to_lowercase();
855                if cond_lower.starts_with("if ") {
856                    // Extract antecedent (roughly)
857                    if let Some(then_idx) = cond_lower.find(" then ") {
858                        // Clean the antecedent by removing punctuation
859                        let antecedent = Self::clean_term(&cond_lower[3..then_idx]);
860
861                        // Check if another premise affirms the antecedent
862                        let affirms_antecedent = argument.premises.iter().any(|p| {
863                            let p_lower = p.statement.to_lowercase();
864                            let p_clean = Self::clean_term(&p_lower);
865                            p.premise_type != PremiseType::Conditional
866                                && (p_clean == antecedent || p_clean.contains(&antecedent))
867                        });
868
869                        // Check if another premise denies the antecedent (potential fallacy)
870                        let denies_antecedent = argument.premises.iter().any(|p| {
871                            let p_lower = p.statement.to_lowercase();
872                            (p_lower.contains("not ") || p_lower.starts_with("no "))
873                                && p_lower.contains(&antecedent)
874                        });
875
876                        if affirms_antecedent {
877                            return Some(ArgumentForm::ModusPonens);
878                        }
879                        if denies_antecedent {
880                            // This would be denying the antecedent - but we return the form anyway
881                            return Some(ArgumentForm::Unknown); // Fallacy pattern
882                        }
883                    }
884                }
885            }
886        }
887
888        // Check for disjunctive syllogism: P v Q, ~P |- Q
889        if has_disjunctive && argument.premises.len() >= 2 {
890            let has_negation = argument.premises.iter().any(|p| {
891                p.premise_type == PremiseType::Negative
892                    || p.statement.to_lowercase().contains("not ")
893            });
894
895            if has_negation {
896                return Some(ArgumentForm::DisjunctiveSyllogism);
897            }
898        }
899
900        // Check for categorical syllogism: All M are P, All S are M |- All S are P
901        // Also handles mixed syllogisms with singular premises (e.g., Socratic syllogism)
902        if has_universal && argument.premises.len() >= 2 {
903            let universal_count = argument
904                .premises
905                .iter()
906                .filter(|p| p.premise_type == PremiseType::Universal)
907                .count();
908
909            // Classic categorical syllogism requires 2 universal premises
910            if universal_count >= 2 {
911                return Some(ArgumentForm::CategoricalSyllogism);
912            }
913
914            // Mixed syllogism: 1 universal + 1 singular (e.g., "All humans are mortal" + "Socrates is human")
915            // This is a valid syllogistic form (Barbara with singular minor)
916            let singular_count = argument
917                .premises
918                .iter()
919                .filter(|p| p.premise_type == PremiseType::Singular)
920                .count();
921
922            if universal_count >= 1 && singular_count >= 1 {
923                // Verify the singular premise relates to the universal
924                // by checking for shared terms
925                let universal_premise = argument
926                    .premises
927                    .iter()
928                    .find(|p| p.premise_type == PremiseType::Universal)?;
929
930                let singular_premise = argument
931                    .premises
932                    .iter()
933                    .find(|p| p.premise_type == PremiseType::Singular)?;
934
935                // Extract predicate from universal (e.g., "humans" from "All humans are mortal")
936                let univ_lower = universal_premise.statement.to_lowercase();
937                if univ_lower.starts_with("all ") {
938                    if let Some(are_idx) = univ_lower.find(" are ") {
939                        let subject_term = Self::clean_term(&univ_lower[4..are_idx]);
940
941                        // Check if singular premise predicates the same term
942                        // e.g., "Socrates is human" should match "humans" (with stemming consideration)
943                        let sing_lower = singular_premise.statement.to_lowercase();
944                        let subject_stem = subject_term.trim_end_matches('s');
945
946                        if sing_lower.contains(&subject_term) || sing_lower.contains(subject_stem) {
947                            return Some(ArgumentForm::CategoricalSyllogism);
948                        }
949                    }
950                }
951            }
952        }
953
954        None
955    }
956
957    /// Check if the argument is valid
958    fn check_validity(&self, argument: &Argument) -> (ValidityStatus, String) {
959        // Check if we detected a known valid form
960        if let Some(form) = self.detect_argument_form(argument) {
961            if form.is_valid_form() {
962                return (
963                    ValidityStatus::Valid,
964                    format!("Argument follows valid {} pattern", form.description()),
965                );
966            }
967        }
968
969        // Check for obvious invalidity patterns
970        let fallacies = self.detect_formal_fallacies(argument);
971        if !fallacies.is_empty() {
972            let fallacy_names: Vec<_> = fallacies.iter().map(|f| f.fallacy.pattern()).collect();
973            return (
974                ValidityStatus::Invalid,
975                format!(
976                    "Argument contains formal fallacy: {}",
977                    fallacy_names.join(", ")
978                ),
979            );
980        }
981
982        // Default to undetermined for complex arguments
983        (
984            ValidityStatus::Undetermined,
985            "Argument structure too complex for automated validation. Manual review recommended."
986                .to_string(),
987        )
988    }
989
990    /// Detect formal fallacies only
991    fn detect_formal_fallacies(&self, argument: &Argument) -> Vec<DetectedFallacy> {
992        let mut fallacies = Vec::new();
993
994        // Check for affirming the consequent
995        if let Some(fallacy) = self.check_affirming_consequent(argument) {
996            fallacies.push(fallacy);
997        }
998
999        // Check for denying the antecedent
1000        if let Some(fallacy) = self.check_denying_antecedent(argument) {
1001            fallacies.push(fallacy);
1002        }
1003
1004        // Check for undistributed middle in syllogisms
1005        if let Some(fallacy) = self.check_undistributed_middle(argument) {
1006            fallacies.push(fallacy);
1007        }
1008
1009        fallacies
1010    }
1011
1012    /// Detect all fallacies (formal and informal)
1013    fn detect_fallacies(&self, argument: &Argument) -> Vec<DetectedFallacy> {
1014        let mut fallacies = self.detect_formal_fallacies(argument);
1015
1016        // Check for circular reasoning
1017        if let Some(fallacy) = self.check_circular_reasoning(argument) {
1018            fallacies.push(fallacy);
1019        }
1020
1021        // Check for false dichotomy
1022        if let Some(fallacy) = self.check_false_dichotomy(argument) {
1023            fallacies.push(fallacy);
1024        }
1025
1026        // Check for non sequitur (basic)
1027        if let Some(fallacy) = self.check_non_sequitur(argument) {
1028            fallacies.push(fallacy);
1029        }
1030
1031        fallacies
1032    }
1033
1034    /// Check for affirming the consequent: P->Q, Q |- P
1035    fn check_affirming_consequent(&self, argument: &Argument) -> Option<DetectedFallacy> {
1036        // Find conditional premise
1037        let conditional = argument
1038            .premises
1039            .iter()
1040            .enumerate()
1041            .find(|(_, p)| p.premise_type == PremiseType::Conditional)?;
1042
1043        let cond_lower = conditional.1.statement.to_lowercase();
1044
1045        // Extract consequent (after "then") - clean punctuation
1046        let then_idx = cond_lower.find(" then ")?;
1047        let consequent = Self::clean_term(&cond_lower[then_idx + 6..]);
1048
1049        // Check if another premise affirms the consequent
1050        let affirming_premise = argument.premises.iter().enumerate().find(|(i, p)| {
1051            *i != conditional.0 && p.premise_type != PremiseType::Conditional && {
1052                let p_clean = Self::clean_term(&p.statement.to_lowercase());
1053                p_clean.contains(&consequent) || consequent.contains(&p_clean)
1054            }
1055        });
1056
1057        if affirming_premise.is_some() {
1058            // Check if conclusion affirms the antecedent
1059            let antecedent = Self::clean_term(&cond_lower[3..then_idx]);
1060            let conclusion_clean = Self::clean_term(&argument.conclusion.to_lowercase());
1061            if conclusion_clean.contains(&antecedent)
1062                || antecedent.contains(&conclusion_clean)
1063                || Self::terms_match(&conclusion_clean, &antecedent)
1064            {
1065                return Some(
1066                    DetectedFallacy::new(
1067                        Fallacy::AffirmingConsequent,
1068                        0.85,
1069                        "Argument affirms the consequent of a conditional to conclude the antecedent",
1070                    )
1071                    .with_premises(vec![conditional.0])
1072                    .with_suggestion("Consider other possible causes for the consequent being true"),
1073                );
1074            }
1075        }
1076
1077        None
1078    }
1079
1080    /// Check if two terms match (handles minor variations like tense)
1081    fn terms_match(a: &str, b: &str) -> bool {
1082        if a == b {
1083            return true;
1084        }
1085
1086        // Handle past tense variations (e.g., "it rained" vs "it rains")
1087        let a_words: Vec<&str> = a.split_whitespace().collect();
1088        let b_words: Vec<&str> = b.split_whitespace().collect();
1089
1090        if a_words.len() == b_words.len() {
1091            let mut matches = 0;
1092            for (aw, bw) in a_words.iter().zip(b_words.iter()) {
1093                if aw == bw {
1094                    matches += 1;
1095                } else {
1096                    // Check for verb tense variations
1097                    let aw_stem = aw.trim_end_matches("ed").trim_end_matches('s');
1098                    let bw_stem = bw.trim_end_matches("ed").trim_end_matches('s');
1099                    if aw_stem == bw_stem {
1100                        matches += 1;
1101                    }
1102                }
1103            }
1104            return matches == a_words.len();
1105        }
1106
1107        false
1108    }
1109
1110    /// Check for denying the antecedent: P->Q, ~P |- ~Q
1111    fn check_denying_antecedent(&self, argument: &Argument) -> Option<DetectedFallacy> {
1112        // Find conditional premise
1113        let conditional = argument
1114            .premises
1115            .iter()
1116            .enumerate()
1117            .find(|(_, p)| p.premise_type == PremiseType::Conditional)?;
1118
1119        let cond_lower = conditional.1.statement.to_lowercase();
1120        let then_idx = cond_lower.find(" then ")?;
1121        let antecedent = Self::clean_term(&cond_lower[3..then_idx]);
1122        let consequent = Self::clean_term(&cond_lower[then_idx + 6..]);
1123
1124        // Check if another premise denies the antecedent
1125        let denying_premise = argument.premises.iter().enumerate().find(|(i, p)| {
1126            *i != conditional.0
1127                && p.statement.to_lowercase().contains(&antecedent)
1128                && (p.statement.to_lowercase().contains("not ")
1129                    || p.statement.to_lowercase().starts_with("no "))
1130        });
1131
1132        if denying_premise.is_some() {
1133            // Check if conclusion denies the consequent
1134            if argument.conclusion.to_lowercase().contains(&consequent)
1135                && (argument.conclusion.to_lowercase().contains("not ")
1136                    || argument.conclusion.to_lowercase().starts_with("no "))
1137            {
1138                return Some(
1139                    DetectedFallacy::new(
1140                        Fallacy::DenyingAntecedent,
1141                        0.85,
1142                        "Argument denies the antecedent to conclude the negation of the consequent",
1143                    )
1144                    .with_premises(vec![conditional.0])
1145                    .with_suggestion("The consequent might still be true for other reasons"),
1146                );
1147            }
1148        }
1149
1150        None
1151    }
1152
1153    /// Check for undistributed middle in syllogisms
1154    fn check_undistributed_middle(&self, argument: &Argument) -> Option<DetectedFallacy> {
1155        // Need at least 2 universal premises for a syllogism
1156        let universals: Vec<_> = argument
1157            .premises
1158            .iter()
1159            .enumerate()
1160            .filter(|(_, p)| p.premise_type == PremiseType::Universal)
1161            .collect();
1162
1163        if universals.len() < 2 {
1164            return None;
1165        }
1166
1167        // Extract terms from "All X are Y" patterns
1168        let mut terms: Vec<HashSet<String>> = Vec::new();
1169        for (_, premise) in &universals {
1170            let lower = premise.statement.to_lowercase();
1171            if lower.starts_with("all ") {
1172                if let Some(are_idx) = lower.find(" are ") {
1173                    let subject = Self::clean_term(&lower[4..are_idx]);
1174                    let predicate = Self::clean_term(&lower[are_idx + 5..]);
1175                    let mut term_set = HashSet::new();
1176                    term_set.insert(subject);
1177                    term_set.insert(predicate);
1178                    terms.push(term_set);
1179                }
1180            }
1181        }
1182
1183        if terms.len() >= 2 {
1184            // Find middle term (appears in both premises)
1185            let intersection: HashSet<_> = terms[0].intersection(&terms[1]).cloned().collect();
1186
1187            if intersection.is_empty() {
1188                // No shared term - possible four-term fallacy or undistributed middle
1189                return Some(
1190                    DetectedFallacy::new(
1191                        Fallacy::UndistributedMiddle,
1192                        0.75,
1193                        "The middle term may not be properly distributed across premises",
1194                    )
1195                    .with_premises(vec![universals[0].0, universals[1].0])
1196                    .with_suggestion(
1197                        "Ensure the middle term is distributed in at least one premise",
1198                    ),
1199                );
1200            }
1201        }
1202
1203        None
1204    }
1205
1206    /// Check for circular reasoning
1207    fn check_circular_reasoning(&self, argument: &Argument) -> Option<DetectedFallacy> {
1208        let conclusion_lower = argument.conclusion.to_lowercase();
1209        let conclusion_words: HashSet<_> = conclusion_lower
1210            .split_whitespace()
1211            .filter(|w| w.len() > 3)
1212            .collect();
1213
1214        // Need at least 2 significant words in conclusion to detect circular reasoning
1215        // This prevents false positives with very short conclusions
1216        if conclusion_words.len() < 2 {
1217            return None;
1218        }
1219
1220        for (idx, premise) in argument.premises.iter().enumerate() {
1221            let premise_lower = premise.statement.to_lowercase();
1222            let premise_words: HashSet<_> = premise_lower
1223                .split_whitespace()
1224                .filter(|w| w.len() > 3)
1225                .collect();
1226
1227            // For circular reasoning, check if the conclusion is essentially
1228            // restating something from the premise.
1229            let overlap = conclusion_words.intersection(&premise_words).count();
1230            let conclusion_len = conclusion_words.len();
1231
1232            // Circular reasoning detection:
1233            // - If conclusion words are mostly found in premise (>= 80% overlap with conclusion)
1234            // - OR if we have at least 2 overlapping significant words with high ratio
1235            let conclusion_overlap_ratio = overlap as f64 / conclusion_len as f64;
1236
1237            // High overlap with conclusion means the conclusion restates the premise
1238            if overlap >= 2 && conclusion_overlap_ratio >= 0.8 {
1239                return Some(
1240                    DetectedFallacy::new(
1241                        Fallacy::CircularReasoning,
1242                        0.7,
1243                        "Premise and conclusion appear to state essentially the same thing",
1244                    )
1245                    .with_premises(vec![idx])
1246                    .with_suggestion(
1247                        "Provide independent evidence that doesn't restate the conclusion",
1248                    ),
1249                );
1250            }
1251        }
1252
1253        None
1254    }
1255
1256    /// Check for false dichotomy
1257    fn check_false_dichotomy(&self, argument: &Argument) -> Option<DetectedFallacy> {
1258        // Look for "either...or" patterns without "or" alternatives
1259        for (idx, premise) in argument.premises.iter().enumerate() {
1260            let lower = premise.statement.to_lowercase();
1261
1262            if (lower.contains("either ") && lower.contains(" or ")) || lower.contains(" only ") {
1263                // Check if it presents just two options
1264                let or_count = lower.matches(" or ").count();
1265                if or_count == 1 {
1266                    return Some(
1267                        DetectedFallacy::new(
1268                            Fallacy::FalseDichotomy,
1269                            0.6,
1270                            "Argument presents only two options when more may exist",
1271                        )
1272                        .with_premises(vec![idx])
1273                        .with_suggestion("Consider whether additional alternatives exist"),
1274                    );
1275                }
1276            }
1277        }
1278
1279        None
1280    }
1281
1282    /// Check for basic non sequitur
1283    fn check_non_sequitur(&self, argument: &Argument) -> Option<DetectedFallacy> {
1284        // Very basic check: are there any shared significant terms between premises and conclusion?
1285        let conclusion_words: HashSet<_> = argument
1286            .conclusion
1287            .to_lowercase()
1288            .split_whitespace()
1289            .filter(|w| w.len() > 4)
1290            .map(|s| s.to_string())
1291            .collect();
1292
1293        let mut any_overlap = false;
1294        for premise in &argument.premises {
1295            let premise_words: HashSet<_> = premise
1296                .statement
1297                .to_lowercase()
1298                .split_whitespace()
1299                .filter(|w| w.len() > 4)
1300                .map(|s| s.to_string())
1301                .collect();
1302
1303            if !conclusion_words.is_disjoint(&premise_words) {
1304                any_overlap = true;
1305                break;
1306            }
1307        }
1308
1309        if !any_overlap && !conclusion_words.is_empty() {
1310            return Some(
1311                DetectedFallacy::new(
1312                    Fallacy::NonSequitur,
1313                    0.5,
1314                    "Conclusion appears disconnected from the premises",
1315                )
1316                .with_suggestion(
1317                    "Establish a clear logical connection between premises and conclusion",
1318                ),
1319            );
1320        }
1321
1322        None
1323    }
1324
1325    /// Detect contradictions in the argument
1326    fn detect_contradictions(&self, argument: &Argument) -> Vec<Contradiction> {
1327        let mut contradictions = Vec::new();
1328
1329        // Check each pair of premises
1330        for (i, p1) in argument.premises.iter().enumerate() {
1331            for (j, p2) in argument.premises.iter().enumerate().skip(i + 1) {
1332                if let Some(contradiction) = self.check_contradiction(&p1.statement, &p2.statement)
1333                {
1334                    contradictions.push(Contradiction {
1335                        statement_a: format!("Premise {}", i + 1),
1336                        statement_b: format!("Premise {}", j + 1),
1337                        contradiction_type: contradiction.0,
1338                        explanation: contradiction.1,
1339                    });
1340                }
1341            }
1342        }
1343
1344        // Check premises against conclusion
1345        for (i, premise) in argument.premises.iter().enumerate() {
1346            if let Some(contradiction) =
1347                self.check_contradiction(&premise.statement, &argument.conclusion)
1348            {
1349                contradictions.push(Contradiction {
1350                    statement_a: format!("Premise {}", i + 1),
1351                    statement_b: "Conclusion".to_string(),
1352                    contradiction_type: contradiction.0,
1353                    explanation: contradiction.1,
1354                });
1355            }
1356        }
1357
1358        contradictions
1359    }
1360
1361    /// Check if two statements contradict
1362    fn check_contradiction(&self, a: &str, b: &str) -> Option<(ContradictionType, String)> {
1363        let a_lower = a.to_lowercase();
1364        let b_lower = b.to_lowercase();
1365
1366        // Check for direct negation patterns
1367        let negation_patterns = [
1368            ("is true", "is false"),
1369            ("exists", "does not exist"),
1370            ("is valid", "is invalid"),
1371            ("should", "should not"),
1372            ("must", "must not"),
1373            ("always", "never"),
1374            ("all", "none"),
1375            ("is ", "is not "),
1376            ("can ", "cannot "),
1377            ("will ", "will not "),
1378        ];
1379
1380        for (pos, neg) in &negation_patterns {
1381            if (a_lower.contains(pos) && b_lower.contains(neg))
1382                || (a_lower.contains(neg) && b_lower.contains(pos))
1383            {
1384                // Check for subject overlap (with relaxed filtering for short subjects)
1385                if self.have_subject_overlap(&a_lower, &b_lower) {
1386                    return Some((
1387                        ContradictionType::DirectNegation,
1388                        format!(
1389                            "Direct contradiction detected: one states '{}' while other states '{}'",
1390                            pos, neg
1391                        ),
1392                    ));
1393                }
1394            }
1395        }
1396
1397        None
1398    }
1399
1400    /// Check if two statements share a common subject
1401    fn have_subject_overlap(&self, a: &str, b: &str) -> bool {
1402        let stopwords: HashSet<&str> = [
1403            "the", "a", "an", "is", "are", "was", "were", "be", "been", "being", "have", "has",
1404            "had", "do", "does", "did", "will", "would", "could", "should", "may", "might", "must",
1405            "shall", "can", "need", "dare", "ought", "used", "to", "of", "in", "for", "on", "with",
1406            "at", "by", "from", "as", "into", "through", "during", "before", "after", "above",
1407            "below", "between", "under", "again", "further", "then", "once", "that", "this",
1408            "these", "those", "not", "no", "nor", "and", "but", "or", "if", "while", "because",
1409            "until", "although", "since", "when", "where", "why", "how", "all", "each", "every",
1410            "both", "few", "more", "most", "other", "some", "such", "only", "own", "same", "so",
1411            "than", "too", "very", "just", "true", "false",
1412        ]
1413        .iter()
1414        .cloned()
1415        .collect();
1416
1417        // Use minimum length of 1 to catch single-letter subjects like "X"
1418        let words_a: HashSet<&str> = a
1419            .split_whitespace()
1420            .filter(|w| !stopwords.contains(w) && !w.is_empty())
1421            .collect();
1422
1423        let words_b: HashSet<&str> = b
1424            .split_whitespace()
1425            .filter(|w| !stopwords.contains(w) && !w.is_empty())
1426            .collect();
1427
1428        if words_a.is_empty() || words_b.is_empty() {
1429            // If both have the same structure after removing stopwords,
1430            // check if they're talking about the same subject
1431            // For "X is true" vs "X is false", both reduce to just "x"
1432            return true;
1433        }
1434
1435        let overlap = words_a.intersection(&words_b).count();
1436
1437        // If there's any overlap, consider it a match
1438        overlap > 0
1439    }
1440
1441    /// Check soundness (valid + true premises)
1442    fn check_soundness(
1443        &self,
1444        argument: &Argument,
1445        validity: ValidityStatus,
1446    ) -> (SoundnessStatus, String) {
1447        if validity != ValidityStatus::Valid {
1448            return (
1449                SoundnessStatus::Unsound,
1450                "Argument is invalid, therefore unsound".to_string(),
1451            );
1452        }
1453
1454        // Calculate average premise confidence
1455        let avg_confidence: f64 = argument.premises.iter().map(|p| p.confidence).sum::<f64>()
1456            / argument.premises.len() as f64;
1457
1458        if avg_confidence >= self.analysis_config.confidence_threshold {
1459            (
1460                SoundnessStatus::Sound,
1461                format!(
1462                    "Argument is valid and premises have high confidence ({:.0}%)",
1463                    avg_confidence * 100.0
1464                ),
1465            )
1466        } else if avg_confidence >= 0.5 {
1467            (
1468                SoundnessStatus::Undetermined,
1469                format!(
1470                    "Argument is valid but premise truth is uncertain ({:.0}% confidence)",
1471                    avg_confidence * 100.0
1472                ),
1473            )
1474        } else {
1475            (
1476                SoundnessStatus::Unsound,
1477                format!(
1478                    "Argument is valid but premises have low confidence ({:.0}%)",
1479                    avg_confidence * 100.0
1480                ),
1481            )
1482        }
1483    }
1484
1485    /// Calculate overall confidence in the analysis
1486    fn calculate_confidence(&self, result: &LaserLogicResult) -> f64 {
1487        let mut confidence = 0.7; // Base confidence
1488
1489        // Boost for detected form
1490        if result.argument_form.is_some() {
1491            confidence += 0.1;
1492        }
1493
1494        // Boost for determined validity
1495        if result.validity != ValidityStatus::Undetermined {
1496            confidence += 0.1;
1497        }
1498
1499        // Penalty for detected fallacies
1500        let fallacy_penalty = result.fallacies.len() as f64 * 0.05;
1501        confidence -= fallacy_penalty.min(0.2);
1502
1503        // Penalty for contradictions
1504        if !result.contradictions.is_empty() {
1505            confidence -= 0.15;
1506        }
1507
1508        confidence.clamp(0.0, 1.0)
1509    }
1510
1511    /// Generate suggestions for improving the argument
1512    fn generate_suggestions(&self, result: &LaserLogicResult) -> Vec<String> {
1513        let mut suggestions = Vec::new();
1514
1515        // Suggestions based on validity
1516        if result.validity == ValidityStatus::Invalid {
1517            suggestions.push(
1518                "Restructure the argument to ensure the conclusion follows logically from the premises."
1519                    .to_string(),
1520            );
1521        }
1522
1523        // Suggestions based on fallacies
1524        for fallacy in &result.fallacies {
1525            if !fallacy.suggestion.is_empty() {
1526                suggestions.push(fallacy.suggestion.clone());
1527            }
1528        }
1529
1530        // Suggestions based on contradictions
1531        if !result.contradictions.is_empty() {
1532            suggestions.push(
1533                "Resolve contradictions by revising conflicting premises or clarifying terms."
1534                    .to_string(),
1535            );
1536        }
1537
1538        // Suggestions based on soundness
1539        if result.soundness == SoundnessStatus::Undetermined {
1540            suggestions
1541                .push("Provide additional evidence to support premise truth claims.".to_string());
1542        }
1543
1544        // Argument form suggestions
1545        if result.argument_form.is_none() {
1546            suggestions.push(
1547                "Consider restructuring into a standard argument form (modus ponens, syllogism, etc.) for clarity."
1548                    .to_string(),
1549            );
1550        }
1551
1552        // Deduplicate
1553        suggestions.sort();
1554        suggestions.dedup();
1555
1556        suggestions
1557    }
1558}
1559
1560impl ThinkToolModule for LaserLogic {
1561    fn config(&self) -> &ThinkToolModuleConfig {
1562        &self.config
1563    }
1564
1565    fn execute(&self, context: &ThinkToolContext) -> Result<ThinkToolOutput> {
1566        // Parse the query as an argument
1567        // Expected format: "Premise 1. Premise 2. Therefore, Conclusion."
1568        // Or: premises separated by semicolons, conclusion after "therefore" or "hence"
1569
1570        let query = &context.query;
1571
1572        // Try to parse premises and conclusion from the query
1573        let (premises, conclusion) = self.parse_argument_from_query(query)?;
1574
1575        // Analyze the argument
1576        let result = self.analyze_argument(&premises, conclusion)?;
1577
1578        // Convert to ThinkToolOutput
1579        Ok(ThinkToolOutput {
1580            module: self.config.name.clone(),
1581            confidence: result.confidence,
1582            output: serde_json::json!({
1583                "validity": format!("{:?}", result.validity),
1584                "validity_explanation": result.validity_explanation,
1585                "soundness": format!("{:?}", result.soundness),
1586                "soundness_explanation": result.soundness_explanation,
1587                "argument_form": result.argument_form.map(|f| f.description()),
1588                "fallacies": result.fallacies.iter().map(|f| {
1589                    serde_json::json!({
1590                        "type": format!("{:?}", f.fallacy),
1591                        "description": f.fallacy.description(),
1592                        "pattern": f.fallacy.pattern(),
1593                        "confidence": f.confidence,
1594                        "evidence": f.evidence,
1595                        "suggestion": f.suggestion
1596                    })
1597                }).collect::<Vec<_>>(),
1598                "contradictions": result.contradictions.iter().map(|c| {
1599                    serde_json::json!({
1600                        "between": [c.statement_a, c.statement_b],
1601                        "type": format!("{:?}", c.contradiction_type),
1602                        "explanation": c.explanation
1603                    })
1604                }).collect::<Vec<_>>(),
1605                "verdict": result.verdict(),
1606                "suggestions": result.suggestions,
1607                "reasoning_steps": result.reasoning_steps
1608            }),
1609        })
1610    }
1611}
1612
1613impl LaserLogic {
1614    /// Parse an argument from a natural language query
1615    fn parse_argument_from_query<'a>(&self, query: &'a str) -> Result<(Vec<&'a str>, &'a str)> {
1616        let query = query.trim();
1617
1618        // Look for conclusion markers
1619        let conclusion_markers = [
1620            "therefore,",
1621            "therefore",
1622            "hence,",
1623            "hence",
1624            "thus,",
1625            "thus",
1626            "so,",
1627            "consequently,",
1628            "consequently",
1629            "it follows that",
1630            "we can conclude",
1631            "which means",
1632        ];
1633
1634        for marker in &conclusion_markers {
1635            if let Some(idx) = query.to_lowercase().find(marker) {
1636                let premises_part = &query[..idx];
1637                let conclusion_start = idx + marker.len();
1638                let conclusion = query[conclusion_start..]
1639                    .trim()
1640                    .trim_start_matches(',')
1641                    .trim();
1642
1643                // Parse premises (split by period, semicolon, or "and")
1644                let premises: Vec<&str> = premises_part
1645                    .split(['.', ';'])
1646                    .map(|s| s.trim())
1647                    .filter(|s| !s.is_empty())
1648                    .collect();
1649
1650                if !premises.is_empty() && !conclusion.is_empty() {
1651                    return Ok((premises, conclusion));
1652                }
1653            }
1654        }
1655
1656        // Fallback: try splitting by sentences
1657        let sentences: Vec<&str> = query
1658            .split('.')
1659            .map(|s| s.trim())
1660            .filter(|s| !s.is_empty())
1661            .collect();
1662
1663        if sentences.len() >= 2 {
1664            let premises = &sentences[..sentences.len() - 1];
1665            let conclusion = sentences.last().unwrap();
1666            return Ok((premises.to_vec(), conclusion));
1667        }
1668
1669        Err(Error::validation(
1670            "Could not parse argument structure. Expected format: 'Premise 1. Premise 2. Therefore, Conclusion.' or similar.",
1671        ))
1672    }
1673}
1674
1675// =============================================================================
1676// Tests
1677// =============================================================================
1678
1679#[cfg(test)]
1680mod tests {
1681    use super::*;
1682
1683    #[test]
1684    fn test_config_default() {
1685        let config = LaserLogicConfig::default();
1686        assert!(config.detect_fallacies);
1687        assert!(config.check_validity);
1688        assert!(config.check_soundness);
1689    }
1690
1691    #[test]
1692    fn test_config_presets() {
1693        let quick = LaserLogicConfig::quick();
1694        assert!(!quick.check_soundness);
1695        assert_eq!(quick.max_premise_depth, 5);
1696
1697        let deep = LaserLogicConfig::deep();
1698        assert!(deep.check_soundness);
1699        assert!(deep.verbose_output);
1700
1701        let paranoid = LaserLogicConfig::paranoid();
1702        assert_eq!(paranoid.confidence_threshold, 0.9);
1703    }
1704
1705    #[test]
1706    fn test_premise_type_inference() {
1707        assert_eq!(
1708            Premise::new("All humans are mortal").premise_type,
1709            PremiseType::Universal
1710        );
1711        assert_eq!(
1712            Premise::new("Some birds can fly").premise_type,
1713            PremiseType::Particular
1714        );
1715        assert_eq!(
1716            Premise::new("If it rains, the ground is wet").premise_type,
1717            PremiseType::Conditional
1718        );
1719        assert_eq!(
1720            Premise::new("Either we go or we stay").premise_type,
1721            PremiseType::Disjunctive
1722        );
1723        assert_eq!(
1724            Premise::new("Socrates is human").premise_type,
1725            PremiseType::Singular
1726        );
1727    }
1728
1729    #[test]
1730    fn test_fallacy_descriptions() {
1731        let fallacy = Fallacy::AffirmingConsequent;
1732        assert!(fallacy.description().contains("Inferring"));
1733        assert!(fallacy.pattern().contains("INVALID"));
1734        assert!(fallacy.is_formal());
1735        assert_eq!(fallacy.severity(), 5);
1736    }
1737
1738    #[test]
1739    fn test_valid_modus_ponens() {
1740        let laser = LaserLogic::new();
1741        let result = laser
1742            .analyze_argument(
1743                &["If it rains, then the ground is wet", "It rains"],
1744                "The ground is wet",
1745            )
1746            .unwrap();
1747
1748        // Should detect modus ponens form
1749        assert!(result.argument_form.is_some());
1750        assert!(!result.has_fallacies());
1751    }
1752
1753    #[test]
1754    fn test_affirming_consequent_detection() {
1755        let laser = LaserLogic::new();
1756        let result = laser
1757            .analyze_argument(
1758                &["If it rains, then the ground is wet", "The ground is wet"],
1759                "It rained",
1760            )
1761            .unwrap();
1762
1763        // Should detect affirming the consequent
1764        assert!(result.has_fallacies());
1765        assert!(result
1766            .fallacies
1767            .iter()
1768            .any(|f| f.fallacy == Fallacy::AffirmingConsequent));
1769    }
1770
1771    #[test]
1772    fn test_categorical_syllogism() {
1773        let laser = LaserLogic::new();
1774        let result = laser
1775            .analyze_argument(
1776                &["All humans are mortal", "All Greeks are humans"],
1777                "All Greeks are mortal",
1778            )
1779            .unwrap();
1780
1781        assert_eq!(
1782            result.argument_form,
1783            Some(ArgumentForm::CategoricalSyllogism)
1784        );
1785    }
1786
1787    #[test]
1788    fn test_circular_reasoning_detection() {
1789        let laser = LaserLogic::new();
1790        let result = laser
1791            .analyze_argument(
1792                &["The Bible is true because it is the word of God"],
1793                "The Bible is the word of God",
1794            )
1795            .unwrap();
1796
1797        assert!(result
1798            .fallacies
1799            .iter()
1800            .any(|f| f.fallacy == Fallacy::CircularReasoning));
1801    }
1802
1803    #[test]
1804    fn test_contradiction_detection() {
1805        let laser = LaserLogic::with_config(LaserLogicConfig::deep());
1806        let result = laser
1807            .analyze_argument(&["X is true", "X is false"], "Something follows")
1808            .unwrap();
1809
1810        assert!(!result.contradictions.is_empty());
1811    }
1812
1813    #[test]
1814    fn test_thinkmodule_trait() {
1815        let laser = LaserLogic::new();
1816        let config = laser.config();
1817        assert_eq!(config.name, "LaserLogic");
1818        assert_eq!(config.version, "3.0.0");
1819    }
1820
1821    #[test]
1822    fn test_execute_with_context() {
1823        let laser = LaserLogic::new();
1824        let context = ThinkToolContext {
1825            query: "All humans are mortal. Socrates is human. Therefore, Socrates is mortal."
1826                .to_string(),
1827            previous_steps: vec![],
1828        };
1829
1830        let output = laser.execute(&context).unwrap();
1831        assert_eq!(output.module, "LaserLogic");
1832        assert!(output.confidence > 0.0);
1833    }
1834
1835    #[test]
1836    fn test_verdict_generation() {
1837        let mut result = LaserLogicResult::new(Argument::new(vec!["premise"], "conclusion"));
1838        result.validity = ValidityStatus::Valid;
1839        result.soundness = SoundnessStatus::Sound;
1840        assert!(result.verdict().contains("SOUND"));
1841
1842        result.validity = ValidityStatus::Invalid;
1843        assert!(result.verdict().contains("INVALID"));
1844    }
1845
1846    #[test]
1847    fn test_argument_form_validity() {
1848        assert!(ArgumentForm::ModusPonens.is_valid_form());
1849        assert!(ArgumentForm::ModusTollens.is_valid_form());
1850        assert!(!ArgumentForm::Unknown.is_valid_form());
1851    }
1852
1853    #[test]
1854    fn test_empty_premises_error() {
1855        let laser = LaserLogic::new();
1856        let result = laser.analyze_argument(&[], "Some conclusion");
1857        assert!(result.is_err());
1858    }
1859
1860    #[test]
1861    fn test_too_many_premises_error() {
1862        let laser = LaserLogic::with_config(LaserLogicConfig {
1863            max_premise_depth: 2,
1864            ..Default::default()
1865        });
1866        let result = laser.analyze_argument(&["P1", "P2", "P3"], "Conclusion");
1867        assert!(result.is_err());
1868    }
1869}