optirs_core/research/
citations.rs

1// Citation management and bibliographic tools
2//
3// This module provides comprehensive citation management, BibTeX parsing,
4// and automated reference generation for academic publications.
5
6use crate::error::{OptimError, Result};
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11/// Citation manager for handling bibliographic references
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct CitationManager {
14    /// Citation database
15    pub citations: HashMap<String, Citation>,
16    /// Citation styles
17    pub styles: HashMap<String, CitationStyle>,
18    /// Default citation style
19    pub default_style: String,
20    /// Citation groups/categories
21    pub groups: HashMap<String, CitationGroup>,
22    /// Import/export settings
23    pub settings: CitationSettings,
24    /// Last modified timestamp
25    pub modified_at: DateTime<Utc>,
26}
27
28/// Individual citation record
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct Citation {
31    /// Citation key/identifier
32    pub key: String,
33    /// Publication type
34    pub publication_type: PublicationType,
35    /// Title
36    pub title: String,
37    /// Authors
38    pub authors: Vec<Author>,
39    /// Publication year
40    pub year: Option<u32>,
41    /// Journal/Conference/Publisher
42    pub venue: Option<String>,
43    /// Volume number
44    pub volume: Option<String>,
45    /// Issue/Number
46    pub issue: Option<String>,
47    /// Page numbers
48    pub pages: Option<String>,
49    /// DOI
50    pub doi: Option<String>,
51    /// URL
52    pub url: Option<String>,
53    /// Abstract
54    pub abstracttext: Option<String>,
55    /// Keywords
56    pub keywords: Vec<String>,
57    /// Notes
58    pub notes: Option<String>,
59    /// Custom fields
60    pub custom_fields: HashMap<String, String>,
61    /// File attachments
62    pub attachments: Vec<String>,
63    /// Citation groups
64    pub groups: Vec<String>,
65    /// Import source
66    pub import_source: Option<String>,
67    /// Creation timestamp
68    pub created_at: DateTime<Utc>,
69    /// Last modified timestamp
70    pub modified_at: DateTime<Utc>,
71}
72
73/// Publication types for citations
74#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
75pub enum PublicationType {
76    /// Journal article
77    Article,
78    /// Conference paper
79    InProceedings,
80    /// Book
81    Book,
82    /// Book chapter
83    InCollection,
84    /// PhD thesis
85    PhDThesis,
86    /// Master's thesis
87    MastersThesis,
88    /// Technical report
89    TechReport,
90    /// Manual
91    Manual,
92    /// Miscellaneous
93    Misc,
94    /// Unpublished work
95    Unpublished,
96    /// Preprint
97    Preprint,
98    /// Patent
99    Patent,
100    /// Software
101    Software,
102    /// Dataset
103    Dataset,
104}
105
106/// Author information for citations
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct Author {
109    /// First name
110    pub first_name: String,
111    /// Last name
112    pub last_name: String,
113    /// Middle name/initial
114    pub middle_name: Option<String>,
115    /// Name suffix (Jr., Sr., etc.)
116    pub suffix: Option<String>,
117    /// ORCID identifier
118    pub orcid: Option<String>,
119    /// Author affiliation
120    pub affiliation: Option<String>,
121}
122
123/// Citation style definition
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct CitationStyle {
126    /// Style name
127    pub name: String,
128    /// Style description
129    pub description: String,
130    /// In-text citation format
131    pub intext_format: InTextFormat,
132    /// Bibliography format
133    pub bibliography_format: BibliographyFormat,
134    /// Formatting rules
135    pub formatting_rules: FormattingRules,
136    /// Sorting rules
137    pub sorting_rules: SortingRules,
138}
139
140/// In-text citation formats
141#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
142pub enum InTextFormat {
143    /// Author-year format: (Smith, 2023)
144    AuthorYear,
145    /// Numbered format: \[1\]
146    Numbered,
147    /// Superscript format: ยน
148    Superscript,
149    /// Author-number format: Smith \[1\]
150    AuthorNumber,
151    /// Footnote format
152    Footnote,
153}
154
155/// Bibliography formatting
156#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct BibliographyFormat {
158    /// Entry separator
159    pub entry_separator: String,
160    /// Field separators
161    pub field_separators: HashMap<String, String>,
162    /// Name formatting
163    pub name_format: NameFormat,
164    /// Title formatting
165    pub title_format: TitleFormat,
166    /// Date formatting
167    pub date_format: DateFormat,
168    /// Punctuation rules
169    pub punctuation: PunctuationRules,
170}
171
172/// Name formatting options
173#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
174pub enum NameFormat {
175    /// Last, First Middle
176    LastFirstMiddle,
177    /// First Middle Last
178    FirstMiddleLast,
179    /// Last, F. M.
180    LastFirstInitial,
181    /// F. M. Last
182    FirstInitialLast,
183    /// Last, F.M.
184    LastFirstInitialNoSpace,
185}
186
187/// Title formatting options
188#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
189pub enum TitleFormat {
190    /// Title Case
191    TitleCase,
192    /// Sentence case
193    SentenceCase,
194    /// UPPERCASE
195    Uppercase,
196    /// lowercase
197    Lowercase,
198    /// As entered
199    AsEntered,
200}
201
202/// Date formatting options
203#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
204pub enum DateFormat {
205    /// 2023
206    Year,
207    /// December 2023
208    MonthYear,
209    /// Dec. 2023
210    MonthAbbrevYear,
211    /// December 15, 2023
212    FullDate,
213    /// 2023-12-15
214    ISODate,
215}
216
217/// Punctuation rules
218#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct PunctuationRules {
220    /// Use periods after abbreviations
221    pub periods_after_abbreviations: bool,
222    /// Use commas between fields
223    pub commas_between_fields: bool,
224    /// Use parentheses around year
225    pub parentheses_around_year: bool,
226    /// Quote titles
227    pub quote_titles: bool,
228    /// Italicize journal names
229    pub italicize_journals: bool,
230}
231
232/// Formatting rules for citations
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct FormattingRules {
235    /// Maximum authors to show
236    pub max_authors: Option<usize>,
237    /// Text to use for "et al."
238    pub et_altext: String,
239    /// Minimum authors before using et al.
240    pub et_al_threshold: usize,
241    /// Use title case for titles
242    pub title_case: bool,
243    /// Abbreviate journal names
244    pub abbreviate_journals: bool,
245    /// Include DOI
246    pub include_doi: bool,
247    /// Include URL
248    pub include_url: bool,
249}
250
251/// Sorting rules for bibliography
252#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct SortingRules {
254    /// Primary sort field
255    pub primary_sort: SortField,
256    /// Secondary sort field
257    pub secondary_sort: Option<SortField>,
258    /// Sort direction
259    pub sort_direction: SortDirection,
260    /// Group by type
261    pub group_by_type: bool,
262}
263
264/// Sort fields
265#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
266pub enum SortField {
267    /// Author last name
268    Author,
269    /// Publication year
270    Year,
271    /// Title
272    Title,
273    /// Journal/venue
274    Venue,
275    /// Citation key
276    Key,
277    /// Date added
278    DateAdded,
279}
280
281/// Sort direction
282#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
283pub enum SortDirection {
284    /// Ascending order
285    Ascending,
286    /// Descending order
287    Descending,
288}
289
290/// Citation group for organizing references
291#[derive(Debug, Clone, Serialize, Deserialize)]
292pub struct CitationGroup {
293    /// Group name
294    pub name: String,
295    /// Group description
296    pub description: String,
297    /// Group color (for UI)
298    pub color: Option<String>,
299    /// Citation keys in this group
300    pub citation_keys: Vec<String>,
301    /// Creation timestamp
302    pub created_at: DateTime<Utc>,
303}
304
305/// Citation manager settings
306#[derive(Debug, Clone, Serialize, Deserialize)]
307pub struct CitationSettings {
308    /// Auto-generate keys
309    pub auto_generate_keys: bool,
310    /// Key generation pattern
311    pub key_pattern: String,
312    /// Auto-import from DOI
313    pub auto_import_doi: bool,
314    /// Auto-import from URL
315    pub auto_import_url: bool,
316    /// Duplicate detection
317    pub duplicate_detection: bool,
318    /// Backup settings
319    pub backup_enabled: bool,
320    /// Export formats
321    pub export_formats: Vec<ExportFormat>,
322}
323
324/// Export formats
325#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
326pub enum ExportFormat {
327    /// BibTeX format
328    BibTeX,
329    /// RIS format
330    RIS,
331    /// EndNote XML
332    EndNote,
333    /// JSON format
334    JSON,
335    /// CSV format
336    CSV,
337    /// Word bibliography
338    Word,
339}
340
341/// BibTeX parser and exporter
342#[derive(Debug)]
343pub struct BibTeXProcessor {
344    /// Parser settings
345    settings: BibTeXSettings,
346}
347
348/// BibTeX processing settings
349#[derive(Debug, Clone, Serialize, Deserialize)]
350pub struct BibTeXSettings {
351    /// Preserve case in titles
352    pub preserve_case: bool,
353    /// Convert to UTF-8
354    pub utf8_conversion: bool,
355    /// Clean up formatting
356    pub cleanup_formatting: bool,
357    /// Validate entries
358    pub validate_entries: bool,
359}
360
361/// Citation search and discovery
362#[derive(Debug)]
363pub struct CitationDiscovery {
364    /// Search engines configuration
365    search_engines: Vec<SearchEngine>,
366    /// API keys for services
367    api_keys: HashMap<String, String>,
368}
369
370/// Search engine configuration
371#[derive(Debug, Clone, Serialize, Deserialize)]
372pub struct SearchEngine {
373    /// Engine name
374    pub name: String,
375    /// API endpoint
376    pub endpoint: String,
377    /// Rate limit (requests per second)
378    pub rate_limit: f64,
379    /// Supported query types
380    pub query_types: Vec<QueryType>,
381}
382
383/// Query types for citation search
384#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
385pub enum QueryType {
386    /// DOI lookup
387    DOI,
388    /// Title search
389    Title,
390    /// Author search
391    Author,
392    /// ArXiv ID
393    ArXiv,
394    /// PubMed ID
395    PubMed,
396    /// ISBN
397    ISBN,
398    /// Free text search
399    FreeText,
400}
401
402/// Citation network analysis
403#[derive(Debug, Clone, Serialize, Deserialize)]
404pub struct CitationNetwork {
405    /// Citations in the network
406    pub citations: Vec<String>,
407    /// Citation relationships
408    pub relationships: Vec<CitationRelationship>,
409    /// Network metrics
410    pub metrics: NetworkMetrics,
411}
412
413/// Citation relationship
414#[derive(Debug, Clone, Serialize, Deserialize)]
415pub struct CitationRelationship {
416    /// Citing paper
417    pub citing: String,
418    /// Cited paper
419    pub cited: String,
420    /// Relationship type
421    pub relationship_type: RelationshipType,
422    /// Relationship strength
423    pub strength: f64,
424}
425
426/// Relationship types between citations
427#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
428pub enum RelationshipType {
429    /// Direct citation
430    DirectCitation,
431    /// Co-citation (cited together)
432    CoCitation,
433    /// Bibliographic coupling (share references)
434    BibliographicCoupling,
435    /// Same author
436    SameAuthor,
437    /// Same venue
438    SameVenue,
439    /// Similar topic
440    SimilarTopic,
441}
442
443/// Network analysis metrics
444#[derive(Debug, Clone, Serialize, Deserialize)]
445pub struct NetworkMetrics {
446    /// Total nodes (papers)
447    pub total_nodes: usize,
448    /// Total edges (relationships)
449    pub total_edges: usize,
450    /// Network density
451    pub density: f64,
452    /// Average clustering coefficient
453    pub clustering_coefficient: f64,
454    /// Most cited papers
455    pub most_cited: Vec<(String, usize)>,
456    /// Most influential authors
457    pub most_influential_authors: Vec<(String, f64)>,
458}
459
460impl Default for CitationManager {
461    fn default() -> Self {
462        Self::new()
463    }
464}
465
466impl CitationManager {
467    /// Create a new citation manager
468    pub fn new() -> Self {
469        let mut styles = HashMap::new();
470        styles.insert("APA".to_string(), Self::create_apa_style());
471        styles.insert("IEEE".to_string(), Self::create_ieee_style());
472        styles.insert("ACM".to_string(), Self::create_acm_style());
473
474        Self {
475            citations: HashMap::new(),
476            styles,
477            default_style: "APA".to_string(),
478            groups: HashMap::new(),
479            settings: CitationSettings::default(),
480            modified_at: Utc::now(),
481        }
482    }
483
484    /// Add a citation to the database
485    pub fn add_citation(&mut self, citation: Citation) -> Result<()> {
486        if self.citations.contains_key(&citation.key) {
487            return Err(OptimError::InvalidConfig(format!(
488                "Citation with key '{}' already exists",
489                citation.key
490            )));
491        }
492
493        self.citations.insert(citation.key.clone(), citation);
494        self.modified_at = Utc::now();
495        Ok(())
496    }
497
498    /// Get a citation by key
499    pub fn get_citation(&self, key: &str) -> Option<&Citation> {
500        self.citations.get(key)
501    }
502
503    /// Update an existing citation
504    pub fn update_citation(&mut self, key: &str, citation: Citation) -> Result<()> {
505        if !self.citations.contains_key(key) {
506            return Err(OptimError::InvalidConfig(format!(
507                "Citation with key '{}' not found",
508                key
509            )));
510        }
511
512        self.citations.insert(key.to_string(), citation);
513        self.modified_at = Utc::now();
514        Ok(())
515    }
516
517    /// Remove a citation
518    pub fn remove_citation(&mut self, key: &str) -> Result<()> {
519        if self.citations.remove(key).is_none() {
520            return Err(OptimError::InvalidConfig(format!(
521                "Citation with key '{}' not found",
522                key
523            )));
524        }
525
526        self.modified_at = Utc::now();
527        Ok(())
528    }
529
530    /// Search citations by various criteria
531    pub fn search_citations(&self, query: &str) -> Vec<&Citation> {
532        let query_lower = query.to_lowercase();
533
534        self.citations
535            .values()
536            .filter(|citation| {
537                citation.title.to_lowercase().contains(&query_lower)
538                    || citation.authors.iter().any(|author| {
539                        author.last_name.to_lowercase().contains(&query_lower)
540                            || author.first_name.to_lowercase().contains(&query_lower)
541                    })
542                    || citation
543                        .keywords
544                        .iter()
545                        .any(|keyword| keyword.to_lowercase().contains(&query_lower))
546                    || citation
547                        .venue
548                        .as_ref()
549                        .is_some_and(|venue| venue.to_lowercase().contains(&query_lower))
550            })
551            .collect()
552    }
553
554    /// Generate formatted citation in specified style
555    pub fn format_citation(&self, key: &str, style: Option<&str>) -> Result<String> {
556        let citation = self
557            .get_citation(key)
558            .ok_or_else(|| OptimError::InvalidConfig(format!("Citation '{}' not found", key)))?;
559
560        let style_name = style.unwrap_or(&self.default_style);
561        let citation_style = self.styles.get(style_name).ok_or_else(|| {
562            OptimError::InvalidConfig(format!("Style '{}' not found", style_name))
563        })?;
564
565        self.format_citation_with_style(citation, citation_style)
566    }
567
568    /// Generate bibliography for multiple citations
569    pub fn generate_bibliography(
570        &self,
571        citation_keys: &[String],
572        style: Option<&str>,
573    ) -> Result<String> {
574        let style_name = style.unwrap_or(&self.default_style);
575        let citation_style = self.styles.get(style_name).ok_or_else(|| {
576            OptimError::InvalidConfig(format!("Style '{}' not found", style_name))
577        })?;
578
579        let mut citations: Vec<&Citation> = citation_keys
580            .iter()
581            .filter_map(|key| self.citations.get(key))
582            .collect();
583
584        // Sort citations according to style rules
585        self.sort_citations(&mut citations, &citation_style.sorting_rules);
586
587        let mut bibliography = String::new();
588        for citation in citations {
589            let formatted = self.format_citation_with_style(citation, citation_style)?;
590            bibliography.push_str(&formatted);
591            bibliography.push('\n');
592        }
593
594        Ok(bibliography)
595    }
596
597    /// Export citations to BibTeX format
598    pub fn export_bibtex(&self, citation_keys: Option<&[String]>) -> String {
599        let citations: Vec<&Citation> = if let Some(_keys) = citation_keys {
600            _keys
601                .iter()
602                .filter_map(|key| self.citations.get(key))
603                .collect()
604        } else {
605            self.citations.values().collect()
606        };
607
608        let mut bibtex = String::new();
609        for citation in citations {
610            bibtex.push_str(&self.citation_to_bibtex(citation));
611            bibtex.push('\n');
612        }
613
614        bibtex
615    }
616
617    /// Import citations from BibTeX
618    pub fn import_bibtex(&mut self, bibtex_content: &str) -> Result<usize> {
619        let processor = BibTeXProcessor::new(BibTeXSettings::default());
620        let citations = processor.parse_bibtex(bibtex_content)?;
621
622        let mut imported_count = 0;
623        for citation in citations {
624            if !self.citations.contains_key(&citation.key) {
625                self.citations.insert(citation.key.clone(), citation);
626                imported_count += 1;
627            }
628        }
629
630        self.modified_at = Utc::now();
631        Ok(imported_count)
632    }
633
634    /// Create a citation group
635    pub fn create_group(&mut self, name: &str, description: &str) -> String {
636        let group_id = uuid::Uuid::new_v4().to_string();
637        let group = CitationGroup {
638            name: name.to_string(),
639            description: description.to_string(),
640            color: None,
641            citation_keys: Vec::new(),
642            created_at: Utc::now(),
643        };
644
645        self.groups.insert(group_id.clone(), group);
646        group_id
647    }
648
649    /// Add citation to group
650    pub fn add_to_group(&mut self, group_id: &str, citation_key: &str) -> Result<()> {
651        let group = self
652            .groups
653            .get_mut(group_id)
654            .ok_or_else(|| OptimError::InvalidConfig(format!("Group '{}' not found", group_id)))?;
655
656        if !group.citation_keys.contains(&citation_key.to_string()) {
657            group.citation_keys.push(citation_key.to_string());
658        }
659
660        Ok(())
661    }
662
663    fn format_citation_with_style(
664        &self,
665        citation: &Citation,
666        style: &CitationStyle,
667    ) -> Result<String> {
668        match style.intext_format {
669            InTextFormat::AuthorYear => self.format_author_year(citation, style),
670            InTextFormat::Numbered => self.format_numbered(citation, style),
671            InTextFormat::Superscript => self.format_superscript(citation, style),
672            InTextFormat::AuthorNumber => self.format_author_number(citation, style),
673            InTextFormat::Footnote => self.format_footnote(citation, style),
674        }
675    }
676
677    fn format_author_year(&self, citation: &Citation, style: &CitationStyle) -> Result<String> {
678        let authors = self.format_authors(&citation.authors, &style.formatting_rules);
679        let year = citation
680            .year
681            .map(|y| y.to_string())
682            .unwrap_or_else(|| "n.d.".to_string());
683
684        Ok(format!("({}, {})", authors, year))
685    }
686
687    fn format_numbered(&self, citation: &Citation, style: &CitationStyle) -> Result<String> {
688        // In a real implementation, you'd need to assign numbers based on order
689        Ok(format!("[{}]", 1)) // Placeholder
690    }
691
692    fn format_superscript(&self, citation: &Citation, style: &CitationStyle) -> Result<String> {
693        Ok("ยน".to_string()) // Placeholder
694    }
695
696    fn format_author_number(&self, citation: &Citation, style: &CitationStyle) -> Result<String> {
697        let authors = self.format_authors(&citation.authors, &style.formatting_rules);
698        Ok(format!("{} [1]", authors)) // Placeholder
699    }
700
701    fn format_footnote(&self, citation: &Citation, style: &CitationStyle) -> Result<String> {
702        self.format_full_citation(citation, style)
703    }
704
705    fn format_full_citation(&self, citation: &Citation, style: &CitationStyle) -> Result<String> {
706        let mut formatted = String::new();
707
708        // Authors
709        let authors = self.format_authors(&citation.authors, &style.formatting_rules);
710        formatted.push_str(&authors);
711
712        // Title
713        let title = self.format_title(&citation.title, &style.bibliography_format.title_format);
714        formatted.push_str(&format!(". {}.", title));
715
716        // Venue
717        if let Some(venue) = &citation.venue {
718            let venue_formatted = if style.bibliography_format.punctuation.italicize_journals {
719                format!(" *{}*", venue)
720            } else {
721                format!(" {venue}")
722            };
723            formatted.push_str(&venue_formatted);
724        }
725
726        // Year
727        if let Some(year) = citation.year {
728            if style
729                .bibliography_format
730                .punctuation
731                .parentheses_around_year
732            {
733                formatted.push_str(&format!(" ({})", year));
734            } else {
735                formatted.push_str(&format!(" {year}"));
736            }
737        }
738
739        // DOI
740        if style.formatting_rules.include_doi {
741            if let Some(doi) = &citation.doi {
742                formatted.push_str(&format!(". DOI: {doi}"));
743            }
744        }
745
746        Ok(formatted)
747    }
748
749    fn format_authors(&self, authors: &[Author], rules: &FormattingRules) -> String {
750        if authors.is_empty() {
751            return "Anonymous".to_string();
752        }
753
754        let max_authors = rules.max_authors.unwrap_or(authors.len());
755        let display_authors = if authors.len() > max_authors && max_authors > 0 {
756            &authors[..max_authors]
757        } else {
758            authors
759        };
760
761        let mut formatted_authors = Vec::new();
762        for author in display_authors {
763            let formatted = format!("{}, {}", author.last_name, author.first_name);
764            formatted_authors.push(formatted);
765        }
766
767        let mut result = formatted_authors.join(", ");
768
769        if authors.len() > max_authors {
770            result.push_str(&format!(", {}", rules.et_altext));
771        }
772
773        result
774    }
775
776    fn format_title(&self, title: &str, format: &TitleFormat) -> String {
777        match format {
778            TitleFormat::TitleCase => self.to_title_case(title),
779            TitleFormat::SentenceCase => self.to_sentence_case(title),
780            TitleFormat::Uppercase => title.to_uppercase(),
781            TitleFormat::Lowercase => title.to_lowercase(),
782            TitleFormat::AsEntered => title.to_string(),
783        }
784    }
785
786    fn to_title_case(&self, s: &str) -> String {
787        s.split_whitespace()
788            .map(|word| {
789                let mut chars = word.chars();
790                match chars.next() {
791                    None => String::new(),
792                    Some(first) => {
793                        first.to_uppercase().collect::<String>() + &chars.as_str().to_lowercase()
794                    }
795                }
796            })
797            .collect::<Vec<_>>()
798            .join(" ")
799    }
800
801    fn to_sentence_case(&self, s: &str) -> String {
802        if s.is_empty() {
803            return String::new();
804        }
805
806        let mut chars = s.chars();
807        let first = chars.next().unwrap().to_uppercase().collect::<String>();
808        first + &chars.as_str().to_lowercase()
809    }
810
811    fn sort_citations(&self, citations: &mut Vec<&Citation>, rules: &SortingRules) {
812        citations.sort_by(|a, b| {
813            let primary_cmp = self.compare_by_field(a, b, &rules.primary_sort);
814            if primary_cmp == std::cmp::Ordering::Equal {
815                if let Some(secondary) = &rules.secondary_sort {
816                    self.compare_by_field(a, b, secondary)
817                } else {
818                    std::cmp::Ordering::Equal
819                }
820            } else {
821                primary_cmp
822            }
823        });
824
825        if rules.sort_direction == SortDirection::Descending {
826            citations.reverse();
827        }
828    }
829
830    fn compare_by_field(
831        &self,
832        a: &Citation,
833        b: &Citation,
834        field: &SortField,
835    ) -> std::cmp::Ordering {
836        match field {
837            SortField::Author => {
838                let a_author = a
839                    .authors
840                    .first()
841                    .map(|au| au.last_name.as_str())
842                    .unwrap_or("");
843                let b_author = b
844                    .authors
845                    .first()
846                    .map(|au| au.last_name.as_str())
847                    .unwrap_or("");
848                a_author.cmp(b_author)
849            }
850            SortField::Year => a.year.cmp(&b.year),
851            SortField::Title => a.title.cmp(&b.title),
852            SortField::Venue => a.venue.cmp(&b.venue),
853            SortField::Key => a.key.cmp(&b.key),
854            SortField::DateAdded => a.created_at.cmp(&b.created_at),
855        }
856    }
857
858    fn citation_to_bibtex(&self, citation: &Citation) -> String {
859        let mut bibtex = format!(
860            "@{}{{{},\n",
861            self.publication_type_to_bibtex(&citation.publication_type),
862            citation.key
863        );
864
865        bibtex.push_str(&format!("  title = {{{}}},\n", citation.title));
866
867        if !citation.authors.is_empty() {
868            let authors = citation
869                .authors
870                .iter()
871                .map(|a| format!("{} {}", a.first_name, a.last_name))
872                .collect::<Vec<_>>()
873                .join(" and ");
874            bibtex.push_str(&format!("  author = {{{}}},\n", authors));
875        }
876
877        if let Some(year) = citation.year {
878            bibtex.push_str(&format!("  year = {{{}}},\n", year));
879        }
880
881        if let Some(venue) = &citation.venue {
882            let field_name = match citation.publication_type {
883                PublicationType::Article => "journal",
884                PublicationType::InProceedings => "booktitle",
885                PublicationType::Book => "publisher",
886                PublicationType::InCollection => "booktitle",
887                PublicationType::PhDThesis => "school",
888                PublicationType::MastersThesis => "school",
889                PublicationType::TechReport => "institution",
890                PublicationType::Manual => "organization",
891                PublicationType::Misc => "howpublished",
892                PublicationType::Unpublished => "note",
893                PublicationType::Preprint => "archivePrefix",
894                PublicationType::Patent => "assignee",
895                PublicationType::Software => "url",
896                PublicationType::Dataset => "url",
897            };
898            bibtex.push_str(&format!("  {} = {{{}}},\n", field_name, venue));
899        }
900
901        if let Some(volume) = &citation.volume {
902            bibtex.push_str(&format!("  volume = {{{}}},\n", volume));
903        }
904
905        if let Some(pages) = &citation.pages {
906            bibtex.push_str(&format!("  pages = {{{}}},\n", pages));
907        }
908
909        if let Some(doi) = &citation.doi {
910            bibtex.push_str(&format!("  doi = {{{}}},\n", doi));
911        }
912
913        bibtex.push_str("}\n");
914        bibtex
915    }
916
917    fn publication_type_to_bibtex(&self, pub_type: &PublicationType) -> &'static str {
918        match pub_type {
919            PublicationType::Article => "article",
920            PublicationType::InProceedings => "inproceedings",
921            PublicationType::Book => "book",
922            PublicationType::InCollection => "incollection",
923            PublicationType::PhDThesis => "phdthesis",
924            PublicationType::MastersThesis => "mastersthesis",
925            PublicationType::TechReport => "techreport",
926            PublicationType::Manual => "manual",
927            PublicationType::Misc => "misc",
928            PublicationType::Unpublished => "unpublished",
929            PublicationType::Preprint => "misc",
930            PublicationType::Patent => "misc",
931            PublicationType::Software => "misc",
932            PublicationType::Dataset => "misc",
933        }
934    }
935
936    fn create_apa_style() -> CitationStyle {
937        CitationStyle {
938            name: "APA".to_string(),
939            description: "American Psychological Association style".to_string(),
940            intext_format: InTextFormat::AuthorYear,
941            bibliography_format: BibliographyFormat {
942                entry_separator: "\n".to_string(),
943                field_separators: {
944                    let mut separators = HashMap::new();
945                    separators.insert("author_title".to_string(), ". ".to_string());
946                    separators.insert("title_venue".to_string(), ". ".to_string());
947                    separators
948                },
949                name_format: NameFormat::LastFirstInitial,
950                title_format: TitleFormat::SentenceCase,
951                date_format: DateFormat::Year,
952                punctuation: PunctuationRules {
953                    periods_after_abbreviations: true,
954                    commas_between_fields: true,
955                    parentheses_around_year: true,
956                    quote_titles: false,
957                    italicize_journals: true,
958                },
959            },
960            formatting_rules: FormattingRules {
961                max_authors: Some(7),
962                et_altext: "et al.".to_string(),
963                et_al_threshold: 8,
964                title_case: false,
965                abbreviate_journals: false,
966                include_doi: true,
967                include_url: false,
968            },
969            sorting_rules: SortingRules {
970                primary_sort: SortField::Author,
971                secondary_sort: Some(SortField::Year),
972                sort_direction: SortDirection::Ascending,
973                group_by_type: false,
974            },
975        }
976    }
977
978    fn create_ieee_style() -> CitationStyle {
979        CitationStyle {
980            name: "IEEE".to_string(),
981            description: "Institute of Electrical and Electronics Engineers style".to_string(),
982            intext_format: InTextFormat::Numbered,
983            bibliography_format: BibliographyFormat {
984                entry_separator: "\n".to_string(),
985                field_separators: HashMap::new(),
986                name_format: NameFormat::FirstInitialLast,
987                title_format: TitleFormat::AsEntered,
988                date_format: DateFormat::Year,
989                punctuation: PunctuationRules {
990                    periods_after_abbreviations: true,
991                    commas_between_fields: true,
992                    parentheses_around_year: false,
993                    quote_titles: true,
994                    italicize_journals: true,
995                },
996            },
997            formatting_rules: FormattingRules {
998                max_authors: None,
999                et_altext: "et al.".to_string(),
1000                et_al_threshold: 7,
1001                title_case: false,
1002                abbreviate_journals: true,
1003                include_doi: true,
1004                include_url: false,
1005            },
1006            sorting_rules: SortingRules {
1007                primary_sort: SortField::Year,
1008                secondary_sort: Some(SortField::Author),
1009                sort_direction: SortDirection::Ascending,
1010                group_by_type: false,
1011            },
1012        }
1013    }
1014
1015    fn create_acm_style() -> CitationStyle {
1016        CitationStyle {
1017            name: "ACM".to_string(),
1018            description: "Association for Computing Machinery style".to_string(),
1019            intext_format: InTextFormat::Numbered,
1020            bibliography_format: BibliographyFormat {
1021                entry_separator: "\n".to_string(),
1022                field_separators: HashMap::new(),
1023                name_format: NameFormat::FirstMiddleLast,
1024                title_format: TitleFormat::TitleCase,
1025                date_format: DateFormat::Year,
1026                punctuation: PunctuationRules {
1027                    periods_after_abbreviations: true,
1028                    commas_between_fields: true,
1029                    parentheses_around_year: false,
1030                    quote_titles: false,
1031                    italicize_journals: true,
1032                },
1033            },
1034            formatting_rules: FormattingRules {
1035                max_authors: None,
1036                et_altext: "et al.".to_string(),
1037                et_al_threshold: 3,
1038                title_case: true,
1039                abbreviate_journals: false,
1040                include_doi: true,
1041                include_url: true,
1042            },
1043            sorting_rules: SortingRules {
1044                primary_sort: SortField::Author,
1045                secondary_sort: Some(SortField::Year),
1046                sort_direction: SortDirection::Ascending,
1047                group_by_type: false,
1048            },
1049        }
1050    }
1051}
1052
1053impl BibTeXProcessor {
1054    /// Create a new BibTeX processor
1055    pub fn new(settings: BibTeXSettings) -> Self {
1056        Self { settings }
1057    }
1058
1059    /// Parse BibTeX content into citations
1060    pub fn parse_bibtex(&self, content: &str) -> Result<Vec<Citation>> {
1061        // Simplified BibTeX parser
1062        // In a real implementation, you'd want a proper BibTeX parser
1063        let mut citations = Vec::new();
1064        let lines: Vec<&str> = content.lines().collect();
1065        let mut current_entry: Option<(String, PublicationType, HashMap<String, String>)> = None;
1066
1067        for line in lines {
1068            let line = line.trim();
1069
1070            if line.starts_with('@') {
1071                // Save previous entry
1072                if let Some((key, pub_type, fields)) = current_entry.take() {
1073                    if let Ok(citation) = self.fields_to_citation(key, pub_type, fields) {
1074                        citations.push(citation);
1075                    }
1076                }
1077
1078                // Parse new entry
1079                if let Some(pos) = line.find('{') {
1080                    let entry_type = line[1..pos].to_lowercase();
1081                    let pub_type = self.bibtex_type_to_publication_type(&entry_type);
1082
1083                    let key_part = &line[pos + 1..];
1084                    if let Some(comma_pos) = key_part.find(',') {
1085                        let key = key_part[..comma_pos].trim().to_string();
1086                        current_entry = Some((key, pub_type, HashMap::new()));
1087                    }
1088                }
1089            } else if line.contains('=') && current_entry.is_some() {
1090                // Parse field
1091                if let Some(eq_pos) = line.find('=') {
1092                    let field_name = line[..eq_pos].trim().to_lowercase();
1093                    let field_value = line[eq_pos + 1..]
1094                        .trim()
1095                        .trim_start_matches('{')
1096                        .trim_end_matches("},")
1097                        .trim_start_matches('"')
1098                        .trim_end_matches("\",")
1099                        .to_string();
1100
1101                    if let Some((_, _, ref mut fields)) = current_entry {
1102                        fields.insert(field_name, field_value);
1103                    }
1104                }
1105            }
1106        }
1107
1108        // Save last entry
1109        if let Some((key, pub_type, fields)) = current_entry {
1110            if let Ok(citation) = self.fields_to_citation(key, pub_type, fields) {
1111                citations.push(citation);
1112            }
1113        }
1114
1115        Ok(citations)
1116    }
1117
1118    fn bibtex_type_to_publication_type(&self, bibtex_type: &str) -> PublicationType {
1119        match bibtex_type {
1120            "article" => PublicationType::Article,
1121            "inproceedings" | "conference" => PublicationType::InProceedings,
1122            "book" => PublicationType::Book,
1123            "incollection" | "inbook" => PublicationType::InCollection,
1124            "phdthesis" => PublicationType::PhDThesis,
1125            "mastersthesis" => PublicationType::MastersThesis,
1126            "techreport" => PublicationType::TechReport,
1127            "manual" => PublicationType::Manual,
1128            "unpublished" => PublicationType::Unpublished,
1129            _ => PublicationType::Misc,
1130        }
1131    }
1132
1133    fn fields_to_citation(
1134        &self,
1135        key: String,
1136        pub_type: PublicationType,
1137        fields: HashMap<String, String>,
1138    ) -> Result<Citation> {
1139        let title = fields.get("title").cloned().unwrap_or_default();
1140
1141        // Parse authors
1142        let authors = if let Some(author_str) = fields.get("author") {
1143            self.parse_authors(author_str)
1144        } else {
1145            Vec::new()
1146        };
1147
1148        // Parse year
1149        let year = fields.get("year").and_then(|y| y.parse().ok());
1150
1151        // Determine venue field based on publication type
1152        let venue = match pub_type {
1153            PublicationType::Article => fields.get("journal").cloned(),
1154            PublicationType::InProceedings => fields.get("booktitle").cloned(),
1155            PublicationType::Book => fields.get("publisher").cloned(),
1156            PublicationType::InCollection => fields.get("booktitle").cloned(),
1157            PublicationType::PhDThesis => fields.get("school").cloned(),
1158            PublicationType::MastersThesis => fields.get("school").cloned(),
1159            PublicationType::TechReport => fields.get("institution").cloned(),
1160            PublicationType::Manual => fields.get("organization").cloned(),
1161            PublicationType::Misc => fields.get("howpublished").cloned(),
1162            PublicationType::Unpublished => fields.get("note").cloned(),
1163            PublicationType::Preprint => fields.get("archivePrefix").cloned(),
1164            PublicationType::Patent => fields.get("assignee").cloned(),
1165            PublicationType::Software => fields.get("url").cloned(),
1166            PublicationType::Dataset => fields.get("url").cloned(),
1167        };
1168
1169        let now = Utc::now();
1170
1171        Ok(Citation {
1172            key,
1173            publication_type: pub_type,
1174            title,
1175            authors,
1176            year,
1177            venue,
1178            volume: fields.get("volume").cloned(),
1179            issue: fields.get("number").cloned(),
1180            pages: fields.get("pages").cloned(),
1181            doi: fields.get("doi").cloned(),
1182            url: fields.get("url").cloned(),
1183            abstracttext: fields.get("abstract").cloned(),
1184            keywords: Vec::new(),
1185            notes: fields.get("note").cloned(),
1186            custom_fields: HashMap::new(),
1187            attachments: Vec::new(),
1188            groups: Vec::new(),
1189            import_source: Some("BibTeX".to_string()),
1190            created_at: now,
1191            modified_at: now,
1192        })
1193    }
1194
1195    fn parse_authors(&self, author_str: &str) -> Vec<Author> {
1196        author_str
1197            .split(" and ")
1198            .map(|author_part| {
1199                let author_part = author_part.trim();
1200                if let Some(comma_pos) = author_part.find(',') {
1201                    // "Last, First" format
1202                    let last_name = author_part[..comma_pos].trim().to_string();
1203                    let first_name = author_part[comma_pos + 1..].trim().to_string();
1204                    Author {
1205                        first_name,
1206                        last_name,
1207                        middle_name: None,
1208                        suffix: None,
1209                        orcid: None,
1210                        affiliation: None,
1211                    }
1212                } else {
1213                    // "First Last" format
1214                    let parts: Vec<&str> = author_part.split_whitespace().collect();
1215                    if parts.len() >= 2 {
1216                        let first_name = parts[0].to_string();
1217                        let last_name = parts[parts.len() - 1].to_string();
1218                        let middle_name = if parts.len() > 2 {
1219                            Some(parts[1..parts.len() - 1].join(" "))
1220                        } else {
1221                            None
1222                        };
1223                        Author {
1224                            first_name,
1225                            last_name,
1226                            middle_name,
1227                            suffix: None,
1228                            orcid: None,
1229                            affiliation: None,
1230                        }
1231                    } else {
1232                        // Single name
1233                        Author {
1234                            first_name: String::new(),
1235                            last_name: author_part.to_string(),
1236                            middle_name: None,
1237                            suffix: None,
1238                            orcid: None,
1239                            affiliation: None,
1240                        }
1241                    }
1242                }
1243            })
1244            .collect()
1245    }
1246}
1247
1248impl Default for CitationSettings {
1249    fn default() -> Self {
1250        Self {
1251            auto_generate_keys: true,
1252            key_pattern: "{author}{year}".to_string(),
1253            auto_import_doi: true,
1254            auto_import_url: false,
1255            duplicate_detection: true,
1256            backup_enabled: true,
1257            export_formats: vec![ExportFormat::BibTeX, ExportFormat::RIS],
1258        }
1259    }
1260}
1261
1262impl Default for BibTeXSettings {
1263    fn default() -> Self {
1264        Self {
1265            preserve_case: true,
1266            utf8_conversion: true,
1267            cleanup_formatting: true,
1268            validate_entries: true,
1269        }
1270    }
1271}
1272
1273#[cfg(test)]
1274mod tests {
1275    use super::*;
1276
1277    #[test]
1278    fn test_citation_manager_creation() {
1279        let manager = CitationManager::new();
1280
1281        assert!(manager.styles.contains_key("APA"));
1282        assert!(manager.styles.contains_key("IEEE"));
1283        assert!(manager.styles.contains_key("ACM"));
1284        assert_eq!(manager.default_style, "APA");
1285    }
1286
1287    #[test]
1288    fn test_add_citation() {
1289        let mut manager = CitationManager::new();
1290
1291        let citation = Citation {
1292            key: "test2023".to_string(),
1293            publication_type: PublicationType::Article,
1294            title: "Test Article".to_string(),
1295            authors: vec![Author {
1296                first_name: "John".to_string(),
1297                last_name: "Doe".to_string(),
1298                middle_name: None,
1299                suffix: None,
1300                orcid: None,
1301                affiliation: None,
1302            }],
1303            year: Some(2023),
1304            venue: Some("Test Journal".to_string()),
1305            volume: None,
1306            issue: None,
1307            pages: None,
1308            doi: None,
1309            url: None,
1310            abstracttext: None,
1311            keywords: Vec::new(),
1312            notes: None,
1313            custom_fields: HashMap::new(),
1314            attachments: Vec::new(),
1315            groups: Vec::new(),
1316            import_source: None,
1317            created_at: Utc::now(),
1318            modified_at: Utc::now(),
1319        };
1320
1321        assert!(manager.add_citation(citation).is_ok());
1322        assert!(manager.citations.contains_key("test2023"));
1323    }
1324
1325    #[test]
1326    fn test_search_citations() {
1327        let mut manager = CitationManager::new();
1328
1329        let citation = Citation {
1330            key: "test2023".to_string(),
1331            publication_type: PublicationType::Article,
1332            title: "Machine Learning Optimization".to_string(),
1333            authors: vec![Author {
1334                first_name: "Jane".to_string(),
1335                last_name: "Smith".to_string(),
1336                middle_name: None,
1337                suffix: None,
1338                orcid: None,
1339                affiliation: None,
1340            }],
1341            year: Some(2023),
1342            venue: None,
1343            volume: None,
1344            issue: None,
1345            pages: None,
1346            doi: None,
1347            url: None,
1348            abstracttext: None,
1349            keywords: vec!["optimization".to_string(), "machine learning".to_string()],
1350            notes: None,
1351            custom_fields: HashMap::new(),
1352            attachments: Vec::new(),
1353            groups: Vec::new(),
1354            import_source: None,
1355            created_at: Utc::now(),
1356            modified_at: Utc::now(),
1357        };
1358
1359        manager.add_citation(citation).unwrap();
1360
1361        let results = manager.search_citations("optimization");
1362        assert_eq!(results.len(), 1);
1363
1364        let results = manager.search_citations("Smith");
1365        assert_eq!(results.len(), 1);
1366    }
1367}