use crate::events::{Consumer, Event};
use crate::parsers::selectors::parser::parse_selector;
use crate::parsers::selectors::{
    matches_full, SimpleSelectorGroup, StackFrame as SelectorStackFrame, StackFrameCollector,
};
use std::collections::HashMap;

#[derive(Debug, Clone)]
pub struct Action {
    pub id: String,
    pub name: String,
    pub selectors: Vec<String>,
    pub description: String,
    pub handler: ActionHandler,
}

#[derive(Debug, Clone)]
pub enum ActionHandler {
    Log(String),
    FixTypos(String),
    Transform(String),
    Validate(String),
    Custom(String),
}

pub struct ActionConsumer {
    actions: Vec<Action>,
    parsed_selectors: HashMap<String, Vec<SimpleSelectorGroup>>,
    collector: StackFrameCollector,
    target_line: Option<usize>,
    matching_actions: Vec<Action>,
}

impl ActionConsumer {
    pub fn new(actions: Vec<Action>) -> Result<Self, String> {
        let mut parsed_selectors = HashMap::new();
        let mut all_selectors = Vec::new();

        for action in &actions {
            let mut selectors = Vec::new();
            for selector_str in &action.selectors {
                let selector = parse_selector(selector_str)
                    .map_err(|e| format!("Failed to parse selector '{selector_str}': {e:?}"))?;
                selectors.push(selector);

                // Collect for StackFrameCollector
                if !all_selectors.contains(selector_str) {
                    all_selectors.push(selector_str.clone());
                }
            }
            parsed_selectors.insert(action.id.clone(), selectors);
        }

        let collector = StackFrameCollector::new(all_selectors);

        Ok(Self {
            actions,
            parsed_selectors,
            collector,
            target_line: None,
            matching_actions: Vec::new(),
        })
    }

    pub fn with_target_line(mut self, line: usize) -> Self {
        self.target_line = Some(line);
        self
    }

    pub fn get_matching_actions(&self) -> Vec<Action> {
        self.matching_actions.clone()
    }

    /// Find actions that match the current stack state or any prefix of it
    pub fn find_matching_actions(&mut self) {
        // Clear previous matches to avoid accumulation
        self.matching_actions.clear();

        if self.target_line.is_some() {
            // Convert collector's stack to SelectorStackFrame format for matching
            let current_stack: Vec<SelectorStackFrame> = self
                .collector
                .current_stack()
                .iter()
                .map(|frame| SelectorStackFrame {
                    metaline: frame.metaline.clone(),
                    child_index: frame.child_index,
                })
                .collect();

            // Check if any actions match the current stack or any prefix of it
            for action in &self.actions {
                if let Some(selector_groups) = self.parsed_selectors.get(&action.id) {
                    let mut action_matched = false;
                    for selector_group in selector_groups {
                        // Check full stack and all prefixes
                        for prefix_len in 1..=current_stack.len() {
                            let stack_prefix = &current_stack[..prefix_len];
                            if matches_full(stack_prefix, selector_group) {
                                self.matching_actions.push(action.clone());
                                action_matched = true;
                                break;
                            }
                        }
                        if action_matched {
                            break;
                        }
                    }
                }
            }
        }
    }
}

impl Consumer for ActionConsumer {
    fn handle(&mut self, event: Event) {
        // Update the collector with all events
        self.collector.handle_event(&event);

        // Check for matches on BlockStarted events if we have a target line
        if let Event::BlockStarted { meta_line } = &event {
            if let Some(target) = self.target_line {
                // Convert target line from 1-based to 0-based
                let target_line_0based = target.saturating_sub(1);
                let current_line = meta_line.line_index.unwrap_or(0);

                // Check if this block contains the target line
                if current_line <= target_line_0based {
                    self.find_matching_actions();
                }
            }
        }
    }
}

pub fn get_sample_actions() -> Vec<Action> {
    vec![
        Action {
            id: "log_paragraph".to_string(),
            name: "Log Paragraph".to_string(),
            selectors: vec!["paragraph".to_string()],
            description: "Log paragraph content for analysis".to_string(),
            handler: ActionHandler::Log("paragraph_content.log".to_string()),
        },
        Action {
            id: "fix_typos".to_string(),
            name: "Fix Typos".to_string(),
            selectors: vec!["paragraph".to_string(), "quote".to_string()],
            description: "Fix common typos in paragraph and quote content".to_string(),
            handler: ActionHandler::FixTypos("typo_fixer.log".to_string()),
        },
        Action {
            id: "transform_quote".to_string(),
            name: "Transform Quote".to_string(),
            selectors: vec!["quote".to_string(), "blockquote".to_string()],
            description: "Apply formatting to quotes".to_string(),
            handler: ActionHandler::Transform("add_citation".to_string()),
        },
        Action {
            id: "section_paragraph".to_string(),
            name: "Section Paragraph Handler".to_string(),
            selectors: vec![
                "section paragraph".to_string(),
                "article paragraph".to_string(),
            ],
            description: "Special handling for paragraphs within sections or articles".to_string(),
            handler: ActionHandler::Custom("section_paragraph_processor".to_string()),
        },
        Action {
            id: "document_validator".to_string(),
            name: "Document Validator".to_string(),
            selectors: vec!["document".to_string()],
            description: "Validate document structure".to_string(),
            handler: ActionHandler::Validate("document_structure".to_string()),
        },
    ]
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::parsers::wasot::{Id, MetaLine, ParsedArguments, QuotedString};

    fn create_test_metaline(type_name: &str, line_index: usize) -> MetaLine {
        MetaLine {
            level: 1,
            type_name: type_name.to_string(),
            id: Id::new("".to_string()),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
            inline_text: QuotedString::new("".to_string(), false),
            line_index: Some(line_index),
            is_void: false,
        }
    }

    #[test]
    fn test_action_consumer_creation() {
        let actions = vec![
            Action {
                id: "test1".to_string(),
                name: "Test Action 1".to_string(),
                selectors: vec!["paragraph".to_string()],
                description: "Test description".to_string(),
                handler: ActionHandler::Log("test.log".to_string()),
            },
            Action {
                id: "test2".to_string(),
                name: "Test Action 2".to_string(),
                selectors: vec!["section > paragraph".to_string()],
                description: "Test description 2".to_string(),
                handler: ActionHandler::Transform("uppercase".to_string()),
            },
        ];

        let consumer = ActionConsumer::new(actions.clone());
        assert!(
            consumer.is_ok(),
            "ActionConsumer should be created successfully"
        );

        let consumer = consumer.unwrap();
        assert_eq!(consumer.actions.len(), 2);
        assert_eq!(consumer.parsed_selectors.len(), 2);
    }

    #[test]
    fn test_action_consumer_invalid_selector() {
        let actions = vec![Action {
            id: "invalid".to_string(),
            name: "Invalid Selector".to_string(),
            selectors: vec![">".to_string()], // Invalid selector
            description: "Test".to_string(),
            handler: ActionHandler::Log("test.log".to_string()),
        }];

        let consumer = ActionConsumer::new(actions);
        assert!(consumer.is_err(), "Should fail with invalid selector");
    }

    #[test]
    fn test_action_matching_simple_selector() {
        let actions = vec![
            Action {
                id: "para_action".to_string(),
                name: "Paragraph Action".to_string(),
                selectors: vec!["paragraph".to_string()],
                description: "Matches paragraphs".to_string(),
                handler: ActionHandler::Log("para.log".to_string()),
            },
            Action {
                id: "section_action".to_string(),
                name: "Section Action".to_string(),
                selectors: vec!["section".to_string()],
                description: "Matches sections".to_string(),
                handler: ActionHandler::Log("section.log".to_string()),
            },
        ];

        let mut consumer = ActionConsumer::new(actions).unwrap().with_target_line(1); // Target line 1 (1-based)

        // Process a paragraph block at line 0 (0-based)
        let paragraph_event = Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 0),
        };
        consumer.handle(paragraph_event);

        // Find matching actions after processing
        consumer.find_matching_actions();

        let matching = consumer.get_matching_actions();
        assert_eq!(matching.len(), 1);
        assert_eq!(matching[0].id, "para_action");
    }

    #[test]
    fn test_action_matching_block_range() {
        // Test that actions match for lines within a block's range
        let actions = vec![Action {
            id: "para_action".to_string(),
            name: "Paragraph Action".to_string(),
            selectors: vec!["paragraph".to_string()],
            description: "Matches paragraphs".to_string(),
            handler: ActionHandler::Log("para.log".to_string()),
        }];

        // Test line at block start (line 1 - the meta line)
        let mut consumer1 = ActionConsumer::new(actions.clone())
            .unwrap()
            .with_target_line(1);
        consumer1.handle(Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 0),
        });
        assert_eq!(consumer1.get_matching_actions().len(), 1);

        // Test targeting a line at paragraph start
        let mut consumer2 = ActionConsumer::new(actions.clone())
            .unwrap()
            .with_target_line(1);
        consumer2.handle(Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 0),
        });
        assert_eq!(consumer2.get_matching_actions().len(), 1);

        // Test targeting after a different block type
        let mut consumer3 = ActionConsumer::new(actions.clone())
            .unwrap()
            .with_target_line(5);
        // Start with a section at line 2
        consumer3.handle(Event::BlockStarted {
            meta_line: create_test_metaline("section", 2),
        });
        // Should not match paragraph selector
        assert_eq!(consumer3.get_matching_actions().len(), 0);
    }

    #[test]
    fn test_action_matching_descendant_selector() {
        let actions = vec![Action {
            id: "nested_para".to_string(),
            name: "Nested Paragraph".to_string(),
            selectors: vec!["section paragraph".to_string()],
            description: "Matches paragraphs inside sections".to_string(),
            handler: ActionHandler::Transform("indent".to_string()),
        }];

        let mut consumer = ActionConsumer::new(actions).unwrap().with_target_line(2); // Target line 2

        // First, process a section
        let section_event = Event::BlockStarted {
            meta_line: create_test_metaline("section", 0),
        };
        consumer.handle(section_event);

        // Now process a paragraph inside the section
        let paragraph_event = Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 1),
        };
        consumer.handle(paragraph_event);

        // Should match now at line 2
        consumer.find_matching_actions();
        let matching = consumer.get_matching_actions();
        assert_eq!(matching.len(), 1);
        assert_eq!(matching[0].id, "nested_para");
    }

    #[test]
    fn test_action_no_match_without_target_line() {
        let actions = vec![Action {
            id: "test".to_string(),
            name: "Test".to_string(),
            selectors: vec!["paragraph".to_string()],
            description: "Test".to_string(),
            handler: ActionHandler::Log("test.log".to_string()),
        }];

        let mut consumer = ActionConsumer::new(actions).unwrap();
        // No target line set

        let event = Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 0),
        };
        consumer.handle(event);

        // Should not match without target line
        assert_eq!(consumer.get_matching_actions().len(), 0);
    }

    #[test]
    fn test_action_handler_types() {
        let handlers = vec![
            ActionHandler::Log("file.log".to_string()),
            ActionHandler::FixTypos("typos.txt".to_string()),
            ActionHandler::Transform("uppercase".to_string()),
            ActionHandler::Validate("schema.xsd".to_string()),
            ActionHandler::Custom("custom_handler".to_string()),
        ];

        for (i, handler) in handlers.into_iter().enumerate() {
            let action = Action {
                id: format!("action_{i}"),
                name: format!("Action {i}"),
                selectors: vec!["div".to_string()],
                description: "Test".to_string(),
                handler: handler.clone(),
            };

            // Verify the handler can be cloned and debug printed
            let _cloned = handler.clone();
            let _debug_str = format!("{handler:?}");

            // Create consumer with this action
            let consumer = ActionConsumer::new(vec![action]);
            assert!(consumer.is_ok());
        }
    }

    #[test]
    fn test_multiple_actions_matching() {
        let actions = vec![
            Action {
                id: "action1".to_string(),
                name: "Action 1".to_string(),
                selectors: vec!["paragraph".to_string()],
                description: "First action".to_string(),
                handler: ActionHandler::Log("1.log".to_string()),
            },
            Action {
                id: "action2".to_string(),
                name: "Action 2".to_string(),
                selectors: vec!["paragraph".to_string()],
                description: "Second action".to_string(),
                handler: ActionHandler::Transform("bold".to_string()),
            },
            Action {
                id: "action3".to_string(),
                name: "Action 3".to_string(),
                selectors: vec!["section".to_string()],
                description: "Won't match".to_string(),
                handler: ActionHandler::Validate("schema".to_string()),
            },
        ];

        let mut consumer = ActionConsumer::new(actions).unwrap().with_target_line(1);

        let event = Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 0),
        };
        consumer.handle(event);

        consumer.find_matching_actions();
        let matching = consumer.get_matching_actions();
        assert_eq!(matching.len(), 2);
        assert!(matching.iter().any(|a| a.id == "action1"));
        assert!(matching.iter().any(|a| a.id == "action2"));
        assert!(!matching.iter().any(|a| a.id == "action3"));
    }

    #[test]
    fn test_child_index_tracking() {
        let actions = vec![Action {
            id: "first_child".to_string(),
            name: "First Child".to_string(),
            selectors: vec!["paragraph:first-child".to_string()],
            description: "Matches first paragraph".to_string(),
            handler: ActionHandler::Log("first.log".to_string()),
        }];

        // Test first paragraph matching
        let mut consumer1 = ActionConsumer::new(actions.clone())
            .unwrap()
            .with_target_line(2); // Target line 2 (1-based) = line index 1 (0-based)

        // Add a parent element
        consumer1.handle(Event::BlockStarted {
            meta_line: create_test_metaline("section", 0),
        });

        // First paragraph (child index 0)
        let first_para = Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 1),
        };
        consumer1.handle(first_para);
        consumer1.find_matching_actions();
        assert_eq!(consumer1.get_matching_actions().len(), 1);

        // Test second paragraph not matching
        let mut consumer2 = ActionConsumer::new(actions).unwrap().with_target_line(3);

        // Add parent and first child
        consumer2.handle(Event::BlockStarted {
            meta_line: create_test_metaline("section", 0),
        });
        consumer2.handle(Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 1),
        });
        consumer2.handle(Event::BlockFinished); // End first paragraph

        // Second paragraph (child index 1)
        let second_para = Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 2),
        };
        consumer2.handle(second_para);
        consumer2.find_matching_actions();
        assert_eq!(consumer2.get_matching_actions().len(), 0); // Not first child
    }

    #[test]
    fn test_nested_block_handling() {
        let actions = vec![Action {
            id: "deep".to_string(),
            name: "Deep Selector".to_string(),
            selectors: vec!["document section paragraph".to_string()],
            description: "Deeply nested".to_string(),
            handler: ActionHandler::Custom("deep_handler".to_string()),
        }];

        let mut consumer = ActionConsumer::new(actions).unwrap().with_target_line(3);

        // Build nested structure
        consumer.handle(Event::BlockStarted {
            meta_line: create_test_metaline("document", 0),
        });
        consumer.handle(Event::BlockStarted {
            meta_line: create_test_metaline("section", 1),
        });
        consumer.handle(Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 2),
        });

        consumer.find_matching_actions();
        assert_eq!(consumer.get_matching_actions().len(), 1);

        // Unwind the structure
        consumer.handle(Event::BlockFinished);
        consumer.handle(Event::BlockFinished);
        consumer.handle(Event::BlockFinished);

        assert_eq!(consumer.collector.depth(), 0);
    }

    #[test]
    fn test_text_event_handling() {
        let actions = vec![Action {
            id: "text_handler".to_string(),
            name: "Text Handler".to_string(),
            selectors: vec!["paragraph".to_string()],
            description: "Handles text".to_string(),
            handler: ActionHandler::FixTypos("typos.txt".to_string()),
        }];

        let mut consumer = ActionConsumer::new(actions).unwrap().with_target_line(1);

        // Start paragraph
        consumer.handle(Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 0),
        });

        // Text event should be handled by collector
        consumer.handle(Event::TextFinished {
            lines: vec!["Some text".to_string()],
            links: vec![vec![]],
            footnotes: vec![],
        });

        // Should match the paragraph
        assert_eq!(consumer.get_matching_actions().len(), 1);
    }

    #[test]
    fn test_get_sample_actions() {
        let samples = get_sample_actions();

        assert!(!samples.is_empty());

        // Verify all sample actions have valid selectors
        for action in &samples {
            assert!(!action.id.is_empty());
            assert!(!action.name.is_empty());
            assert!(!action.selectors.is_empty());
            assert!(!action.description.is_empty());

            // Try to parse all selectors to ensure they're valid
            for selector in &action.selectors {
                let parsed = parse_selector(selector);
                assert!(
                    parsed.is_ok(),
                    "Sample action '{}' has invalid selector: {}",
                    action.id,
                    selector
                );
            }
        }

        // Check that we have different handler types in samples
        let has_log = samples
            .iter()
            .any(|a| matches!(a.handler, ActionHandler::Log(_)));
        let has_transform = samples
            .iter()
            .any(|a| matches!(a.handler, ActionHandler::Transform(_)));
        assert!(has_log);
        assert!(has_transform);
    }

    #[test]
    fn test_nested_block_ranges() {
        // Test that nested blocks handle selectors correctly
        let actions = vec![
            Action {
                id: "section_action".to_string(),
                name: "Section Action".to_string(),
                selectors: vec!["section".to_string()],
                description: "Matches sections".to_string(),
                handler: ActionHandler::Log("section.log".to_string()),
            },
            Action {
                id: "nested_para".to_string(),
                name: "Nested Paragraph".to_string(),
                selectors: vec!["section paragraph".to_string()],
                description: "Matches paragraphs in sections".to_string(),
                handler: ActionHandler::Transform("indent".to_string()),
            },
        ];

        let mut consumer = ActionConsumer::new(actions).unwrap().with_target_line(3);

        // Section starts at line 0
        consumer.handle(Event::BlockStarted {
            meta_line: create_test_metaline("section", 0),
        });

        // Paragraph starts at line 2 - should trigger matching for nested paragraph
        consumer.handle(Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 2),
        });

        let matching = consumer.get_matching_actions();
        assert_eq!(matching.len(), 2);
        // Both section and nested paragraph actions should match
        assert!(matching.iter().any(|a| a.id == "section_action"));
        assert!(matching.iter().any(|a| a.id == "nested_para"));
    }

    #[test]
    fn test_action_with_multiple_selectors() {
        let actions = vec![Action {
            id: "multi_selector".to_string(),
            name: "Multi Selector Action".to_string(),
            selectors: vec![
                "paragraph".to_string(),
                "quote".to_string(),
                "section > paragraph".to_string(),
            ],
            description: "Action with multiple selectors".to_string(),
            handler: ActionHandler::Log("multi.log".to_string()),
        }];

        let mut consumer = ActionConsumer::new(actions).unwrap().with_target_line(1);

        // Test that it matches a paragraph
        consumer.handle(Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 0),
        });
        consumer.find_matching_actions();
        assert_eq!(consumer.get_matching_actions().len(), 1);

        // Reset and test that it matches a quote
        let mut consumer2 = ActionConsumer::new(vec![Action {
            id: "multi_selector".to_string(),
            name: "Multi Selector Action".to_string(),
            selectors: vec![
                "paragraph".to_string(),
                "quote".to_string(),
                "section > paragraph".to_string(),
            ],
            description: "Action with multiple selectors".to_string(),
            handler: ActionHandler::Log("multi.log".to_string()),
        }])
        .unwrap()
        .with_target_line(1);

        consumer2.handle(Event::BlockStarted {
            meta_line: create_test_metaline("quote", 0),
        });
        consumer2.find_matching_actions();
        assert_eq!(consumer2.get_matching_actions().len(), 1);

        // Reset and test that it matches section > paragraph
        let mut consumer3 = ActionConsumer::new(vec![Action {
            id: "multi_selector".to_string(),
            name: "Multi Selector Action".to_string(),
            selectors: vec![
                "paragraph".to_string(),
                "quote".to_string(),
                "section > paragraph".to_string(),
            ],
            description: "Action with multiple selectors".to_string(),
            handler: ActionHandler::Log("multi.log".to_string()),
        }])
        .unwrap()
        .with_target_line(2);

        consumer3.handle(Event::BlockStarted {
            meta_line: create_test_metaline("section", 0),
        });
        consumer3.handle(Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 1),
        });
        consumer3.find_matching_actions();
        assert_eq!(consumer3.get_matching_actions().len(), 1);
    }

    #[test]
    fn test_duplicate_selectors_in_collector() {
        // Test that duplicate selectors don't cause issues in StackFrameCollector
        let actions = vec![
            Action {
                id: "action1".to_string(),
                name: "Action 1".to_string(),
                selectors: vec!["paragraph".to_string()],
                description: "First action".to_string(),
                handler: ActionHandler::Log("1.log".to_string()),
            },
            Action {
                id: "action2".to_string(),
                name: "Action 2".to_string(),
                selectors: vec!["paragraph".to_string()], // Same selector as action1
                description: "Second action".to_string(),
                handler: ActionHandler::Transform("bold".to_string()),
            },
        ];

        let consumer = ActionConsumer::new(actions);
        assert!(consumer.is_ok());
        let consumer = consumer.unwrap();

        // Should only have one "paragraph" entry in the collector
        assert_eq!(consumer.parsed_selectors.len(), 2);
        assert!(consumer.parsed_selectors.contains_key("action1"));
        assert!(consumer.parsed_selectors.contains_key("action2"));
    }

    #[test]
    fn test_empty_actions_list() {
        let actions = vec![];
        let consumer = ActionConsumer::new(actions);
        assert!(consumer.is_ok());

        let consumer = consumer.unwrap();
        assert_eq!(consumer.actions.len(), 0);
        assert_eq!(consumer.parsed_selectors.len(), 0);
    }

    #[test]
    fn test_with_target_line_chaining() {
        let actions = vec![Action {
            id: "test".to_string(),
            name: "Test".to_string(),
            selectors: vec!["paragraph".to_string()],
            description: "Test".to_string(),
            handler: ActionHandler::Log("test.log".to_string()),
        }];

        let consumer = ActionConsumer::new(actions).unwrap();
        assert!(consumer.target_line.is_none());

        let consumer = consumer.with_target_line(42);
        assert_eq!(consumer.target_line, Some(42));
    }

    #[test]
    fn test_find_matching_actions_clears_previous_matches() {
        let actions = vec![Action {
            id: "test".to_string(),
            name: "Test".to_string(),
            selectors: vec!["paragraph".to_string()],
            description: "Test".to_string(),
            handler: ActionHandler::Log("test.log".to_string()),
        }];

        let mut consumer = ActionConsumer::new(actions).unwrap().with_target_line(1);

        // Manually add a match to verify it gets cleared
        consumer.matching_actions.push(Action {
            id: "fake".to_string(),
            name: "Fake".to_string(),
            selectors: vec!["div".to_string()],
            description: "Fake".to_string(),
            handler: ActionHandler::Log("fake.log".to_string()),
        });
        assert_eq!(consumer.matching_actions.len(), 1);

        // find_matching_actions should clear previous matches
        consumer.find_matching_actions();
        assert_eq!(consumer.matching_actions.len(), 0);
    }

    #[test]
    fn test_find_matching_actions_without_target_line() {
        let actions = vec![Action {
            id: "test".to_string(),
            name: "Test".to_string(),
            selectors: vec!["paragraph".to_string()],
            description: "Test".to_string(),
            handler: ActionHandler::Log("test.log".to_string()),
        }];

        let mut consumer = ActionConsumer::new(actions).unwrap();
        // No target line set

        consumer.find_matching_actions();
        assert_eq!(consumer.matching_actions.len(), 0);
    }

    #[test]
    fn test_action_matching_with_empty_stack() {
        let actions = vec![Action {
            id: "root_action".to_string(),
            name: "Root Action".to_string(),
            selectors: vec!["document".to_string()],
            description: "Matches document".to_string(),
            handler: ActionHandler::Log("doc.log".to_string()),
        }];

        let mut consumer = ActionConsumer::new(actions).unwrap().with_target_line(1);

        // No blocks started, stack should be empty
        consumer.find_matching_actions();
        assert_eq!(consumer.matching_actions.len(), 0);
    }

    #[test]
    fn test_target_line_edge_cases() {
        let actions = vec![Action {
            id: "test".to_string(),
            name: "Test".to_string(),
            selectors: vec!["paragraph".to_string()],
            description: "Test".to_string(),
            handler: ActionHandler::Log("test.log".to_string()),
        }];

        // Test with target_line of 0 (edge case for saturating_sub)
        let mut consumer = ActionConsumer::new(actions.clone())
            .unwrap()
            .with_target_line(0);

        let event = Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 0),
        };
        consumer.handle(event);
        // Should still match since line_index 0 matches target line 0 (after saturating_sub(1))
        assert_eq!(consumer.get_matching_actions().len(), 1);

        // Test with target_line beyond the current line
        let mut consumer2 = ActionConsumer::new(actions).unwrap().with_target_line(100);

        let event2 = Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 0),
        };
        consumer2.handle(event2);
        // Should match since current_line (0) <= target_line_0based (99)
        assert_eq!(consumer2.get_matching_actions().len(), 1);
    }

    #[test]
    fn test_meta_line_without_line_index() {
        let actions = vec![Action {
            id: "test".to_string(),
            name: "Test".to_string(),
            selectors: vec!["paragraph".to_string()],
            description: "Test".to_string(),
            handler: ActionHandler::Log("test.log".to_string()),
        }];

        let mut consumer = ActionConsumer::new(actions).unwrap().with_target_line(1);

        // Create a meta_line with None line_index
        let mut meta_line = create_test_metaline("paragraph", 0);
        meta_line.line_index = None;

        let event = Event::BlockStarted { meta_line };
        consumer.handle(event);

        // Should match since current_line defaults to 0 when line_index is None
        assert_eq!(consumer.get_matching_actions().len(), 1);
    }

    #[test]
    fn test_duplicate_selector_collection() {
        // Test lines 47-49: The logic for checking if selectors are already in all_selectors
        let actions = vec![
            Action {
                id: "action1".to_string(),
                name: "Action 1".to_string(),
                selectors: vec!["paragraph".to_string(), "quote".to_string()],
                description: "First action".to_string(),
                handler: ActionHandler::Log("1.log".to_string()),
            },
            Action {
                id: "action2".to_string(),
                name: "Action 2".to_string(),
                selectors: vec!["paragraph".to_string(), "section".to_string()], // "paragraph" is duplicate
                description: "Second action".to_string(),
                handler: ActionHandler::Transform("transform".to_string()),
            },
        ];

        let consumer = ActionConsumer::new(actions).unwrap();

        // Verify that we have the right number of unique selectors in the collector
        // The collector should be created with ["paragraph", "quote", "section"] - 3 unique selectors
        assert_eq!(consumer.collector.selector_count(), 3);
    }

    #[test]
    fn test_prefix_matching_all_lengths() {
        // Test lines 97-104: The nested loop logic in find_matching_actions for all prefix lengths
        let actions = vec![Action {
            id: "deep_match".to_string(),
            name: "Deep Match".to_string(),
            selectors: vec!["document section".to_string()], // Should match at prefix length 2
            description: "Matches document section prefix".to_string(),
            handler: ActionHandler::Log("deep.log".to_string()),
        }];

        let mut consumer = ActionConsumer::new(actions).unwrap().with_target_line(3);

        // Build a 3-level deep stack: document -> section -> paragraph
        consumer.handle(Event::BlockStarted {
            meta_line: create_test_metaline("document", 0),
        });
        consumer.handle(Event::BlockStarted {
            meta_line: create_test_metaline("section", 1),
        });
        consumer.handle(Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 2),
        });

        // The "document section" selector should match at prefix length 2
        // even though we have a 3-element stack [document, section, paragraph]
        consumer.find_matching_actions();
        assert_eq!(consumer.get_matching_actions().len(), 1);
    }

    #[test]
    fn test_action_break_on_first_match() {
        // Test lines 101-106: The action_matched break logic to ensure we don't add duplicate actions
        let actions = vec![Action {
            id: "multi_match".to_string(),
            name: "Multi Match".to_string(),
            selectors: vec![
                "paragraph".to_string(),
                "section paragraph".to_string(), // Both could match the same element
            ],
            description: "Has multiple matching selectors".to_string(),
            handler: ActionHandler::Log("multi.log".to_string()),
        }];

        let mut consumer = ActionConsumer::new(actions).unwrap().with_target_line(2);

        // Create a structure where both selectors could match
        consumer.handle(Event::BlockStarted {
            meta_line: create_test_metaline("section", 0),
        });
        consumer.handle(Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 1),
        });

        consumer.find_matching_actions();

        // Should only have one instance of the action, not two, due to the break on first match
        assert_eq!(consumer.get_matching_actions().len(), 1);
        assert_eq!(consumer.get_matching_actions()[0].id, "multi_match");
    }

    #[test]
    fn test_empty_prefix_matching() {
        // Test edge case where stack is very small and prefix lengths start from 1
        let actions = vec![Action {
            id: "single_element".to_string(),
            name: "Single Element".to_string(),
            selectors: vec!["document".to_string()],
            description: "Matches single element".to_string(),
            handler: ActionHandler::Log("single.log".to_string()),
        }];

        let mut consumer = ActionConsumer::new(actions).unwrap().with_target_line(1);

        // Single element stack
        consumer.handle(Event::BlockStarted {
            meta_line: create_test_metaline("document", 0),
        });

        consumer.find_matching_actions();
        assert_eq!(consumer.get_matching_actions().len(), 1);
    }

    #[test]
    fn test_non_matching_selector_in_group() {
        // Test that actions with no matching selectors don't get added
        let actions = vec![
            Action {
                id: "matching".to_string(),
                name: "Matching Action".to_string(),
                selectors: vec!["paragraph".to_string()],
                description: "Should match".to_string(),
                handler: ActionHandler::Log("match.log".to_string()),
            },
            Action {
                id: "non_matching".to_string(),
                name: "Non-Matching Action".to_string(),
                selectors: vec!["article".to_string(), "aside".to_string()], // Neither should match
                description: "Should not match".to_string(),
                handler: ActionHandler::Log("no_match.log".to_string()),
            },
        ];

        let mut consumer = ActionConsumer::new(actions).unwrap().with_target_line(1);

        consumer.handle(Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 0),
        });

        consumer.find_matching_actions();
        assert_eq!(consumer.get_matching_actions().len(), 1);
        assert_eq!(consumer.get_matching_actions()[0].id, "matching");
    }

    #[test]
    fn test_current_line_comparison_edge_cases() {
        // Test lines 123-131: current_line comparison with target line edge cases
        let actions = vec![Action {
            id: "test".to_string(),
            name: "Test".to_string(),
            selectors: vec!["paragraph".to_string()],
            description: "Test".to_string(),
            handler: ActionHandler::Log("test.log".to_string()),
        }];

        // Test case where current_line equals target_line_0based exactly
        let mut consumer1 = ActionConsumer::new(actions.clone())
            .unwrap()
            .with_target_line(5);
        consumer1.handle(Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 4), // line 4 (0-based) = line 5 (1-based)
        });
        assert_eq!(consumer1.get_matching_actions().len(), 1);

        // Test case where current_line is one past target_line_0based (should not match)
        let mut consumer2 = ActionConsumer::new(actions).unwrap().with_target_line(5);
        consumer2.handle(Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 5), // line 5 (0-based) > target line 4 (0-based)
        });
        assert_eq!(consumer2.get_matching_actions().len(), 0);
    }

    #[test]
    fn test_action_consumer_debug_trait() {
        // Test that Action and ActionHandler implement Debug properly
        let action = Action {
            id: "debug_test".to_string(),
            name: "Debug Test".to_string(),
            selectors: vec!["test".to_string()],
            description: "Test debug trait".to_string(),
            handler: ActionHandler::Custom("debug_handler".to_string()),
        };

        let debug_string = format!("{action:?}");
        assert!(debug_string.contains("debug_test"));
        assert!(debug_string.contains("Debug Test"));
        assert!(debug_string.contains("Custom"));

        let handler_debug = format!("{:?}", ActionHandler::Validate("schema.xml".to_string()));
        assert!(handler_debug.contains("Validate"));
        assert!(handler_debug.contains("schema.xml"));
    }

    #[test]
    fn test_find_matching_actions_with_missing_parsed_selectors() {
        // Test the edge case where parsed_selectors might not contain an action's ID
        // This is a defensive test for the get() call in find_matching_actions
        let actions = vec![Action {
            id: "test_action".to_string(),
            name: "Test Action".to_string(),
            selectors: vec!["paragraph".to_string()],
            description: "Test".to_string(),
            handler: ActionHandler::Log("test.log".to_string()),
        }];

        let mut consumer = ActionConsumer::new(actions).unwrap().with_target_line(1);

        // Manually corrupt the parsed_selectors to test the edge case
        consumer.parsed_selectors.clear();

        consumer.handle(Event::BlockStarted {
            meta_line: create_test_metaline("paragraph", 0),
        });

        consumer.find_matching_actions();

        // Should not crash and should find no matches since parsed_selectors was cleared
        assert_eq!(consumer.get_matching_actions().len(), 0);
    }
}
