ass_editor/core/
fluent.rs

1//! Fluent API for document editing
2//!
3//! Provides an ergonomic builder pattern for document edits:
4//! ```
5//! # use ass_editor::{EditorDocument, Position, Range};
6//! # let mut doc = EditorDocument::new();
7//! # let pos = Position::new(0);
8//! # let range = Range::new(Position::new(0), Position::new(5));
9//! doc.at(pos).insert_text("Hello").unwrap();
10//! // doc.at_line(5).replace_text("New content"); // Not yet implemented
11//! doc.select(range).wrap_with_tag("\\b1", "\\b0").unwrap();
12//! ```
13
14use super::{EditorDocument, Position, Range, Result, StyleBuilder};
15use crate::commands::{
16    AdjustKaraokeCommand, ApplyKaraokeCommand, ApplyStyleCommand, CloneStyleCommand,
17    CreateStyleCommand, DeleteStyleCommand, EditStyleCommand, EditorCommand, EffectOperation,
18    EventEffectCommand, GenerateKaraokeCommand, InsertTagCommand, KaraokeType, MergeEventsCommand,
19    ParseTagCommand, ParsedTag, RemoveTagCommand, ReplaceTagCommand, SplitEventCommand,
20    SplitKaraokeCommand, TimingAdjustCommand, ToggleEventTypeCommand, WrapTagCommand,
21};
22use crate::core::errors::EditorError;
23use ass_core::parser::ast::{Event, EventType, Section};
24use core::cmp::Ordering;
25
26#[cfg(not(feature = "std"))]
27use alloc::{
28    string::{String, ToString},
29    vec,
30    vec::Vec,
31};
32
33#[cfg(feature = "std")]
34use std::vec;
35
36// ============================================================================
37// Event Query and Filtering Types
38// ============================================================================
39
40/// Event information with index for filtering/sorting results
41#[derive(Debug, Clone, PartialEq)]
42pub struct EventInfo {
43    /// Zero-based index in the document
44    pub index: usize,
45    /// Owned copy of the event data
46    pub event: OwnedEvent,
47    /// Line number in the document (1-based)
48    pub line_number: usize,
49    /// Character position range in document
50    pub range: Range,
51}
52
53/// Owned version of Event for use in EventInfo
54#[derive(Debug, Clone, PartialEq)]
55pub struct OwnedEvent {
56    /// Event type (Dialogue, Comment, etc.)
57    pub event_type: EventType,
58    /// Layer for drawing order (higher layers drawn on top)
59    pub layer: String,
60    /// Start time in ASS time format (H:MM:SS.CS)
61    pub start: String,
62    /// End time in ASS time format (H:MM:SS.CS)
63    pub end: String,
64    /// Style name reference
65    pub style: String,
66    /// Character name or speaker
67    pub name: String,
68    /// Left margin override (pixels)
69    pub margin_l: String,
70    /// Right margin override (pixels)
71    pub margin_r: String,
72    /// Vertical margin override (pixels) (V4+)
73    pub margin_v: String,
74    /// Top margin override (pixels) (V4++) - optional
75    pub margin_t: Option<String>,
76    /// Bottom margin override (pixels) (V4++) - optional
77    pub margin_b: Option<String>,
78    /// Effect specification for special rendering
79    pub effect: String,
80    /// Text content with possible style overrides
81    pub text: String,
82}
83
84impl<'a> From<&Event<'a>> for OwnedEvent {
85    fn from(event: &Event<'a>) -> Self {
86        Self {
87            event_type: event.event_type,
88            layer: event.layer.to_string(),
89            start: event.start.to_string(),
90            end: event.end.to_string(),
91            style: event.style.to_string(),
92            name: event.name.to_string(),
93            margin_l: event.margin_l.to_string(),
94            margin_r: event.margin_r.to_string(),
95            margin_v: event.margin_v.to_string(),
96            margin_t: event.margin_t.map(|s| s.to_string()),
97            margin_b: event.margin_b.map(|s| s.to_string()),
98            effect: event.effect.to_string(),
99            text: event.text.to_string(),
100        }
101    }
102}
103
104/// Filter criteria for events
105#[derive(Debug, Clone, Default)]
106pub struct EventFilter {
107    /// Filter by event type (Dialogue, Comment)
108    pub event_type: Option<EventType>,
109    /// Filter by style name pattern
110    pub style_pattern: Option<String>,
111    /// Filter by speaker/actor name pattern  
112    pub speaker_pattern: Option<String>,
113    /// Filter by text content pattern
114    pub text_pattern: Option<String>,
115    /// Filter by time range (start_cs, end_cs)
116    pub time_range: Option<(u32, u32)>,
117    /// Filter by layer
118    pub layer: Option<u32>,
119    /// Filter by effect presence/pattern
120    pub effect_pattern: Option<String>,
121    /// Use regex for pattern matching
122    pub use_regex: bool,
123    /// Case sensitive matching
124    pub case_sensitive: bool,
125}
126
127/// Sort criteria for events
128#[derive(Debug, Clone, PartialEq, Eq)]
129pub enum EventSortCriteria {
130    /// Sort by start time (default)
131    StartTime,
132    /// Sort by end time
133    EndTime,
134    /// Sort by duration (end - start)
135    Duration,
136    /// Sort by style name
137    Style,
138    /// Sort by speaker/actor name
139    Speaker,
140    /// Sort by layer
141    Layer,
142    /// Sort by document order (original index)
143    Index,
144    /// Sort by text content (alphabetical)
145    Text,
146}
147
148/// Sort options
149#[derive(Debug, Clone)]
150pub struct EventSortOptions {
151    /// Primary sort criteria
152    pub criteria: EventSortCriteria,
153    /// Secondary sort criteria (for ties)
154    pub secondary: Option<EventSortCriteria>,
155    /// Sort in ascending order (default true)
156    pub ascending: bool,
157}
158
159impl Default for EventSortOptions {
160    fn default() -> Self {
161        Self {
162            criteria: EventSortCriteria::Index,
163            secondary: None,
164            ascending: true,
165        }
166    }
167}
168
169// ============================================================================
170// Existing Fluent API Types
171// ============================================================================
172
173/// Fluent API builder for document operations at a specific position
174pub struct AtPosition<'a> {
175    document: &'a mut EditorDocument,
176    position: Position,
177}
178
179impl<'a> AtPosition<'a> {
180    /// Create a new fluent API at position
181    pub(crate) fn new(document: &'a mut EditorDocument, position: Position) -> Self {
182        Self { document, position }
183    }
184
185    /// Insert text at the current position
186    pub fn insert_text(self, text: &str) -> Result<&'a mut EditorDocument> {
187        let range = Range::empty(self.position);
188        self.document.replace(range, text)?;
189        Ok(self.document)
190    }
191
192    /// Insert a line break at the current position
193    pub fn insert_line(self) -> Result<&'a mut EditorDocument> {
194        self.insert_text("\n")
195    }
196
197    /// Delete a number of characters forward from position
198    pub fn delete(self, count: usize) -> Result<&'a mut EditorDocument> {
199        let end = self.position.advance(count);
200        let range = Range::new(self.position, end);
201        self.document.delete(range)?;
202        Ok(self.document)
203    }
204
205    /// Delete characters backward from position (backspace)
206    pub fn backspace(self, count: usize) -> Result<&'a mut EditorDocument> {
207        let start = self.position.retreat(count);
208        let range = Range::new(start, self.position);
209        self.document.delete(range)?;
210        Ok(self.document)
211    }
212
213    /// Replace text from position to end of line
214    pub fn replace_to_line_end(self, text: &str) -> Result<&'a mut EditorDocument> {
215        #[cfg(feature = "rope")]
216        {
217            let rope = self.document.rope();
218            let line_idx = rope.byte_to_line(self.position.offset);
219            let line_end_byte = if line_idx + 1 < rope.len_lines() {
220                rope.line_to_byte(line_idx + 1).saturating_sub(1)
221            } else {
222                rope.len_bytes()
223            };
224            let range = Range::new(self.position, Position::new(line_end_byte));
225            self.document.replace(range, text)?;
226            Ok(self.document)
227        }
228
229        #[cfg(not(feature = "rope"))]
230        {
231            Err(EditorError::FeatureNotEnabled {
232                feature: "line-based operations".to_string(),
233                required_feature: "rope".to_string(),
234            })
235        }
236    }
237
238    /// Get the current position
239    pub const fn position(&self) -> Position {
240        self.position
241    }
242
243    /// Convert position to line/column
244    #[cfg(feature = "rope")]
245    pub fn to_line_column(&self) -> Result<(usize, usize)> {
246        let rope = self.document.rope();
247        let line_idx = rope.byte_to_line(self.position.offset);
248        let line_start = rope.line_to_byte(line_idx);
249        let col_offset = self.position.offset - line_start;
250
251        // Convert byte offset to character offset
252        let line = rope.line(line_idx);
253        let mut char_col = 0;
254        let mut byte_count = 0;
255
256        for ch in line.chars() {
257            if byte_count >= col_offset {
258                break;
259            }
260            byte_count += ch.len_utf8();
261            char_col += 1;
262        }
263
264        Ok((line_idx + 1, char_col + 1)) // Convert to 1-indexed
265    }
266}
267
268/// Fluent API builder for operations on a selected range
269pub struct SelectRange<'a> {
270    document: &'a mut EditorDocument,
271    range: Range,
272}
273
274impl<'a> SelectRange<'a> {
275    /// Create a new fluent API for range
276    pub(crate) fn new(document: &'a mut EditorDocument, range: Range) -> Self {
277        Self { document, range }
278    }
279
280    /// Replace the selected range with text
281    pub fn replace_with(self, text: &str) -> Result<&'a mut EditorDocument> {
282        self.document.replace(self.range, text)?;
283        Ok(self.document)
284    }
285
286    /// Delete the selected range
287    pub fn delete(self) -> Result<&'a mut EditorDocument> {
288        self.document.delete(self.range)?;
289        Ok(self.document)
290    }
291
292    /// Wrap the selection with ASS tags
293    pub fn wrap_with_tag(self, open_tag: &str, close_tag: &str) -> Result<&'a mut EditorDocument> {
294        // Get the selected text
295        let selected = self
296            .document
297            .rope()
298            .byte_slice(self.range.start.offset..self.range.end.offset);
299        let mut wrapped =
300            String::with_capacity(open_tag.len() + selected.len_bytes() + close_tag.len());
301        wrapped.push_str(open_tag);
302        wrapped.push_str(&selected.to_string());
303        wrapped.push_str(close_tag);
304
305        self.document.replace(self.range, &wrapped)?;
306        Ok(self.document)
307    }
308
309    /// Indent the selected lines
310    #[cfg(feature = "rope")]
311    pub fn indent(self, spaces: usize) -> Result<&'a mut EditorDocument> {
312        // Get line information before mutating
313        let start_line = self.document.rope().byte_to_line(self.range.start.offset);
314        let end_line = self.document.rope().byte_to_line(self.range.end.offset);
315        let indent = " ".repeat(spaces);
316
317        // Collect line positions
318        let mut line_positions = Vec::new();
319        for line_idx in (start_line..=end_line).rev() {
320            let line_start = self.document.rope().line_to_byte(line_idx);
321            line_positions.push(line_start);
322        }
323
324        // Apply indentation
325        for line_start in line_positions {
326            let pos = Position::new(line_start);
327            let range = Range::empty(pos);
328            self.document.replace(range, &indent)?;
329        }
330
331        Ok(self.document)
332    }
333
334    /// Unindent the selected lines
335    #[cfg(feature = "rope")]
336    pub fn unindent(self, spaces: usize) -> Result<&'a mut EditorDocument> {
337        // Get line information before mutating
338        let start_line = self.document.rope().byte_to_line(self.range.start.offset);
339        let end_line = self.document.rope().byte_to_line(self.range.end.offset);
340
341        // Collect unindent operations
342        let mut unindent_ops = Vec::new();
343        for line_idx in (start_line..=end_line).rev() {
344            let line_start = self.document.rope().line_to_byte(line_idx);
345            let line = self.document.rope().line(line_idx);
346
347            // Count spaces to remove
348            let mut space_count = 0;
349            for ch in line.chars().take(spaces) {
350                if ch == ' ' {
351                    space_count += 1;
352                } else {
353                    break;
354                }
355            }
356
357            if space_count > 0 {
358                unindent_ops.push((line_start, space_count));
359            }
360        }
361
362        // Apply unindent operations
363        for (line_start, space_count) in unindent_ops {
364            let range = Range::new(
365                Position::new(line_start),
366                Position::new(line_start + space_count),
367            );
368            self.document.delete(range)?;
369        }
370
371        Ok(self.document)
372    }
373
374    /// Get the selected text
375    pub fn text(&self) -> String {
376        self.document
377            .rope()
378            .byte_slice(self.range.start.offset..self.range.end.offset)
379            .to_string()
380    }
381
382    /// Get the range
383    pub const fn range(&self) -> Range {
384        self.range
385    }
386}
387
388/// Fluent API builder for style operations
389pub struct StyleOps<'a> {
390    document: &'a mut EditorDocument,
391}
392
393impl<'a> StyleOps<'a> {
394    /// Create a new style operations builder
395    pub(crate) fn new(document: &'a mut EditorDocument) -> Self {
396        Self { document }
397    }
398
399    /// Create a new style
400    pub fn create(self, name: &str, builder: StyleBuilder) -> Result<&'a mut EditorDocument> {
401        let command = CreateStyleCommand::new(name.to_string(), builder);
402        command.execute(self.document)?;
403        Ok(self.document)
404    }
405
406    /// Edit an existing style
407    pub fn edit(self, name: &str) -> StyleEditor<'a> {
408        StyleEditor::new(self.document, name.to_string())
409    }
410
411    /// Delete a style
412    pub fn delete(self, name: &str) -> Result<&'a mut EditorDocument> {
413        let command = DeleteStyleCommand::new(name.to_string());
414        command.execute(self.document)?;
415        Ok(self.document)
416    }
417
418    /// Clone a style
419    pub fn clone(self, source: &str, target: &str) -> Result<&'a mut EditorDocument> {
420        let command = CloneStyleCommand::new(source.to_string(), target.to_string());
421        command.execute(self.document)?;
422        Ok(self.document)
423    }
424
425    /// Apply a style to events
426    pub fn apply(self, old_style: &str, new_style: &str) -> StyleApplicator<'a> {
427        StyleApplicator::new(self.document, old_style.to_string(), new_style.to_string())
428    }
429}
430
431/// Fluent API builder for editing a specific style
432pub struct StyleEditor<'a> {
433    document: &'a mut EditorDocument,
434    command: EditStyleCommand,
435}
436
437impl<'a> StyleEditor<'a> {
438    /// Create a new style editor
439    pub(crate) fn new(document: &'a mut EditorDocument, style_name: String) -> Self {
440        let command = EditStyleCommand::new(style_name);
441        Self { document, command }
442    }
443
444    /// Set font name
445    pub fn font(mut self, font: &str) -> Self {
446        self.command = self.command.set_font(font);
447        self
448    }
449
450    /// Set font size
451    pub fn size(mut self, size: u32) -> Self {
452        self.command = self.command.set_size(size);
453        self
454    }
455
456    /// Set primary color
457    pub fn color(mut self, color: &str) -> Self {
458        self.command = self.command.set_color(color);
459        self
460    }
461
462    /// Set bold
463    pub fn bold(mut self, bold: bool) -> Self {
464        self.command = self.command.set_bold(bold);
465        self
466    }
467
468    /// Set italic
469    pub fn italic(mut self, italic: bool) -> Self {
470        self.command = self.command.set_italic(italic);
471        self
472    }
473
474    /// Set alignment
475    pub fn alignment(mut self, alignment: u32) -> Self {
476        self.command = self.command.set_alignment(alignment);
477        self
478    }
479
480    /// Set a custom field
481    pub fn field(mut self, name: &str, value: &str) -> Self {
482        self.command = self.command.set_field(name, value.to_string());
483        self
484    }
485
486    /// Apply the changes
487    pub fn apply(self) -> Result<&'a mut EditorDocument> {
488        self.command.execute(self.document)?;
489        Ok(self.document)
490    }
491}
492
493/// Fluent API builder for applying styles to events
494pub struct StyleApplicator<'a> {
495    document: &'a mut EditorDocument,
496    command: ApplyStyleCommand,
497}
498
499impl<'a> StyleApplicator<'a> {
500    /// Create a new style applicator
501    pub(crate) fn new(
502        document: &'a mut EditorDocument,
503        old_style: String,
504        new_style: String,
505    ) -> Self {
506        let command = ApplyStyleCommand::new(old_style, new_style);
507        Self { document, command }
508    }
509
510    /// Only apply to events containing specific text
511    pub fn with_filter(mut self, filter: &str) -> Self {
512        self.command = self.command.with_filter(filter.to_string());
513        self
514    }
515
516    /// Apply the style changes
517    pub fn apply(self) -> Result<&'a mut EditorDocument> {
518        self.command.execute(self.document)?;
519        Ok(self.document)
520    }
521}
522
523/// Fluent API builder for event operations
524pub struct EventOps<'a> {
525    document: &'a mut EditorDocument,
526}
527
528impl<'a> EventOps<'a> {
529    /// Create a new event operations builder
530    pub(crate) fn new(document: &'a mut EditorDocument) -> Self {
531        Self { document }
532    }
533
534    /// Split an event at a specific time
535    pub fn split(self, event_index: usize, split_time: &str) -> Result<&'a mut EditorDocument> {
536        let command = SplitEventCommand::new(event_index, split_time.to_string());
537        command.execute(self.document)?;
538        Ok(self.document)
539    }
540
541    /// Merge two consecutive events
542    pub fn merge(self, first_index: usize, second_index: usize) -> EventMerger<'a> {
543        EventMerger::new(self.document, first_index, second_index)
544    }
545
546    /// Adjust timing for events
547    pub fn timing(self) -> EventTimer<'a> {
548        EventTimer::new(self.document)
549    }
550
551    /// Toggle event types between Dialogue and Comment
552    pub fn toggle_type(self) -> EventToggler<'a> {
553        EventToggler::new(self.document)
554    }
555
556    /// Modify event effects
557    pub fn effects(self) -> EventEffector<'a> {
558        EventEffector::new(self.document)
559    }
560
561    // ============================================================================
562    // Direct Event Access Methods (NEW!)
563    // ============================================================================
564
565    /// Get event information by index
566    pub fn get(self, index: usize) -> Result<Option<EventInfo>> {
567        self.document
568            .parse_script_with(|script| -> Result<Option<EventInfo>> {
569                let mut current_index = 0;
570
571                for section in script.sections() {
572                    if let Section::Events(events) = section {
573                        for event in events {
574                            if current_index == index {
575                                let event_info = EventInfo {
576                                    index,
577                                    event: OwnedEvent::from(event),
578                                    line_number: self.find_line_number_for_event(event)?,
579                                    range: self.find_range_for_event(event)?,
580                                };
581                                return Ok(Some(event_info));
582                            }
583                            current_index += 1;
584                        }
585                    }
586                }
587
588                Ok(None)
589            })?
590    }
591
592    /// Get event by index with fluent access to properties
593    pub fn event(self, index: usize) -> EventAccessor<'a> {
594        EventAccessor::new(self.document, index)
595    }
596
597    /// Get all events as a vector
598    pub fn all(self) -> Result<Vec<EventInfo>> {
599        EventQuery::new(self.document).execute()
600    }
601
602    /// Get event count
603    pub fn count(self) -> Result<usize> {
604        self.document.parse_script_with(|script| {
605            let mut count = 0;
606
607            for section in script.sections() {
608                if let Section::Events(events) = section {
609                    count += events.len();
610                }
611            }
612
613            count
614        })
615    }
616
617    // ============================================================================
618    // Query and Filtering Methods (NEW!)
619    // ============================================================================
620
621    /// Start a query chain for filtering and sorting events
622    pub fn query(self) -> EventQuery<'a> {
623        EventQuery::new(self.document)
624    }
625
626    /// Shorthand for common filters
627    pub fn dialogues(self) -> EventQuery<'a> {
628        EventQuery::new(self.document).filter_by_type(EventType::Dialogue)
629    }
630
631    pub fn comments(self) -> EventQuery<'a> {
632        EventQuery::new(self.document).filter_by_type(EventType::Comment)
633    }
634
635    pub fn in_time_range(self, start_cs: u32, end_cs: u32) -> EventQuery<'a> {
636        EventQuery::new(self.document).filter_by_time_range(start_cs, end_cs)
637    }
638
639    pub fn with_style(self, pattern: &str) -> EventQuery<'a> {
640        EventQuery::new(self.document).filter_by_style(pattern)
641    }
642
643    /// Find events by text pattern
644    pub fn containing(self, text: &str) -> EventQuery<'a> {
645        EventQuery::new(self.document).filter_by_text(text)
646    }
647
648    /// Get events in order they appear in document
649    pub fn in_order(self) -> EventQuery<'a> {
650        EventQuery::new(self.document).sort(EventSortCriteria::Index)
651    }
652
653    /// Get events sorted by time
654    pub fn by_time(self) -> EventQuery<'a> {
655        EventQuery::new(self.document).sort_by_time()
656    }
657
658    // ============================================================================
659    // Helper Methods
660    // ============================================================================
661
662    fn find_line_number_for_event(&self, _event: &Event) -> Result<usize> {
663        // For now, return a placeholder. This would need to be implemented
664        // by tracking line numbers during parsing or using rope byte-to-line conversion
665        Ok(1)
666    }
667
668    fn find_range_for_event(&self, _event: &Event) -> Result<Range> {
669        // For now, return a placeholder range. This would need to be implemented
670        // by using the event's span information from the parser
671        Ok(Range::new(Position::new(0), Position::new(0)))
672    }
673}
674
675/// Fluent API builder for merging events
676pub struct EventMerger<'a> {
677    document: &'a mut EditorDocument,
678    first_index: usize,
679    second_index: usize,
680    separator: String,
681}
682
683impl<'a> EventMerger<'a> {
684    /// Create a new event merger
685    pub(crate) fn new(
686        document: &'a mut EditorDocument,
687        first_index: usize,
688        second_index: usize,
689    ) -> Self {
690        Self {
691            document,
692            first_index,
693            second_index,
694            separator: " ".to_string(),
695        }
696    }
697
698    /// Set the text separator for merged text
699    pub fn with_separator(mut self, separator: &str) -> Self {
700        self.separator = separator.to_string();
701        self
702    }
703
704    /// Execute the merge
705    pub fn apply(self) -> Result<&'a mut EditorDocument> {
706        let command = MergeEventsCommand::new(self.first_index, self.second_index)
707            .with_separator(self.separator);
708        command.execute(self.document)?;
709        Ok(self.document)
710    }
711}
712
713/// Fluent API builder for timing adjustments
714pub struct EventTimer<'a> {
715    document: &'a mut EditorDocument,
716    event_indices: Vec<usize>,
717}
718
719impl<'a> EventTimer<'a> {
720    /// Create a new event timer
721    pub(crate) fn new(document: &'a mut EditorDocument) -> Self {
722        Self {
723            document,
724            event_indices: Vec::new(), // Default to all events
725        }
726    }
727
728    /// Specify which events to adjust
729    pub fn events(mut self, indices: Vec<usize>) -> Self {
730        self.event_indices = indices;
731        self
732    }
733
734    /// Adjust a single event
735    pub fn event(mut self, index: usize) -> Self {
736        self.event_indices = vec![index];
737        self
738    }
739
740    /// Shift start and end times by the same offset (preserves duration)
741    pub fn shift(self, offset_cs: i32) -> Result<&'a mut EditorDocument> {
742        let command = TimingAdjustCommand::new(self.event_indices, offset_cs, offset_cs);
743        command.execute(self.document)?;
744        Ok(self.document)
745    }
746
747    /// Shift only start times (changes duration)
748    pub fn shift_start(self, offset_cs: i32) -> Result<&'a mut EditorDocument> {
749        let command = TimingAdjustCommand::new(self.event_indices, offset_cs, 0);
750        command.execute(self.document)?;
751        Ok(self.document)
752    }
753
754    /// Shift only end times (changes duration)
755    pub fn shift_end(self, offset_cs: i32) -> Result<&'a mut EditorDocument> {
756        let command = TimingAdjustCommand::new(self.event_indices, 0, offset_cs);
757        command.execute(self.document)?;
758        Ok(self.document)
759    }
760
761    /// Scale duration by a factor
762    pub fn scale_duration(self, factor: f64) -> Result<&'a mut EditorDocument> {
763        let command = TimingAdjustCommand::scale_duration(self.event_indices, factor);
764        command.execute(self.document)?;
765        Ok(self.document)
766    }
767
768    /// Custom start and end offsets
769    pub fn adjust(
770        self,
771        start_offset_cs: i32,
772        end_offset_cs: i32,
773    ) -> Result<&'a mut EditorDocument> {
774        let command = TimingAdjustCommand::new(self.event_indices, start_offset_cs, end_offset_cs);
775        command.execute(self.document)?;
776        Ok(self.document)
777    }
778}
779
780/// Fluent API builder for toggling event types
781pub struct EventToggler<'a> {
782    document: &'a mut EditorDocument,
783    event_indices: Vec<usize>,
784}
785
786impl<'a> EventToggler<'a> {
787    /// Create a new event toggler
788    pub(crate) fn new(document: &'a mut EditorDocument) -> Self {
789        Self {
790            document,
791            event_indices: Vec::new(), // Default to all events
792        }
793    }
794
795    /// Specify which events to toggle
796    pub fn events(mut self, indices: Vec<usize>) -> Self {
797        self.event_indices = indices;
798        self
799    }
800
801    /// Toggle a single event
802    pub fn event(mut self, index: usize) -> Self {
803        self.event_indices = vec![index];
804        self
805    }
806
807    /// Execute the toggle
808    pub fn apply(self) -> Result<&'a mut EditorDocument> {
809        let command = ToggleEventTypeCommand::new(self.event_indices);
810        command.execute(self.document)?;
811        Ok(self.document)
812    }
813}
814
815/// Fluent API builder for event effects
816pub struct EventEffector<'a> {
817    document: &'a mut EditorDocument,
818    event_indices: Vec<usize>,
819}
820
821impl<'a> EventEffector<'a> {
822    /// Create a new event effector
823    pub(crate) fn new(document: &'a mut EditorDocument) -> Self {
824        Self {
825            document,
826            event_indices: Vec::new(), // Default to all events
827        }
828    }
829
830    /// Specify which events to modify
831    pub fn events(mut self, indices: Vec<usize>) -> Self {
832        self.event_indices = indices;
833        self
834    }
835
836    /// Modify a single event
837    pub fn event(mut self, index: usize) -> Self {
838        self.event_indices = vec![index];
839        self
840    }
841
842    /// Set the effect (replace existing)
843    pub fn set(self, effect: &str) -> Result<&'a mut EditorDocument> {
844        let command = EventEffectCommand::set_effect(self.event_indices, effect.to_string());
845        command.execute(self.document)?;
846        Ok(self.document)
847    }
848
849    /// Clear all effects
850    pub fn clear(self) -> Result<&'a mut EditorDocument> {
851        let command = EventEffectCommand::clear_effect(self.event_indices);
852        command.execute(self.document)?;
853        Ok(self.document)
854    }
855
856    /// Append to existing effect
857    pub fn append(self, effect: &str) -> Result<&'a mut EditorDocument> {
858        let command = EventEffectCommand::append_effect(self.event_indices, effect.to_string());
859        command.execute(self.document)?;
860        Ok(self.document)
861    }
862
863    /// Prepend to existing effect
864    pub fn prepend(self, effect: &str) -> Result<&'a mut EditorDocument> {
865        let command = EventEffectCommand::new(
866            self.event_indices,
867            effect.to_string(),
868            EffectOperation::Prepend,
869        );
870        command.execute(self.document)?;
871        Ok(self.document)
872    }
873}
874
875/// Fluent API for ASS tag operations
876pub struct TagOps<'a> {
877    document: &'a mut EditorDocument,
878    range: Option<Range>,
879    position: Option<Position>,
880}
881
882impl<'a> TagOps<'a> {
883    /// Create new tag operations
884    fn new(document: &'a mut EditorDocument) -> Self {
885        Self {
886            document,
887            range: None,
888            position: None,
889        }
890    }
891
892    /// Set position for tag insertion
893    #[must_use]
894    pub fn at(mut self, position: Position) -> Self {
895        self.position = Some(position);
896        self
897    }
898
899    /// Set range for tag operations
900    #[must_use]
901    pub fn in_range(mut self, range: Range) -> Self {
902        self.range = Some(range);
903        self
904    }
905
906    /// Insert ASS override tag at position
907    pub fn insert(self, tag: &str) -> Result<&'a mut EditorDocument> {
908        let position = self
909            .position
910            .ok_or_else(|| EditorError::command_failed("Position required for tag insertion"))?;
911
912        let command = InsertTagCommand::new(position, tag.to_string());
913        command.execute(self.document)?;
914        Ok(self.document)
915    }
916
917    /// Insert tag without auto-wrapping in {}
918    pub fn insert_raw(self, tag: &str) -> Result<&'a mut EditorDocument> {
919        let position = self
920            .position
921            .ok_or_else(|| EditorError::command_failed("Position required for tag insertion"))?;
922
923        let command = InsertTagCommand::new(position, tag.to_string()).no_auto_wrap();
924        command.execute(self.document)?;
925        Ok(self.document)
926    }
927
928    /// Remove all tags from range
929    pub fn remove_all(self) -> Result<&'a mut EditorDocument> {
930        let range = self
931            .range
932            .ok_or_else(|| EditorError::command_failed("Range required for tag removal"))?;
933
934        let command = RemoveTagCommand::new(range);
935        command.execute(self.document)?;
936        Ok(self.document)
937    }
938
939    /// Remove specific tag pattern from range
940    pub fn remove_pattern(self, pattern: &str) -> Result<&'a mut EditorDocument> {
941        let range = self
942            .range
943            .ok_or_else(|| EditorError::command_failed("Range required for tag removal"))?;
944
945        let command = RemoveTagCommand::new(range).pattern(pattern.to_string());
946        command.execute(self.document)?;
947        Ok(self.document)
948    }
949
950    /// Replace tag pattern with another tag
951    pub fn replace(self, find_pattern: &str, replace_with: &str) -> Result<&'a mut EditorDocument> {
952        let range = self
953            .range
954            .ok_or_else(|| EditorError::command_failed("Range required for tag replacement"))?;
955
956        let command =
957            ReplaceTagCommand::new(range, find_pattern.to_string(), replace_with.to_string());
958        command.execute(self.document)?;
959        Ok(self.document)
960    }
961
962    /// Replace all occurrences of tag pattern
963    pub fn replace_all(
964        self,
965        find_pattern: &str,
966        replace_with: &str,
967    ) -> Result<&'a mut EditorDocument> {
968        let range = self
969            .range
970            .ok_or_else(|| EditorError::command_failed("Range required for tag replacement"))?;
971
972        let command =
973            ReplaceTagCommand::new(range, find_pattern.to_string(), replace_with.to_string()).all();
974        command.execute(self.document)?;
975        Ok(self.document)
976    }
977
978    /// Wrap range with opening and closing tags
979    pub fn wrap(self, opening_tag: &str) -> Result<&'a mut EditorDocument> {
980        let range = self
981            .range
982            .ok_or_else(|| EditorError::command_failed("Range required for tag wrapping"))?;
983
984        let command = WrapTagCommand::new(range, opening_tag.to_string());
985        command.execute(self.document)?;
986        Ok(self.document)
987    }
988
989    /// Wrap range with explicit opening and closing tags
990    pub fn wrap_with(self, opening_tag: &str, closing_tag: &str) -> Result<&'a mut EditorDocument> {
991        let range = self
992            .range
993            .ok_or_else(|| EditorError::command_failed("Range required for tag wrapping"))?;
994
995        let command = WrapTagCommand::new(range, opening_tag.to_string())
996            .closing_tag(closing_tag.to_string());
997        command.execute(self.document)?;
998        Ok(self.document)
999    }
1000
1001    /// Parse tags from range and return parsed information
1002    pub fn parse(self) -> Result<Vec<ParsedTag>> {
1003        let range = self.range.unwrap_or_else(|| {
1004            Range::new(Position::new(0), Position::new(self.document.text().len()))
1005        });
1006
1007        let command = ParseTagCommand::new(range).with_positions();
1008        let text = self.document.text_range(range)?;
1009        command.parse_tags_from_text(&text)
1010    }
1011}
1012
1013/// Fluent API for ASS karaoke operations
1014pub struct KaraokeOps<'a> {
1015    document: &'a mut EditorDocument,
1016    range: Option<Range>,
1017}
1018
1019impl<'a> KaraokeOps<'a> {
1020    /// Create new karaoke operations
1021    fn new(document: &'a mut EditorDocument) -> Self {
1022        Self {
1023            document,
1024            range: None,
1025        }
1026    }
1027
1028    /// Set range for karaoke operations
1029    #[must_use]
1030    pub fn in_range(mut self, range: Range) -> Self {
1031        self.range = Some(range);
1032        self
1033    }
1034
1035    /// Generate karaoke timing for text
1036    pub fn generate(self, default_duration: u32) -> KaraokeGenerator<'a> {
1037        let default_range = if self.range.is_none() {
1038            let doc_len = self.document.text().len();
1039            Range::new(Position::new(0), Position::new(doc_len))
1040        } else {
1041            Range::new(Position::new(0), Position::new(0)) // Placeholder, won't be used
1042        };
1043
1044        KaraokeGenerator {
1045            document: self.document,
1046            range: self.range.unwrap_or(default_range),
1047            default_duration,
1048            karaoke_type: KaraokeType::Standard,
1049            auto_detect_syllables: true,
1050        }
1051    }
1052
1053    /// Split existing karaoke timing
1054    pub fn split(self, split_positions: Vec<usize>) -> KaraokeSplitter<'a> {
1055        let default_range = if self.range.is_none() {
1056            let doc_len = self.document.text().len();
1057            Range::new(Position::new(0), Position::new(doc_len))
1058        } else {
1059            Range::new(Position::new(0), Position::new(0)) // Placeholder
1060        };
1061
1062        KaraokeSplitter {
1063            document: self.document,
1064            range: self.range.unwrap_or(default_range),
1065            split_positions,
1066            new_duration: None,
1067        }
1068    }
1069
1070    /// Adjust existing karaoke timing
1071    pub fn adjust(self) -> KaraokeAdjuster<'a> {
1072        let default_range = if self.range.is_none() {
1073            let doc_len = self.document.text().len();
1074            Range::new(Position::new(0), Position::new(doc_len))
1075        } else {
1076            Range::new(Position::new(0), Position::new(0)) // Placeholder
1077        };
1078
1079        KaraokeAdjuster {
1080            document: self.document,
1081            range: self.range.unwrap_or(default_range),
1082        }
1083    }
1084
1085    /// Apply karaoke template
1086    pub fn apply(self) -> KaraokeApplicator<'a> {
1087        let default_range = if self.range.is_none() {
1088            let doc_len = self.document.text().len();
1089            Range::new(Position::new(0), Position::new(doc_len))
1090        } else {
1091            Range::new(Position::new(0), Position::new(0)) // Placeholder
1092        };
1093
1094        KaraokeApplicator {
1095            document: self.document,
1096            range: self.range.unwrap_or(default_range),
1097        }
1098    }
1099}
1100
1101/// Karaoke generator builder
1102pub struct KaraokeGenerator<'a> {
1103    document: &'a mut EditorDocument,
1104    range: Range,
1105    default_duration: u32,
1106    karaoke_type: KaraokeType,
1107    auto_detect_syllables: bool,
1108}
1109
1110impl<'a> KaraokeGenerator<'a> {
1111    /// Set karaoke type
1112    #[must_use]
1113    pub fn karaoke_type(mut self, karaoke_type: KaraokeType) -> Self {
1114        self.karaoke_type = karaoke_type;
1115        self
1116    }
1117
1118    /// Use manual syllable splitting
1119    #[must_use]
1120    pub fn manual_syllables(mut self) -> Self {
1121        self.auto_detect_syllables = false;
1122        self
1123    }
1124
1125    /// Execute the generation
1126    pub fn execute(self) -> Result<&'a mut EditorDocument> {
1127        let mut command = GenerateKaraokeCommand::new(self.range, self.default_duration)
1128            .karaoke_type(self.karaoke_type);
1129
1130        if !self.auto_detect_syllables {
1131            command = command.manual_syllables();
1132        }
1133
1134        command.execute(self.document)?;
1135        Ok(self.document)
1136    }
1137}
1138
1139/// Karaoke splitter builder
1140pub struct KaraokeSplitter<'a> {
1141    document: &'a mut EditorDocument,
1142    range: Range,
1143    split_positions: Vec<usize>,
1144    new_duration: Option<u32>,
1145}
1146
1147impl<'a> KaraokeSplitter<'a> {
1148    /// Set new duration for split segments
1149    #[must_use]
1150    pub fn duration(mut self, duration: u32) -> Self {
1151        self.new_duration = Some(duration);
1152        self
1153    }
1154
1155    /// Execute the split
1156    pub fn execute(self) -> Result<&'a mut EditorDocument> {
1157        let mut command = SplitKaraokeCommand::new(self.range, self.split_positions);
1158
1159        if let Some(duration) = self.new_duration {
1160            command = command.duration(duration);
1161        }
1162
1163        command.execute(self.document)?;
1164        Ok(self.document)
1165    }
1166}
1167
1168/// Karaoke adjuster builder
1169pub struct KaraokeAdjuster<'a> {
1170    document: &'a mut EditorDocument,
1171    range: Range,
1172}
1173
1174impl<'a> KaraokeAdjuster<'a> {
1175    /// Scale timing by factor
1176    pub fn scale(self, factor: f32) -> Result<&'a mut EditorDocument> {
1177        let command = AdjustKaraokeCommand::scale(self.range, factor);
1178        command.execute(self.document)?;
1179        Ok(self.document)
1180    }
1181
1182    /// Offset timing by centiseconds
1183    pub fn offset(self, offset: i32) -> Result<&'a mut EditorDocument> {
1184        let command = AdjustKaraokeCommand::offset(self.range, offset);
1185        command.execute(self.document)?;
1186        Ok(self.document)
1187    }
1188
1189    /// Set all timing to specific duration
1190    pub fn set_all(self, duration: u32) -> Result<&'a mut EditorDocument> {
1191        let command = AdjustKaraokeCommand::set_all(self.range, duration);
1192        command.execute(self.document)?;
1193        Ok(self.document)
1194    }
1195
1196    /// Apply custom timing to each syllable
1197    pub fn custom(self, timings: Vec<u32>) -> Result<&'a mut EditorDocument> {
1198        let command = AdjustKaraokeCommand::custom(self.range, timings);
1199        command.execute(self.document)?;
1200        Ok(self.document)
1201    }
1202}
1203
1204/// Karaoke applicator builder
1205pub struct KaraokeApplicator<'a> {
1206    document: &'a mut EditorDocument,
1207    range: Range,
1208}
1209
1210impl<'a> KaraokeApplicator<'a> {
1211    /// Apply equal timing
1212    pub fn equal(self, duration: u32, karaoke_type: KaraokeType) -> Result<&'a mut EditorDocument> {
1213        let command = ApplyKaraokeCommand::equal(self.range, duration, karaoke_type);
1214        command.execute(self.document)?;
1215        Ok(self.document)
1216    }
1217
1218    /// Apply beat-based timing
1219    pub fn beat(
1220        self,
1221        bpm: u32,
1222        beats_per_syllable: f32,
1223        karaoke_type: KaraokeType,
1224    ) -> Result<&'a mut EditorDocument> {
1225        let command = ApplyKaraokeCommand::beat(self.range, bpm, beats_per_syllable, karaoke_type);
1226        command.execute(self.document)?;
1227        Ok(self.document)
1228    }
1229
1230    /// Apply pattern-based timing
1231    pub fn pattern(
1232        self,
1233        durations: Vec<u32>,
1234        karaoke_type: KaraokeType,
1235    ) -> Result<&'a mut EditorDocument> {
1236        let command = ApplyKaraokeCommand::pattern(self.range, durations, karaoke_type);
1237        command.execute(self.document)?;
1238        Ok(self.document)
1239    }
1240
1241    /// Import timing from another event
1242    pub fn import_from(self, source_event_index: usize) -> Result<&'a mut EditorDocument> {
1243        let command = ApplyKaraokeCommand::import_from(self.range, source_event_index);
1244        command.execute(self.document)?;
1245        Ok(self.document)
1246    }
1247}
1248
1249/// Extension trait to add fluent API to EditorDocument
1250impl EditorDocument {
1251    /// Start a fluent operation at a position
1252    pub fn at_pos(&mut self, position: Position) -> AtPosition<'_> {
1253        AtPosition::new(self, position)
1254    }
1255
1256    /// Start a fluent operation at a line
1257    #[cfg(feature = "rope")]
1258    pub fn at_line(&mut self, line: usize) -> Result<AtPosition<'_>> {
1259        let line_idx = line.saturating_sub(1);
1260        if line_idx >= self.rope().len_lines() {
1261            return Err(EditorError::InvalidPosition { line, column: 1 });
1262        }
1263
1264        let byte_pos = self.rope().line_to_byte(line_idx);
1265        Ok(AtPosition::new(self, Position::new(byte_pos)))
1266    }
1267
1268    /// Start a fluent operation at the start of the document
1269    pub fn at_start(&mut self) -> AtPosition<'_> {
1270        AtPosition::new(self, Position::start())
1271    }
1272
1273    /// Start a fluent operation at the end of the document
1274    pub fn at_end(&mut self) -> AtPosition<'_> {
1275        let end_pos = Position::new(self.len());
1276        AtPosition::new(self, end_pos)
1277    }
1278
1279    /// Start a fluent operation on a range
1280    pub fn select(&mut self, range: Range) -> SelectRange<'_> {
1281        SelectRange::new(self, range)
1282    }
1283
1284    /// Start fluent style operations
1285    pub fn styles(&mut self) -> StyleOps<'_> {
1286        StyleOps::new(self)
1287    }
1288
1289    /// Start fluent event operations
1290    pub fn events(&mut self) -> EventOps<'_> {
1291        EventOps::new(self)
1292    }
1293
1294    /// Start fluent tag operations
1295    pub fn tags(&mut self) -> TagOps<'_> {
1296        TagOps::new(self)
1297    }
1298
1299    /// Start fluent karaoke operations
1300    pub fn karaoke(&mut self) -> KaraokeOps<'_> {
1301        KaraokeOps::new(self)
1302    }
1303
1304    /// Convert a Position to line/column tuple
1305    #[cfg(feature = "rope")]
1306    pub fn position_to_line_col(&self, pos: Position) -> Result<(usize, usize)> {
1307        if pos.offset > self.len() {
1308            return Err(EditorError::PositionOutOfBounds {
1309                position: pos.offset,
1310                length: self.len(),
1311            });
1312        }
1313
1314        let line_idx = self.rope().byte_to_line(pos.offset);
1315        let line_start = self.rope().line_to_byte(line_idx);
1316        let col_offset = pos.offset - line_start;
1317
1318        // Convert byte offset to character offset
1319        let line = self.rope().line(line_idx);
1320        let mut char_col = 0;
1321        let mut byte_count = 0;
1322
1323        for ch in line.chars() {
1324            if byte_count >= col_offset {
1325                break;
1326            }
1327            byte_count += ch.len_utf8();
1328            char_col += 1;
1329        }
1330
1331        Ok((line_idx + 1, char_col + 1)) // Convert to 1-indexed
1332    }
1333
1334    /// Convert line/column to Position
1335    #[cfg(feature = "rope")]
1336    pub fn line_column_to_position(&self, line: usize, column: usize) -> Result<Position> {
1337        use super::PositionBuilder;
1338
1339        PositionBuilder::new()
1340            .line(line)
1341            .column(column)
1342            .build(self.rope())
1343    }
1344}
1345
1346// ============================================================================
1347// Event Accessor for Individual Event Operations (NEW!)
1348// ============================================================================
1349
1350/// Fluent accessor for individual event properties and operations
1351pub struct EventAccessor<'a> {
1352    document: &'a mut EditorDocument,
1353    index: usize,
1354}
1355
1356impl<'a> EventAccessor<'a> {
1357    pub(crate) fn new(document: &'a mut EditorDocument, index: usize) -> Self {
1358        Self { document, index }
1359    }
1360
1361    /// Get the full event information
1362    pub fn get(self) -> Result<Option<EventInfo>> {
1363        EventOps::new(self.document).get(self.index)
1364    }
1365
1366    /// Get just the event text
1367    pub fn text(self) -> Result<Option<String>> {
1368        Ok(self.get()?.map(|info| info.event.text))
1369    }
1370
1371    /// Get event style name
1372    pub fn style(self) -> Result<Option<String>> {
1373        Ok(self.get()?.map(|info| info.event.style))
1374    }
1375
1376    /// Get event speaker/actor name
1377    pub fn speaker(self) -> Result<Option<String>> {
1378        Ok(self.get()?.map(|info| info.event.name))
1379    }
1380
1381    /// Get event timing as (start, end) in centiseconds
1382    pub fn timing(self) -> Result<Option<(String, String)>> {
1383        Ok(self.get()?.map(|info| (info.event.start, info.event.end)))
1384    }
1385
1386    /// Get event start time
1387    pub fn start_time(self) -> Result<Option<String>> {
1388        Ok(self.get()?.map(|info| info.event.start))
1389    }
1390
1391    /// Get event end time  
1392    pub fn end_time(self) -> Result<Option<String>> {
1393        Ok(self.get()?.map(|info| info.event.end))
1394    }
1395
1396    /// Get event layer
1397    pub fn layer(self) -> Result<Option<String>> {
1398        Ok(self.get()?.map(|info| info.event.layer))
1399    }
1400
1401    /// Get event effect
1402    pub fn effect(self) -> Result<Option<String>> {
1403        Ok(self.get()?.map(|info| info.event.effect))
1404    }
1405
1406    /// Get event type (Dialogue/Comment)
1407    pub fn event_type(self) -> Result<Option<EventType>> {
1408        Ok(self.get()?.map(|info| info.event.event_type))
1409    }
1410
1411    /// Check if event exists
1412    pub fn exists(self) -> Result<bool> {
1413        Ok(self.get()?.is_some())
1414    }
1415
1416    /// Get event margins as (left, right, vertical)
1417    pub fn margins(self) -> Result<Option<(String, String, String)>> {
1418        Ok(self.get()?.map(|info| {
1419            (
1420                info.event.margin_l,
1421                info.event.margin_r,
1422                info.event.margin_v,
1423            )
1424        }))
1425    }
1426
1427    /// Convert to timing operations for this specific event
1428    pub fn timing_ops(self) -> EventTimer<'a> {
1429        EventTimer::new(self.document).event(self.index)
1430    }
1431
1432    /// Convert to toggle operations for this specific event
1433    pub fn toggle_ops(self) -> EventToggler<'a> {
1434        EventToggler::new(self.document).event(self.index)
1435    }
1436
1437    /// Convert to effect operations for this specific event
1438    pub fn effect_ops(self) -> EventEffector<'a> {
1439        EventEffector::new(self.document).event(self.index)
1440    }
1441}
1442
1443// ============================================================================
1444// Event Query Builder for Filtering and Sorting (NEW!)
1445// ============================================================================
1446
1447/// Main query builder for filtering and sorting events
1448pub struct EventQuery<'a> {
1449    document: &'a mut EditorDocument,
1450    filters: EventFilter,
1451    sort_options: Option<EventSortOptions>,
1452    limit: Option<usize>,
1453}
1454
1455impl<'a> EventQuery<'a> {
1456    pub(crate) fn new(document: &'a mut EditorDocument) -> Self {
1457        Self {
1458            document,
1459            filters: EventFilter::default(),
1460            sort_options: None,
1461            limit: None,
1462        }
1463    }
1464
1465    // Filter methods
1466    pub fn filter(mut self, filter: EventFilter) -> Self {
1467        self.filters = filter;
1468        self
1469    }
1470
1471    pub fn filter_by_type(mut self, event_type: EventType) -> Self {
1472        self.filters.event_type = Some(event_type);
1473        self
1474    }
1475
1476    pub fn filter_by_style(mut self, pattern: &str) -> Self {
1477        self.filters.style_pattern = Some(pattern.to_string());
1478        self
1479    }
1480
1481    pub fn filter_by_speaker(mut self, pattern: &str) -> Self {
1482        self.filters.speaker_pattern = Some(pattern.to_string());
1483        self
1484    }
1485
1486    pub fn filter_by_text(mut self, pattern: &str) -> Self {
1487        self.filters.text_pattern = Some(pattern.to_string());
1488        self
1489    }
1490
1491    pub fn filter_by_time_range(mut self, start_cs: u32, end_cs: u32) -> Self {
1492        self.filters.time_range = Some((start_cs, end_cs));
1493        self
1494    }
1495
1496    pub fn filter_by_layer(mut self, layer: u32) -> Self {
1497        self.filters.layer = Some(layer);
1498        self
1499    }
1500
1501    pub fn filter_by_effect(mut self, pattern: &str) -> Self {
1502        self.filters.effect_pattern = Some(pattern.to_string());
1503        self
1504    }
1505
1506    pub fn with_regex(mut self, use_regex: bool) -> Self {
1507        self.filters.use_regex = use_regex;
1508        self
1509    }
1510
1511    pub fn case_sensitive(mut self, case_sensitive: bool) -> Self {
1512        self.filters.case_sensitive = case_sensitive;
1513        self
1514    }
1515
1516    // Sort methods
1517    pub fn sort(mut self, criteria: EventSortCriteria) -> Self {
1518        self.sort_options = Some(EventSortOptions {
1519            criteria,
1520            secondary: None,
1521            ascending: true,
1522        });
1523        self
1524    }
1525
1526    pub fn sort_by(mut self, options: EventSortOptions) -> Self {
1527        self.sort_options = Some(options);
1528        self
1529    }
1530
1531    pub fn sort_by_time(self) -> Self {
1532        self.sort(EventSortCriteria::StartTime)
1533    }
1534
1535    pub fn sort_by_style(self) -> Self {
1536        self.sort(EventSortCriteria::Style)
1537    }
1538
1539    pub fn sort_by_duration(self) -> Self {
1540        self.sort(EventSortCriteria::Duration)
1541    }
1542
1543    pub fn descending(mut self) -> Self {
1544        if let Some(ref mut options) = self.sort_options {
1545            options.ascending = false;
1546        }
1547        self
1548    }
1549
1550    pub fn then_by(mut self, criteria: EventSortCriteria) -> Self {
1551        if let Some(ref mut options) = self.sort_options {
1552            options.secondary = Some(criteria);
1553        }
1554        self
1555    }
1556
1557    // Limit results
1558    pub fn limit(mut self, count: usize) -> Self {
1559        self.limit = Some(count);
1560        self
1561    }
1562
1563    pub fn take(self, count: usize) -> Self {
1564        self.limit(count)
1565    }
1566
1567    // Execution methods
1568    pub fn execute(self) -> Result<Vec<EventInfo>> {
1569        let mut results = self.collect_events()?;
1570
1571        // Apply filters
1572        results = self.apply_filters(results)?;
1573
1574        // Apply sorting
1575        if let Some(ref sort_options) = self.sort_options {
1576            self.apply_sort(&mut results, sort_options);
1577        }
1578
1579        // Apply limit
1580        if let Some(limit) = self.limit {
1581            results.truncate(limit);
1582        }
1583
1584        Ok(results)
1585    }
1586
1587    /// Execute and return only the indices
1588    pub fn indices(self) -> Result<Vec<usize>> {
1589        Ok(self.execute()?.into_iter().map(|info| info.index).collect())
1590    }
1591
1592    /// Execute and return events with their indices as tuples
1593    pub fn with_indices(self) -> Result<Vec<(usize, OwnedEvent)>> {
1594        Ok(self
1595            .execute()?
1596            .into_iter()
1597            .map(|info| (info.index, info.event))
1598            .collect())
1599    }
1600
1601    /// Execute and get the first matching event
1602    pub fn first(self) -> Result<Option<EventInfo>> {
1603        let mut results = self.limit(1).execute()?;
1604        Ok(results.pop())
1605    }
1606
1607    /// Execute and get count of matching events
1608    pub fn count(self) -> Result<usize> {
1609        Ok(self.execute()?.len())
1610    }
1611
1612    /// Chain with existing fluent operations
1613    pub fn timing(self) -> Result<EventTimer<'a>> {
1614        let _indices: Vec<usize> = self.execute()?.into_iter().map(|info| info.index).collect();
1615        // Need to create a new EventQuery to get document reference since self is consumed
1616        // This is a limitation of the current API design
1617        Err(EditorError::command_failed(
1618            "Cannot chain timing operations after query execution - use indices() first",
1619        ))
1620    }
1621
1622    pub fn toggle_type(self) -> Result<EventToggler<'a>> {
1623        let _indices: Vec<usize> = self.execute()?.into_iter().map(|info| info.index).collect();
1624        Err(EditorError::command_failed(
1625            "Cannot chain toggle operations after query execution - use indices() first",
1626        ))
1627    }
1628
1629    pub fn effects(self) -> Result<EventEffector<'a>> {
1630        let _indices: Vec<usize> = self.execute()?.into_iter().map(|info| info.index).collect();
1631        Err(EditorError::command_failed(
1632            "Cannot chain effect operations after query execution - use indices() first",
1633        ))
1634    }
1635
1636    // ============================================================================
1637    // Implementation Details
1638    // ============================================================================
1639
1640    fn collect_events(&self) -> Result<Vec<EventInfo>> {
1641        self.document
1642            .parse_script_with(|script| -> Result<Vec<EventInfo>> {
1643                let mut events = Vec::new();
1644                let mut event_index = 0;
1645
1646                for section in script.sections() {
1647                    if let Section::Events(section_events) = section {
1648                        for event in section_events {
1649                            // Build EventInfo with position tracking
1650                            let event_info = EventInfo {
1651                                index: event_index,
1652                                event: OwnedEvent::from(event),
1653                                line_number: self.find_line_number(event)?,
1654                                range: self.find_event_range(event)?,
1655                            };
1656                            events.push(event_info);
1657                            event_index += 1;
1658                        }
1659                    }
1660                }
1661
1662                Ok(events)
1663            })?
1664    }
1665
1666    fn apply_filters(&self, events: Vec<EventInfo>) -> Result<Vec<EventInfo>> {
1667        let mut filtered = Vec::new();
1668
1669        for event_info in events {
1670            if self.matches_filter(&event_info)? {
1671                filtered.push(event_info);
1672            }
1673        }
1674
1675        Ok(filtered)
1676    }
1677
1678    fn matches_filter(&self, event_info: &EventInfo) -> Result<bool> {
1679        // Apply each filter criteria
1680        if let Some(event_type) = self.filters.event_type {
1681            if event_info.event.event_type != event_type {
1682                return Ok(false);
1683            }
1684        }
1685
1686        if let Some(ref pattern) = self.filters.style_pattern {
1687            if !self.matches_pattern(&event_info.event.style, pattern)? {
1688                return Ok(false);
1689            }
1690        }
1691
1692        if let Some(ref pattern) = self.filters.text_pattern {
1693            if !self.matches_pattern(&event_info.event.text, pattern)? {
1694                return Ok(false);
1695            }
1696        }
1697
1698        if let Some(ref pattern) = self.filters.speaker_pattern {
1699            if !self.matches_pattern(&event_info.event.name, pattern)? {
1700                return Ok(false);
1701            }
1702        }
1703
1704        if let Some(ref pattern) = self.filters.effect_pattern {
1705            if !self.matches_pattern(&event_info.event.effect, pattern)? {
1706                return Ok(false);
1707            }
1708        }
1709
1710        if let Some(layer) = self.filters.layer {
1711            if let Ok(event_layer) = event_info.event.layer.parse::<u32>() {
1712                if event_layer != layer {
1713                    return Ok(false);
1714                }
1715            } else {
1716                return Ok(false);
1717            }
1718        }
1719
1720        if let Some((start_cs, end_cs)) = self.filters.time_range {
1721            // Parse timing - this is a simplified implementation
1722            // In practice, you'd want proper time parsing from ass_core
1723            if let (Ok(event_start), Ok(event_end)) = (
1724                self.parse_time_to_cs(&event_info.event.start),
1725                self.parse_time_to_cs(&event_info.event.end),
1726            ) {
1727                if event_start < start_cs || event_end > end_cs {
1728                    return Ok(false);
1729                }
1730            } else {
1731                return Ok(false);
1732            }
1733        }
1734
1735        Ok(true)
1736    }
1737
1738    fn matches_pattern(&self, text: &str, pattern: &str) -> Result<bool> {
1739        if self.filters.use_regex {
1740            // For now, just do simple string matching
1741            // In a full implementation, you'd use regex crate
1742            Ok(if self.filters.case_sensitive {
1743                text.contains(pattern)
1744            } else {
1745                text.to_lowercase().contains(&pattern.to_lowercase())
1746            })
1747        } else {
1748            Ok(if self.filters.case_sensitive {
1749                text.contains(pattern)
1750            } else {
1751                text.to_lowercase().contains(&pattern.to_lowercase())
1752            })
1753        }
1754    }
1755
1756    fn parse_time_to_cs(&self, time_str: &str) -> Result<u32> {
1757        // Simplified time parsing - in practice use ass_core utilities
1758        // Format: H:MM:SS.CS
1759        let parts: Vec<&str> = time_str.split(':').collect();
1760        if parts.len() != 3 {
1761            return Err(EditorError::command_failed("Invalid time format"));
1762        }
1763
1764        let hours: u32 = parts[0]
1765            .parse()
1766            .map_err(|_| EditorError::command_failed("Invalid hours"))?;
1767        let minutes: u32 = parts[1]
1768            .parse()
1769            .map_err(|_| EditorError::command_failed("Invalid minutes"))?;
1770
1771        let sec_cs_parts: Vec<&str> = parts[2].split('.').collect();
1772        if sec_cs_parts.len() != 2 {
1773            return Err(EditorError::command_failed("Invalid seconds format"));
1774        }
1775
1776        let seconds: u32 = sec_cs_parts[0]
1777            .parse()
1778            .map_err(|_| EditorError::command_failed("Invalid seconds"))?;
1779        let centiseconds: u32 = sec_cs_parts[1]
1780            .parse()
1781            .map_err(|_| EditorError::command_failed("Invalid centiseconds"))?;
1782
1783        Ok(hours * 360000 + minutes * 6000 + seconds * 100 + centiseconds)
1784    }
1785
1786    fn apply_sort(&self, events: &mut [EventInfo], options: &EventSortOptions) {
1787        events.sort_by(|a, b| {
1788            let primary_cmp = self.compare_by_criteria(a, b, &options.criteria);
1789
1790            match primary_cmp {
1791                Ordering::Equal => {
1792                    if let Some(secondary) = &options.secondary {
1793                        let secondary_cmp = self.compare_by_criteria(a, b, secondary);
1794                        if options.ascending {
1795                            secondary_cmp
1796                        } else {
1797                            secondary_cmp.reverse()
1798                        }
1799                    } else {
1800                        Ordering::Equal
1801                    }
1802                }
1803                other => {
1804                    if options.ascending {
1805                        other
1806                    } else {
1807                        other.reverse()
1808                    }
1809                }
1810            }
1811        });
1812    }
1813
1814    fn compare_by_criteria(
1815        &self,
1816        a: &EventInfo,
1817        b: &EventInfo,
1818        criteria: &EventSortCriteria,
1819    ) -> Ordering {
1820        match criteria {
1821            EventSortCriteria::StartTime => {
1822                let a_time = self.parse_time_to_cs(&a.event.start).unwrap_or(0);
1823                let b_time = self.parse_time_to_cs(&b.event.start).unwrap_or(0);
1824                a_time.cmp(&b_time)
1825            }
1826            EventSortCriteria::EndTime => {
1827                let a_time = self.parse_time_to_cs(&a.event.end).unwrap_or(0);
1828                let b_time = self.parse_time_to_cs(&b.event.end).unwrap_or(0);
1829                a_time.cmp(&b_time)
1830            }
1831            EventSortCriteria::Duration => {
1832                let a_start = self.parse_time_to_cs(&a.event.start).unwrap_or(0);
1833                let a_end = self.parse_time_to_cs(&a.event.end).unwrap_or(0);
1834                let b_start = self.parse_time_to_cs(&b.event.start).unwrap_or(0);
1835                let b_end = self.parse_time_to_cs(&b.event.end).unwrap_or(0);
1836                let a_duration = a_end.saturating_sub(a_start);
1837                let b_duration = b_end.saturating_sub(b_start);
1838                a_duration.cmp(&b_duration)
1839            }
1840            EventSortCriteria::Style => a.event.style.cmp(&b.event.style),
1841            EventSortCriteria::Speaker => a.event.name.cmp(&b.event.name),
1842            EventSortCriteria::Layer => {
1843                let a_layer = a.event.layer.parse::<u32>().unwrap_or(0);
1844                let b_layer = b.event.layer.parse::<u32>().unwrap_or(0);
1845                a_layer.cmp(&b_layer)
1846            }
1847            EventSortCriteria::Index => a.index.cmp(&b.index),
1848            EventSortCriteria::Text => a.event.text.cmp(&b.event.text),
1849        }
1850    }
1851
1852    fn find_line_number(&self, _event: &Event) -> Result<usize> {
1853        // For now, return a placeholder. This would need to be implemented
1854        // by tracking line numbers during parsing or using rope byte-to-line conversion
1855        Ok(1)
1856    }
1857
1858    fn find_event_range(&self, _event: &Event) -> Result<Range> {
1859        // For now, return a placeholder range. This would need to be implemented
1860        // by using the event's span information from the parser
1861        Ok(Range::new(Position::new(0), Position::new(0)))
1862    }
1863}
1864
1865#[cfg(test)]
1866mod tests {
1867    use super::*;
1868    #[cfg(not(feature = "std"))]
1869    use alloc::string::ToString;
1870    #[cfg(not(feature = "std"))]
1871    use alloc::vec;
1872
1873    #[test]
1874    #[cfg(feature = "rope")]
1875    fn test_fluent_insert() {
1876        let mut doc = EditorDocument::new();
1877        doc.at_start().insert_text("Hello, ").unwrap();
1878        doc.at_end().insert_text("World!").unwrap();
1879
1880        assert_eq!(doc.text(), "Hello, World!");
1881    }
1882
1883    #[test]
1884    #[cfg(feature = "rope")]
1885    fn test_fluent_line_operations() {
1886        let mut doc = EditorDocument::from_content("Line 1\nLine 2\nLine 3").unwrap();
1887
1888        // Insert at beginning of line 2
1889        doc.at_line(2).unwrap().insert_text("Start: ").unwrap();
1890        assert_eq!(doc.text(), "Line 1\nStart: Line 2\nLine 3");
1891
1892        // Replace to end of line
1893        doc.at_line(2)
1894            .unwrap()
1895            .replace_to_line_end("New Line 2")
1896            .unwrap();
1897        assert_eq!(doc.text(), "Line 1\nNew Line 2\nLine 3");
1898    }
1899
1900    #[test]
1901    #[cfg(feature = "rope")]
1902    fn test_fluent_selection() {
1903        let mut doc = EditorDocument::from_content("Hello World").unwrap();
1904
1905        let range = Range::new(Position::new(6), Position::new(11));
1906        doc.select(range).replace_with("Rust").unwrap();
1907        assert_eq!(doc.text(), "Hello Rust");
1908
1909        // Test wrapping
1910        let range = Range::new(Position::new(6), Position::new(10));
1911        doc.select(range).wrap_with_tag("{\\b1}", "{\\b0}").unwrap();
1912        assert_eq!(doc.text(), "Hello {\\b1}Rust{\\b0}");
1913    }
1914
1915    #[test]
1916    #[cfg(feature = "rope")]
1917    fn test_position_conversion() {
1918        let doc = EditorDocument::from_content("Line 1\nLine 2\nLine 3").unwrap();
1919
1920        // Test position to line/column
1921        let pos = Position::new(7); // Start of "Line 2"
1922        let (line, col) = doc.position_to_line_col(pos).unwrap();
1923        assert_eq!((line, col), (2, 1));
1924
1925        // Test line/column to position
1926        let pos2 = doc.line_column_to_position(2, 1).unwrap();
1927        assert_eq!(pos2.offset, 7);
1928    }
1929
1930    #[test]
1931    #[cfg(feature = "rope")]
1932    fn test_indent_unindent() {
1933        let mut doc = EditorDocument::from_content("Line 1\nLine 2\nLine 3").unwrap();
1934
1935        // Select all and indent
1936        let range = Range::new(Position::start(), Position::new(doc.len()));
1937        doc.select(range).indent(2).unwrap();
1938        assert_eq!(doc.text(), "  Line 1\n  Line 2\n  Line 3");
1939
1940        // Unindent
1941        let range = Range::new(Position::start(), Position::new(doc.len()));
1942        doc.select(range).unindent(2).unwrap();
1943        assert_eq!(doc.text(), "Line 1\nLine 2\nLine 3");
1944    }
1945
1946    #[test]
1947    fn test_fluent_style_operations() {
1948        const TEST_CONTENT: &str = r#"[V4+ Styles]
1949Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
1950Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
1951
1952[Events]
1953Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
1954Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,Hello world!
1955"#;
1956
1957        let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
1958
1959        // Test create style
1960        doc.styles()
1961            .create(
1962                "NewStyle",
1963                StyleBuilder::new()
1964                    .font("Comic Sans MS")
1965                    .size(24)
1966                    .bold(true),
1967            )
1968            .unwrap();
1969
1970        assert!(doc.text().contains("Style: NewStyle"));
1971        assert!(doc.text().contains("Comic Sans MS"));
1972
1973        // Test edit style
1974        doc.styles()
1975            .edit("Default")
1976            .font("Helvetica")
1977            .size(18)
1978            .bold(true)
1979            .apply()
1980            .unwrap();
1981
1982        assert!(doc.text().contains("Helvetica"));
1983        assert!(doc.text().contains("18"));
1984
1985        // Test clone style
1986        doc.styles().clone("Default", "DefaultCopy").unwrap();
1987
1988        assert!(doc.text().contains("Style: DefaultCopy"));
1989
1990        // Test apply style to events
1991        doc.styles().apply("Default", "NewStyle").apply().unwrap();
1992
1993        // The dialogue event should now use NewStyle
1994        let text = doc.text();
1995        let events_section = text.split("[Events]").nth(1).unwrap();
1996        assert!(events_section.contains("NewStyle"));
1997    }
1998
1999    #[test]
2000    fn test_fluent_style_delete() {
2001        const TEST_CONTENT: &str = r#"[V4+ Styles]
2002Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
2003Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
2004Style: ToDelete,Times,22,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
2005"#;
2006
2007        let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2008
2009        // Verify style exists
2010        assert!(doc.text().contains("Style: ToDelete"));
2011
2012        // Delete the style
2013        doc.styles().delete("ToDelete").unwrap();
2014
2015        // Verify style is gone
2016        assert!(!doc.text().contains("Style: ToDelete"));
2017        assert!(doc.text().contains("Style: Default")); // Other styles should remain
2018    }
2019
2020    #[test]
2021    fn test_fluent_style_apply_with_filter() {
2022        const TEST_CONTENT: &str = r#"[V4+ Styles]
2023Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
2024Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
2025Style: FilterStyle,Times,22,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
2026
2027[Events]
2028Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2029Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,Hello world!
2030Dialogue: 0,0:00:06.00,0:00:10.00,Default,Speaker,0,0,0,,Goodbye world!
2031"#;
2032
2033        let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2034
2035        // Apply style only to events containing "Hello"
2036        doc.styles()
2037            .apply("Default", "FilterStyle")
2038            .with_filter("Hello")
2039            .apply()
2040            .unwrap();
2041
2042        let content = doc.text();
2043        let lines: Vec<&str> = content.lines().collect();
2044
2045        // Find the dialogue lines
2046        let hello_line = lines.iter().find(|line| line.contains("Hello")).unwrap();
2047        let goodbye_line = lines.iter().find(|line| line.contains("Goodbye")).unwrap();
2048
2049        // Only the "Hello" line should use FilterStyle
2050        assert!(hello_line.contains("FilterStyle"));
2051        assert!(goodbye_line.contains("Default")); // Should still use Default
2052    }
2053
2054    #[test]
2055    fn test_fluent_event_operations() {
2056        const TEST_CONTENT: &str = r#"[V4+ Styles]
2057Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
2058Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
2059
2060[Events]
2061Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2062Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First event
2063Dialogue: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second event
2064Comment: 0,0:00:10.00,0:00:15.00,Default,Speaker,0,0,0,,Third event
2065"#;
2066
2067        let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2068
2069        // Test split event
2070        doc.events().split(0, "0:00:03.00").unwrap();
2071
2072        // Should now have 4 events (first split into 2)
2073        let events_count = doc
2074            .text()
2075            .lines()
2076            .filter(|line| line.starts_with("Dialogue:") || line.starts_with("Comment:"))
2077            .count();
2078        assert_eq!(events_count, 4);
2079        assert!(doc.text().contains("0:00:01.00,0:00:03.00"));
2080        assert!(doc.text().contains("0:00:03.00,0:00:05.00"));
2081    }
2082
2083    #[test]
2084    fn test_fluent_event_merge() {
2085        const TEST_CONTENT: &str = r#"[Events]
2086Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2087Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First event
2088Dialogue: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second event
2089Comment: 0,0:00:10.00,0:00:15.00,Default,Speaker,0,0,0,,Third event
2090"#;
2091
2092        let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2093
2094        // Test merge events with custom separator
2095        doc.events()
2096            .merge(0, 1)
2097            .with_separator(" | ")
2098            .apply()
2099            .unwrap();
2100
2101        // Should now have 2 events (first two merged)
2102        let events_count = doc
2103            .text()
2104            .lines()
2105            .filter(|line| line.starts_with("Dialogue:") || line.starts_with("Comment:"))
2106            .count();
2107        assert_eq!(events_count, 2);
2108        assert!(doc.text().contains("First event | Second event"));
2109        assert!(doc.text().contains("0:00:01.00,0:00:10.00")); // Start of first, end of second
2110    }
2111
2112    #[test]
2113    fn test_fluent_event_timing() {
2114        const TEST_CONTENT: &str = r#"[Events]
2115Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2116Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First event
2117Dialogue: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second event
2118"#;
2119
2120        let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2121
2122        // Test shifting all events by 2 seconds (200 centiseconds)
2123        doc.events().timing().shift(200).unwrap();
2124
2125        assert!(doc.text().contains("0:00:03.00,0:00:07.00")); // First event shifted
2126        assert!(doc.text().contains("0:00:07.00,0:00:12.00")); // Second event shifted
2127    }
2128
2129    #[test]
2130    fn test_fluent_event_timing_specific() {
2131        const TEST_CONTENT: &str = r#"[Events]
2132Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2133Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First event
2134Dialogue: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second event
2135"#;
2136
2137        let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2138
2139        // Test adjusting only first event
2140        doc.events()
2141            .timing()
2142            .event(0)
2143            .shift_start(100) // +1 second to start only
2144            .unwrap();
2145
2146        // Only first event should change
2147        assert!(doc.text().contains("0:00:02.00,0:00:05.00")); // First event start shifted
2148        assert!(doc.text().contains("0:00:05.00,0:00:10.00")); // Second event unchanged
2149    }
2150
2151    #[test]
2152    fn test_fluent_event_toggle() {
2153        const TEST_CONTENT: &str = r#"[Events]
2154Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2155Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First event
2156Comment: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second event
2157"#;
2158
2159        let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2160
2161        // Test toggling first event type
2162        doc.events().toggle_type().event(0).apply().unwrap();
2163
2164        let text = doc.text();
2165        let lines: Vec<&str> = text.lines().collect();
2166        let event_lines: Vec<&str> = lines
2167            .iter()
2168            .filter(|line| line.starts_with("Dialogue:") || line.starts_with("Comment:"))
2169            .copied()
2170            .collect();
2171
2172        // First event should now be Comment (was Dialogue)
2173        assert_eq!(event_lines.len(), 2);
2174        assert!(event_lines[0].starts_with("Comment:"));
2175        assert!(event_lines[1].starts_with("Comment:")); // Second unchanged
2176    }
2177
2178    #[test]
2179    fn test_fluent_event_effects() {
2180        const TEST_CONTENT: &str = r#"[Events]
2181Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2182Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First event
2183Dialogue: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second event
2184"#;
2185
2186        let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2187
2188        // Test setting effects
2189        doc.events()
2190            .effects()
2191            .events(vec![0, 1])
2192            .set("Fade(255,0)")
2193            .unwrap();
2194
2195        // Both events should have the effect
2196        let text = doc.text();
2197        let event_lines: Vec<&str> = text
2198            .lines()
2199            .filter(|line| line.starts_with("Dialogue:") || line.starts_with("Comment:"))
2200            .collect();
2201
2202        assert!(event_lines[0].contains("Fade(255,0)"));
2203        assert!(event_lines[1].contains("Fade(255,0)"));
2204    }
2205
2206    #[test]
2207    fn test_fluent_event_effects_chaining() {
2208        const TEST_CONTENT: &str = r#"[Events]
2209Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2210Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First event
2211"#;
2212
2213        let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2214
2215        // Test effect chaining: set, then append
2216        doc.events().effects().event(0).set("Fade(255,0)").unwrap();
2217
2218        doc.events()
2219            .effects()
2220            .event(0)
2221            .append("Move(100,200)")
2222            .unwrap();
2223
2224        // Should have both effects
2225        assert!(doc.text().contains("Fade(255,0) Move(100,200)"));
2226
2227        // Test clearing
2228        doc.events().effects().event(0).clear().unwrap();
2229
2230        // Effect field should be empty
2231        let text = doc.text();
2232        let event_line = text
2233            .lines()
2234            .find(|line| line.starts_with("Dialogue:"))
2235            .unwrap();
2236        let parts: Vec<&str> = event_line.split(',').collect();
2237        assert_eq!(parts[8].trim(), ""); // Effect field should be empty
2238    }
2239
2240    #[test]
2241    fn test_fluent_event_complex_workflow() {
2242        const TEST_CONTENT: &str = r#"[Events]
2243Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2244Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,Long event that needs splitting
2245Dialogue: 0,0:00:05.00,0:00:07.00,Default,Speaker,0,0,0,,Short event
2246Comment: 0,0:00:10.00,0:00:15.00,Default,Speaker,0,0,0,,Comment to toggle
2247"#;
2248
2249        let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2250
2251        // Complex workflow: split, adjust timing, toggle type, add effects
2252
2253        // 1. Split the first event
2254        doc.events().split(0, "0:00:03.00").unwrap();
2255
2256        // Now we have 4 events: split first, original second, original comment
2257
2258        // 2. Shift all events forward by 1 second
2259        doc.events()
2260            .timing()
2261            .shift(100) // 1 second
2262            .unwrap();
2263
2264        // 3. Toggle the comment (now at index 3) to dialogue
2265        doc.events().toggle_type().event(3).apply().unwrap();
2266
2267        // 4. Add fade effect to all events
2268        doc.events().effects().set("Fade(255,0)").unwrap();
2269
2270        let content = doc.text();
2271
2272        // Verify results
2273        let event_lines: Vec<&str> = content
2274            .lines()
2275            .filter(|line| line.starts_with("Dialogue:") || line.starts_with("Comment:"))
2276            .collect();
2277
2278        // Should have 4 events, all Dialogue (comment was toggled)
2279        assert_eq!(event_lines.len(), 4);
2280        assert!(event_lines.iter().all(|line| line.starts_with("Dialogue:")));
2281
2282        // All should have timing shifted by 1 second
2283        assert!(content.contains("0:00:02.00,0:00:04.00")); // First part of split
2284        assert!(content.contains("0:00:04.00,0:00:06.00")); // Second part of split
2285        assert!(content.contains("0:00:06.00,0:00:08.00")); // Original second event
2286        assert!(content.contains("0:00:11.00,0:00:16.00")); // Original comment (now dialogue)
2287
2288        // All should have fade effect
2289        assert!(event_lines.iter().all(|line| line.contains("Fade(255,0)")));
2290    }
2291
2292    #[test]
2293    fn tag_operations() {
2294        let mut doc = EditorDocument::from_content("Hello World").unwrap();
2295
2296        // Test tag insertion
2297        doc.tags().at(Position::new(5)).insert("\\b1").unwrap();
2298        assert_eq!(doc.text(), "Hello{\\b1} World");
2299
2300        // Test raw tag insertion - need to account for the inserted tag
2301        doc.tags().at(Position::new(12)).insert_raw("\\i1").unwrap();
2302        assert_eq!(doc.text(), "Hello{\\b1} W\\i1orld");
2303    }
2304
2305    #[test]
2306    fn tag_removal() {
2307        let mut doc =
2308            EditorDocument::from_content("Hello {\\b1\\i1}World{\\c&H00FF00&} test").unwrap();
2309        let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2310
2311        // Remove specific pattern
2312        doc.tags().in_range(range).remove_pattern("\\b").unwrap();
2313        assert_eq!(doc.text(), "Hello {\\i1}World{\\c&H00FF00&} test");
2314
2315        // Remove all tags
2316        let full_range = Range::new(Position::new(0), Position::new(doc.text().len()));
2317        doc.tags().in_range(full_range).remove_all().unwrap();
2318        assert_eq!(doc.text(), "Hello World test");
2319    }
2320
2321    #[test]
2322    fn tag_replacement() {
2323        let mut doc = EditorDocument::from_content("Hello {\\b1}World{\\b1} test").unwrap();
2324        let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2325
2326        // Replace all bold tags with italic
2327        doc.tags()
2328            .in_range(range)
2329            .replace_all("\\b1", "\\i1")
2330            .unwrap();
2331        assert_eq!(doc.text(), "Hello {\\i1}World{\\i1} test");
2332    }
2333
2334    #[test]
2335    fn tag_wrapping() {
2336        let mut doc = EditorDocument::from_content("Hello World").unwrap();
2337        let range = Range::new(Position::new(6), Position::new(11));
2338
2339        // Wrap with bold tags
2340        doc.tags().in_range(range).wrap("\\b1").unwrap();
2341        assert_eq!(doc.text(), "Hello {\\b1}World{\\b0}");
2342
2343        // Test explicit closing tag
2344        let mut doc2 = EditorDocument::from_content("Hello World").unwrap();
2345        let range2 = Range::new(Position::new(6), Position::new(11));
2346        doc2.tags()
2347            .in_range(range2)
2348            .wrap_with("\\c&HFF0000&", "\\c")
2349            .unwrap();
2350        assert_eq!(doc2.text(), "Hello {\\c&HFF0000&}World{\\c}");
2351    }
2352
2353    #[test]
2354    fn tag_parsing() {
2355        let mut doc =
2356            EditorDocument::from_content("Hello {\\b1\\c&H00FF00&\\pos(100,200)}World").unwrap();
2357        let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2358
2359        let parsed_tags = doc.tags().in_range(range).parse().unwrap();
2360
2361        assert_eq!(parsed_tags.len(), 3);
2362        assert_eq!(parsed_tags[0].tag, "\\b1");
2363        assert_eq!(parsed_tags[1].tag, "\\c&H00FF00&");
2364        assert_eq!(parsed_tags[2].tag, "\\pos");
2365        assert_eq!(parsed_tags[2].parameters.len(), 2);
2366        assert_eq!(parsed_tags[2].parameters[0], "100");
2367        assert_eq!(parsed_tags[2].parameters[1], "200");
2368    }
2369
2370    #[test]
2371    fn karaoke_generate() {
2372        let mut doc = EditorDocument::from_content("Hello World Test").unwrap();
2373        let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2374
2375        // Test basic karaoke generation with manual syllables to preserve text
2376        doc.karaoke()
2377            .in_range(range)
2378            .generate(50)
2379            .manual_syllables()
2380            .execute()
2381            .unwrap();
2382
2383        let text = doc.text();
2384        assert!(text.contains("\\k50"));
2385        // With manual syllables, the entire text should be preserved
2386        assert!(text.contains("Hello World Test"));
2387    }
2388
2389    #[test]
2390    fn karaoke_generate_with_types() {
2391        let mut doc = EditorDocument::from_content("Test Text").unwrap();
2392        let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2393
2394        // Test with fill karaoke
2395        doc.karaoke()
2396            .in_range(range)
2397            .generate(40)
2398            .karaoke_type(KaraokeType::Fill)
2399            .execute()
2400            .unwrap();
2401
2402        assert!(doc.text().contains("\\kf40"));
2403
2404        // Test with outline karaoke
2405        let mut doc2 = EditorDocument::from_content("Test Text").unwrap();
2406        let range2 = Range::new(Position::new(0), Position::new(doc2.text().len()));
2407
2408        doc2.karaoke()
2409            .in_range(range2)
2410            .generate(30)
2411            .karaoke_type(KaraokeType::Outline)
2412            .execute()
2413            .unwrap();
2414
2415        assert!(doc2.text().contains("\\ko30"));
2416    }
2417
2418    #[test]
2419    fn karaoke_generate_manual_syllables() {
2420        let mut doc = EditorDocument::from_content("Syllable Test").unwrap();
2421        let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2422
2423        // Test with manual syllable detection disabled
2424        doc.karaoke()
2425            .in_range(range)
2426            .generate(60)
2427            .manual_syllables()
2428            .execute()
2429            .unwrap();
2430
2431        let text = doc.text();
2432        assert!(text.contains("\\k60"));
2433        assert!(text.contains("Syllable Test"));
2434    }
2435
2436    #[test]
2437    fn karaoke_split() {
2438        let mut doc = EditorDocument::from_content("{\\k100}Hello World").unwrap();
2439        let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2440
2441        // Split at position 5 (between "Hello" and " World")
2442        doc.karaoke()
2443            .in_range(range)
2444            .split(vec![5])
2445            .duration(25)
2446            .execute()
2447            .unwrap();
2448
2449        let text = doc.text();
2450        assert!(text.contains("\\k25"));
2451    }
2452
2453    #[test]
2454    fn karaoke_adjust_scale() {
2455        let mut doc = EditorDocument::from_content("{\\k50}Hello {\\k30}World").unwrap();
2456        let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2457
2458        // Scale timing by 2.0
2459        doc.karaoke().in_range(range).adjust().scale(2.0).unwrap();
2460
2461        let text = doc.text();
2462        assert!(text.contains("\\k100")); // 50 * 2.0
2463        assert!(text.contains("\\k60")); // 30 * 2.0
2464    }
2465
2466    #[test]
2467    fn karaoke_adjust_offset() {
2468        let mut doc = EditorDocument::from_content("{\\k50}Hello {\\k30}World").unwrap();
2469        let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2470
2471        // Add 20 centiseconds to all timings
2472        doc.karaoke().in_range(range).adjust().offset(20).unwrap();
2473
2474        let text = doc.text();
2475        assert!(text.contains("\\k70")); // 50 + 20
2476        assert!(text.contains("\\k50")); // 30 + 20
2477    }
2478
2479    #[test]
2480    fn karaoke_adjust_set_all() {
2481        let mut doc = EditorDocument::from_content("{\\k50}Hello {\\k30}World").unwrap();
2482        let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2483
2484        // Set all timings to 45 centiseconds
2485        doc.karaoke().in_range(range).adjust().set_all(45).unwrap();
2486
2487        let text = doc.text();
2488        assert!(text.contains("\\k45"));
2489        // Should contain exactly two instances of \\k45
2490        assert_eq!(text.matches("\\k45").count(), 2);
2491    }
2492
2493    #[test]
2494    fn karaoke_adjust_custom() {
2495        let mut doc = EditorDocument::from_content("{\\k50}Hello {\\k30}World").unwrap();
2496        let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2497
2498        // Apply custom timings: 80cs for first, 40cs for second
2499        doc.karaoke()
2500            .in_range(range)
2501            .adjust()
2502            .custom(vec![80, 40])
2503            .unwrap();
2504
2505        let text = doc.text();
2506        assert!(text.contains("\\k80"));
2507        assert!(text.contains("\\k40"));
2508    }
2509
2510    #[test]
2511    fn karaoke_apply_equal() {
2512        let mut doc = EditorDocument::from_content("Hello World Test").unwrap();
2513        let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2514
2515        // Apply equal timing of 35cs with fill karaoke
2516        doc.karaoke()
2517            .in_range(range)
2518            .apply()
2519            .equal(35, KaraokeType::Fill)
2520            .unwrap();
2521
2522        let text = doc.text();
2523        assert!(text.contains("\\kf35"));
2524        assert!(text.contains("Hello"));
2525        assert!(text.contains("World"));
2526        assert!(text.contains("Test"));
2527    }
2528
2529    #[test]
2530    fn karaoke_apply_beat() {
2531        let mut doc = EditorDocument::from_content("Hello World").unwrap();
2532        let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2533
2534        // Apply beat-based timing: 120 BPM, 0.5 beats per syllable
2535        // Expected duration: (60/120) * 0.5 * 100 = 25 centiseconds
2536        doc.karaoke()
2537            .in_range(range)
2538            .apply()
2539            .beat(120, 0.5, KaraokeType::Standard)
2540            .unwrap();
2541
2542        let text = doc.text();
2543        assert!(text.contains("\\k25"));
2544    }
2545
2546    #[test]
2547    fn karaoke_apply_pattern() {
2548        let mut doc = EditorDocument::from_content("Hello World Test").unwrap();
2549        let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2550
2551        // Apply pattern-based timing: 40cs, 60cs, repeating
2552        doc.karaoke()
2553            .in_range(range)
2554            .apply()
2555            .pattern(vec![40, 60], KaraokeType::Outline)
2556            .unwrap();
2557
2558        let text = doc.text();
2559        assert!(text.contains("\\ko40"));
2560        assert!(text.contains("\\ko60"));
2561    }
2562
2563    #[test]
2564    fn karaoke_apply_import() {
2565        let mut doc = EditorDocument::from_content("Source text for import").unwrap();
2566        let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2567
2568        // Apply import timing (simplified test - would import from event 0)
2569        doc.karaoke()
2570            .in_range(range)
2571            .apply()
2572            .import_from(0)
2573            .unwrap();
2574
2575        // Since import is simplified and returns original text, verify no crash
2576        assert!(doc.text().contains("Source text for import"));
2577    }
2578
2579    #[test]
2580    fn karaoke_complex_workflow() {
2581        let mut doc =
2582            EditorDocument::from_content("Complex karaoke test with multiple words").unwrap();
2583        let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2584
2585        // 1. Generate initial karaoke with standard timing and manual syllables
2586        doc.karaoke()
2587            .in_range(range)
2588            .generate(50)
2589            .karaoke_type(KaraokeType::Standard)
2590            .manual_syllables()
2591            .execute()
2592            .unwrap();
2593
2594        let mut text = doc.text();
2595        assert!(text.contains("\\k50"));
2596
2597        // 2. Scale the timing by 1.5
2598        let current_range = Range::new(Position::new(0), Position::new(doc.text().len()));
2599        doc.karaoke()
2600            .in_range(current_range)
2601            .adjust()
2602            .scale(1.5)
2603            .unwrap();
2604
2605        text = doc.text();
2606        assert!(text.contains("\\k75")); // 50 * 1.5
2607
2608        // 3. Add 10cs offset
2609        let final_range = Range::new(Position::new(0), Position::new(doc.text().len()));
2610        doc.karaoke()
2611            .in_range(final_range)
2612            .adjust()
2613            .offset(10)
2614            .unwrap();
2615
2616        text = doc.text();
2617        assert!(text.contains("\\k85")); // 75 + 10
2618
2619        // With manual syllables, the entire original text is preserved
2620        assert!(text.contains("Complex karaoke test with multiple words"));
2621    }
2622
2623    #[test]
2624    fn karaoke_different_types_workflow() {
2625        // Test all karaoke types in sequence
2626        let test_text = "Test karaoke types";
2627
2628        // Standard karaoke
2629        let mut doc1 = EditorDocument::from_content(test_text).unwrap();
2630        let range1 = Range::new(Position::new(0), Position::new(doc1.text().len()));
2631        doc1.karaoke()
2632            .in_range(range1)
2633            .generate(30)
2634            .karaoke_type(KaraokeType::Standard)
2635            .execute()
2636            .unwrap();
2637        assert!(doc1.text().contains("\\k30"));
2638
2639        // Fill karaoke
2640        let mut doc2 = EditorDocument::from_content(test_text).unwrap();
2641        let range2 = Range::new(Position::new(0), Position::new(doc2.text().len()));
2642        doc2.karaoke()
2643            .in_range(range2)
2644            .generate(40)
2645            .karaoke_type(KaraokeType::Fill)
2646            .execute()
2647            .unwrap();
2648        assert!(doc2.text().contains("\\kf40"));
2649
2650        // Outline karaoke
2651        let mut doc3 = EditorDocument::from_content(test_text).unwrap();
2652        let range3 = Range::new(Position::new(0), Position::new(doc3.text().len()));
2653        doc3.karaoke()
2654            .in_range(range3)
2655            .generate(50)
2656            .karaoke_type(KaraokeType::Outline)
2657            .execute()
2658            .unwrap();
2659        assert!(doc3.text().contains("\\ko50"));
2660
2661        // Transition karaoke
2662        let mut doc4 = EditorDocument::from_content(test_text).unwrap();
2663        let range4 = Range::new(Position::new(0), Position::new(doc4.text().len()));
2664        doc4.karaoke()
2665            .in_range(range4)
2666            .generate(60)
2667            .karaoke_type(KaraokeType::Transition)
2668            .execute()
2669            .unwrap();
2670        assert!(doc4.text().contains("\\kt60"));
2671    }
2672
2673    #[test]
2674    fn karaoke_error_conditions() {
2675        // Test with text containing override blocks (should fail)
2676        let mut doc = EditorDocument::from_content("Hello {\\b1}World{\\b0}").unwrap();
2677        let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2678
2679        let result = doc.karaoke().in_range(range).generate(50).execute();
2680
2681        // Should fail because text contains override blocks
2682        assert!(result.is_err());
2683    }
2684
2685    #[test]
2686    fn karaoke_edge_cases() {
2687        // Test with empty text
2688        let mut doc = EditorDocument::from_content("").unwrap();
2689        let range = Range::new(Position::new(0), Position::new(0));
2690
2691        let result = doc.karaoke().in_range(range).generate(50).execute();
2692
2693        // Should handle empty text gracefully
2694        assert!(result.is_ok());
2695
2696        // Test with single character
2697        let mut doc2 = EditorDocument::from_content("A").unwrap();
2698        let range2 = Range::new(Position::new(0), Position::new(1));
2699
2700        doc2.karaoke()
2701            .in_range(range2)
2702            .generate(25)
2703            .execute()
2704            .unwrap();
2705
2706        assert!(doc2.text().contains("\\k25"));
2707        assert!(doc2.text().contains("A"));
2708    }
2709
2710    #[test]
2711    fn test_new_event_api_direct_access() {
2712        const TEST_CONTENT: &str = r#"[V4+ Styles]
2713Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
2714Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
2715
2716[Events]
2717Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2718Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First event
2719Dialogue: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second event
2720Comment: 0,0:00:10.00,0:00:15.00,Default,Speaker,0,0,0,,Third event
2721"#;
2722
2723        let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2724
2725        // Test direct event access
2726        let event_info = doc.events().get(0).unwrap();
2727        assert!(event_info.is_some());
2728        let info = event_info.unwrap();
2729        assert_eq!(info.index, 0);
2730        assert_eq!(info.event.text, "First event");
2731        assert_eq!(info.event.event_type, EventType::Dialogue);
2732
2733        // Test event count
2734        let count = doc.events().count().unwrap();
2735        assert_eq!(count, 3);
2736
2737        // Test fluent accessor
2738        let text = doc.events().event(1).text().unwrap();
2739        assert_eq!(text, Some("Second event".to_string()));
2740
2741        let style = doc.events().event(1).style().unwrap();
2742        assert_eq!(style, Some("Default".to_string()));
2743
2744        let exists = doc.events().event(5).exists().unwrap();
2745        assert!(!exists);
2746    }
2747
2748    #[test]
2749    fn test_new_event_api_filtering() {
2750        const TEST_CONTENT: &str = r#"[V4+ Styles]
2751Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
2752Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
2753
2754[Events]
2755Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2756Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First dialogue
2757Dialogue: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second dialogue
2758Comment: 0,0:00:10.00,0:00:15.00,Default,Speaker,0,0,0,,First comment
2759Comment: 0,0:00:15.00,0:00:20.00,Default,Speaker,0,0,0,,Second comment
2760"#;
2761
2762        let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2763
2764        // Test filtering by type
2765        let dialogues = doc.events().dialogues().execute().unwrap();
2766        assert_eq!(dialogues.len(), 2);
2767        assert!(dialogues
2768            .iter()
2769            .all(|info| info.event.event_type == EventType::Dialogue));
2770
2771        let comments = doc.events().comments().execute().unwrap();
2772        assert_eq!(comments.len(), 2);
2773        assert!(comments
2774            .iter()
2775            .all(|info| info.event.event_type == EventType::Comment));
2776
2777        // Test text filtering
2778        let with_first = doc
2779            .events()
2780            .query()
2781            .filter_by_text("First")
2782            .execute()
2783            .unwrap();
2784        assert_eq!(with_first.len(), 2);
2785        assert!(with_first[0].event.text.contains("First"));
2786        assert!(with_first[1].event.text.contains("First"));
2787
2788        // Test case insensitive filtering
2789        let with_first_insensitive = doc
2790            .events()
2791            .query()
2792            .filter_by_text("first")
2793            .case_sensitive(false)
2794            .execute()
2795            .unwrap();
2796        assert_eq!(with_first_insensitive.len(), 2);
2797    }
2798
2799    #[test]
2800    fn test_new_event_api_sorting() {
2801        const TEST_CONTENT: &str = r#"[V4+ Styles]
2802Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
2803Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
2804
2805[Events]
2806Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2807Dialogue: 0,0:00:10.00,0:00:15.00,Default,Speaker,0,0,0,,Third by time
2808Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First by time
2809Dialogue: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second by time
2810"#;
2811
2812        let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2813
2814        // Test sorting by time (should reorder events)
2815        let by_time = doc.events().by_time().execute().unwrap();
2816        assert_eq!(by_time.len(), 3);
2817        assert_eq!(by_time[0].event.text, "First by time");
2818        assert_eq!(by_time[1].event.text, "Second by time");
2819        assert_eq!(by_time[2].event.text, "Third by time");
2820
2821        // Test original order
2822        let in_order = doc.events().in_order().execute().unwrap();
2823        assert_eq!(in_order.len(), 3);
2824        assert_eq!(in_order[0].event.text, "Third by time");
2825        assert_eq!(in_order[1].event.text, "First by time");
2826        assert_eq!(in_order[2].event.text, "Second by time");
2827
2828        // Test descending sort
2829        let by_time_desc = doc
2830            .events()
2831            .query()
2832            .sort_by_time()
2833            .descending()
2834            .execute()
2835            .unwrap();
2836        assert_eq!(by_time_desc[0].event.text, "Third by time");
2837        assert_eq!(by_time_desc[1].event.text, "Second by time");
2838        assert_eq!(by_time_desc[2].event.text, "First by time");
2839    }
2840
2841    #[test]
2842    fn test_new_event_api_combined_operations() {
2843        const TEST_CONTENT: &str = r#"[V4+ Styles]
2844Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
2845Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
2846
2847[Events]
2848Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2849Dialogue: 0,0:00:10.00,0:00:15.00,Default,Speaker,0,0,0,,Important dialogue
2850Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,Another dialogue
2851Comment: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Important comment
2852Dialogue: 0,0:00:15.00,0:00:20.00,Default,Speaker,0,0,0,,Final dialogue
2853"#;
2854
2855        let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2856
2857        // Test combined filtering and sorting with limit
2858        let important_dialogues = doc
2859            .events()
2860            .query()
2861            .filter_by_type(EventType::Dialogue)
2862            .filter_by_text("Important")
2863            .sort_by_time()
2864            .limit(1)
2865            .execute()
2866            .unwrap();
2867
2868        assert_eq!(important_dialogues.len(), 1);
2869        assert_eq!(important_dialogues[0].event.text, "Important dialogue");
2870        assert_eq!(important_dialogues[0].event.event_type, EventType::Dialogue);
2871
2872        // Test getting indices only
2873        let dialogue_indices = doc.events().dialogues().sort_by_time().indices().unwrap();
2874
2875        assert_eq!(dialogue_indices.len(), 3);
2876        // Should be indices in time order: 1, 0, 3 (based on start times)
2877        assert_eq!(dialogue_indices, vec![1, 0, 3]);
2878
2879        // Test count
2880        let dialogue_count = doc.events().dialogues().count().unwrap();
2881        assert_eq!(dialogue_count, 3);
2882
2883        // Test first
2884        let first_dialogue = doc.events().dialogues().sort_by_time().first().unwrap();
2885
2886        assert!(first_dialogue.is_some());
2887        let first = first_dialogue.unwrap();
2888        assert_eq!(first.event.text, "Another dialogue");
2889    }
2890
2891    #[test]
2892    fn karaoke_chaining_operations() {
2893        let mut doc = EditorDocument::from_content("Chain test").unwrap();
2894
2895        // Test that karaoke operations can be chained with other fluent operations
2896        doc.at_pos(Position::new(0))
2897            .insert_text("Prefix: ")
2898            .unwrap();
2899
2900        assert_eq!(doc.text(), "Prefix: Chain test");
2901
2902        // Now apply karaoke to the appended part with manual syllables
2903        let karaoke_range = Range::new(Position::new(8), Position::new(doc.text().len()));
2904        doc.karaoke()
2905            .in_range(karaoke_range)
2906            .generate(45)
2907            .manual_syllables()
2908            .execute()
2909            .unwrap();
2910
2911        let text = doc.text();
2912        assert!(text.starts_with("Prefix: "));
2913        assert!(text.contains("\\k45"));
2914        // With manual syllables, the original appended text is preserved
2915        assert!(text.contains("Chain test"));
2916    }
2917}