//! XML Emitter - reads XML and emits wasot events
//!
//! This emitter parses XML files in the wasot XML format and emits
//! the corresponding events (BlockStarted, TextFinished, BlockFinished, etc.)

use crate::events::{Emitter, Event};
use crate::parsers::wasot::{Id, MetaLine, ParsedArguments, QuotedString};
use bus::Bus;
use std::fs::File;
use std::io::{self, BufReader, Read};
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::thread;
use xml::reader::{EventReader, XmlEvent};

/// Unescape XML entities
fn unescape_xml(s: &str) -> String {
    s.replace("&lt;", "<")
        .replace("&gt;", ">")
        .replace("&quot;", "\"")
        .replace("&apos;", "'")
        .replace("&amp;", "&")
}

/// State for parsing a block element
#[derive(Debug, Clone, Default)]
struct BlockState {
    block_type: String,
    level: usize,
    id: String,
    is_void: bool,
    inline_text: String,
    inline_text_quoted: bool,
    args: Vec<QuotedString>,
    kwargs: Vec<(QuotedString, QuotedString)>,
    started: bool,
}

/// Emitter that reads XML files and produces wasot events
pub struct XmlEmitter {
    file_path: Option<String>,
    use_stdin: bool,
    bus: Arc<Mutex<Bus<Event>>>,
}

impl XmlEmitter {
    /// Create a new XmlEmitter that reads from a file
    pub fn from_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
        let path_str = path.as_ref().to_string_lossy().to_string();
        Ok(XmlEmitter {
            file_path: Some(path_str),
            use_stdin: false,
            bus: Arc::new(Mutex::new(Bus::new(1024))),
        })
    }

    /// Create a new XmlEmitter that reads from stdin
    pub fn from_stdin() -> Self {
        XmlEmitter {
            file_path: None,
            use_stdin: true,
            bus: Arc::new(Mutex::new(Bus::new(1024))),
        }
    }

    /// Emit BlockStarted event for the current block state
    fn emit_block_started(&self, state: &BlockState, line_index: usize) {
        let meta_line = MetaLine {
            type_name: state.block_type.clone(),
            level: state.level,
            id: Id::new(state.id.clone()),
            inline_text: QuotedString::new(state.inline_text.clone(), state.inline_text_quoted),
            arguments: ParsedArguments {
                args: state.args.clone(),
                kwargs: state.kwargs.clone(),
            },
            line_index: Some(line_index),
            is_void: state.is_void,
        };

        if state.is_void {
            self.bus
                .lock()
                .unwrap()
                .broadcast(Event::VoidElement { meta_line });
        } else {
            self.bus
                .lock()
                .unwrap()
                .broadcast(Event::BlockStarted { meta_line });
        }
    }

    /// Process XML content and emit events
    fn process_xml<R: Read>(&self, reader: R) -> io::Result<()> {
        let parser = EventReader::new(BufReader::new(reader));
        let mut line_index: usize = 0;

        // Stack of block states for nested blocks
        let mut block_stack: Vec<BlockState> = Vec::new();

        // Current element being parsed
        let mut current_element: Option<String> = None;
        let mut current_text = String::new();

        // For kwarg parsing
        let mut kwarg_key_content = String::new();
        let mut kwarg_key_quoted = false;
        let mut kwarg_value_content = String::new();
        let mut kwarg_value_quoted = false;
        let mut in_kwarg_key = false;
        let mut in_kwarg_value = false;

        // For arg parsing
        let mut current_arg_quoted = false;

        // Text content lines
        let mut text_lines: Vec<String> = Vec::new();

        for event in parser {
            match event {
                Ok(XmlEvent::StartElement {
                    name, attributes, ..
                }) => {
                    let element_name = name.local_name.as_str();

                    // Before starting a new nested element, emit the parent block if needed
                    if (element_name == "block"
                        || element_name == "text"
                        || element_name == "context-line")
                        && !block_stack.is_empty()
                    {
                        let parent = block_stack.last_mut().unwrap();
                        if !parent.started && !parent.is_void {
                            self.emit_block_started(parent, line_index);
                            parent.started = true;
                            line_index += 1;
                        }
                    }

                    match element_name {
                        "block" => {
                            let mut state = BlockState {
                                level: 1,
                                ..Default::default()
                            };

                            for attr in &attributes {
                                match attr.name.local_name.as_str() {
                                    "type" => state.block_type = attr.value.clone(),
                                    "level" => {
                                        state.level = attr.value.parse().unwrap_or(1);
                                    }
                                    "id" => state.id = attr.value.clone(),
                                    "void" => state.is_void = attr.value == "true",
                                    _ => {}
                                }
                            }
                            block_stack.push(state);
                            current_element = Some("block".to_string());
                        }
                        "context-line" => {
                            let mut block_type = String::new();
                            let mut block_level: usize = 1;
                            let mut block_id = String::new();

                            for attr in &attributes {
                                match attr.name.local_name.as_str() {
                                    "type" => block_type = attr.value.clone(),
                                    "level" => {
                                        block_level = attr.value.parse().unwrap_or(1);
                                    }
                                    "id" => block_id = attr.value.clone(),
                                    _ => {}
                                }
                            }

                            // Store context-line info temporarily
                            let state = BlockState {
                                block_type,
                                level: block_level,
                                id: block_id,
                                is_void: false,
                                inline_text: String::new(),
                                inline_text_quoted: false,
                                args: vec![],
                                kwargs: vec![],
                                started: false,
                            };
                            block_stack.push(state);
                            current_element = Some("context-line".to_string());
                            current_text.clear();
                        }
                        "inline-text" => {
                            let quoted = attributes
                                .iter()
                                .any(|a| a.name.local_name == "quoted" && a.value == "true");
                            if let Some(state) = block_stack.last_mut() {
                                state.inline_text_quoted = quoted;
                            }
                            current_element = Some("inline-text".to_string());
                            current_text.clear();
                        }
                        "arg" => {
                            current_arg_quoted = attributes
                                .iter()
                                .any(|a| a.name.local_name == "quoted" && a.value == "true");
                            current_element = Some("arg".to_string());
                            current_text.clear();
                        }
                        "kwarg" => {
                            kwarg_key_content.clear();
                            kwarg_key_quoted = false;
                            kwarg_value_content.clear();
                            kwarg_value_quoted = false;
                            current_element = Some("kwarg".to_string());
                        }
                        "key" => {
                            kwarg_key_quoted = attributes
                                .iter()
                                .any(|a| a.name.local_name == "quoted" && a.value == "true");
                            in_kwarg_key = true;
                            current_text.clear();
                        }
                        "value" => {
                            kwarg_value_quoted = attributes
                                .iter()
                                .any(|a| a.name.local_name == "quoted" && a.value == "true");
                            in_kwarg_value = true;
                            current_text.clear();
                        }
                        "text" => {
                            text_lines.clear();
                            current_element = Some("text".to_string());
                        }
                        "wasot" => {
                            // Root element, ignore
                        }
                        _ => {}
                    }
                }

                Ok(XmlEvent::Characters(text)) | Ok(XmlEvent::CData(text)) => {
                    // Note: xml-rs already handles basic entity unescaping for Characters,
                    // but our unescape_xml handles any remaining or custom escaping
                    let unescaped = unescape_xml(&text);
                    if in_kwarg_key {
                        kwarg_key_content.push_str(&unescaped);
                    } else if in_kwarg_value {
                        kwarg_value_content.push_str(&unescaped);
                    } else if current_element.as_deref() == Some("text") {
                        // Collect text lines - preserve content exactly (CDATA preserves whitespace)
                        for line in unescaped.lines() {
                            text_lines.push(line.to_string());
                        }
                    } else if current_element.as_deref() == Some("context-line")
                        || current_element.as_deref() == Some("inline-text")
                        || current_element.as_deref() == Some("arg")
                    {
                        current_text.push_str(&unescaped);
                    }
                }

                Ok(XmlEvent::EndElement { name }) => {
                    let element_name = name.local_name.as_str();

                    match element_name {
                        "block" => {
                            if let Some(state) = block_stack.pop() {
                                if state.is_void {
                                    // Void element - emit it now
                                    self.emit_block_started(&state, line_index);
                                    line_index += 1;
                                } else {
                                    // If block wasn't started yet (empty block), start it now
                                    if !state.started {
                                        self.emit_block_started(&state, line_index);
                                        line_index += 1;
                                    }
                                    // Emit BlockFinished
                                    self.bus.lock().unwrap().broadcast(Event::BlockFinished);
                                }
                            }
                            current_element = if block_stack.is_empty() {
                                None
                            } else {
                                Some("block".to_string())
                            };
                        }
                        "context-line" => {
                            if let Some(state) = block_stack.pop() {
                                // Emit ContextLine event
                                let content = current_text.trim().to_string();
                                let meta_line = MetaLine {
                                    type_name: state.block_type,
                                    level: state.level,
                                    id: Id::new(state.id),
                                    inline_text: QuotedString::new(content, false),
                                    arguments: ParsedArguments {
                                        args: vec![],
                                        kwargs: vec![],
                                    },
                                    line_index: Some(line_index),
                                    is_void: false,
                                };
                                self.bus
                                    .lock()
                                    .unwrap()
                                    .broadcast(Event::ContextLine { meta_line });
                                line_index += 1;
                            }
                            current_element = None;
                            current_text.clear();
                        }
                        "inline-text" => {
                            if let Some(state) = block_stack.last_mut() {
                                state.inline_text = current_text.trim().to_string();
                            }
                            current_text.clear();
                        }
                        "arg" => {
                            if let Some(state) = block_stack.last_mut() {
                                state.args.push(QuotedString::new(
                                    current_text.trim().to_string(),
                                    current_arg_quoted,
                                ));
                            }
                            current_text.clear();
                        }
                        "key" => {
                            // kwarg_key_content is already set by Characters handler
                            kwarg_key_content = kwarg_key_content.trim().to_string();
                            in_kwarg_key = false;
                        }
                        "value" => {
                            // kwarg_value_content is already set by Characters handler
                            kwarg_value_content = kwarg_value_content.trim().to_string();
                            in_kwarg_value = false;
                        }
                        "kwarg" => {
                            if let Some(state) = block_stack.last_mut() {
                                state.kwargs.push((
                                    QuotedString::new(kwarg_key_content.clone(), kwarg_key_quoted),
                                    QuotedString::new(
                                        kwarg_value_content.clone(),
                                        kwarg_value_quoted,
                                    ),
                                ));
                            }
                        }
                        "text" => {
                            // With CDATA format, content is exact - no stripping needed.
                            // The output format is: line1\nline2\n (each line ends with \n)
                            // A blank line is represented as \n alone.
                            // Example: ["a", "b", ""] produces "a\nb\n\n" in CDATA
                            // .lines() gives ["a", "b", ""] which is exactly what we want.

                            // Emit TextFinished event if we have lines
                            if !text_lines.is_empty() {
                                let links: Vec<Vec<crate::parsers::wasot::Link>> =
                                    text_lines.iter().map(|_| vec![]).collect();
                                self.bus.lock().unwrap().broadcast(Event::TextFinished {
                                    lines: text_lines.clone(),
                                    links,
                                    footnotes: vec![],
                                });
                            }
                            text_lines.clear();
                            current_element = if block_stack.is_empty() {
                                None
                            } else {
                                Some("block".to_string())
                            };
                        }
                        "wasot" => {
                            // Root element end, ignore
                        }
                        _ => {}
                    }
                }

                Ok(XmlEvent::StartDocument { .. }) | Ok(XmlEvent::EndDocument) => {}

                Ok(XmlEvent::Whitespace(ws)) => {
                    // Handle whitespace inside text elements
                    if current_element.as_deref() == Some("text") {
                        // Add lines for the whitespace - this preserves empty lines
                        for line in ws.lines() {
                            text_lines.push(line.trim().to_string());
                        }
                    }
                    // Ignore whitespace outside text elements
                }

                Ok(XmlEvent::Comment(_)) | Ok(XmlEvent::ProcessingInstruction { .. }) => {}

                Err(e) => {
                    eprintln!("XML parsing error: {e}");
                    return Err(io::Error::new(io::ErrorKind::InvalidData, e.to_string()));
                }
            }
        }

        Ok(())
    }
}

impl Emitter for XmlEmitter {
    type Reader = crate::events::BusReaderIterator;

    fn subscribe(&self) -> Self::Reader {
        let rx = self.bus.lock().unwrap().add_rx();
        crate::events::BusReaderIterator::new(rx)
    }

    fn run(self) {
        let file_path = self.file_path.clone();
        let use_stdin = self.use_stdin;

        thread::spawn(move || {
            let reader: Box<dyn Read + Send> = if use_stdin {
                Box::new(io::stdin())
            } else if let Some(path) = file_path {
                match File::open(&path) {
                    Ok(file) => Box::new(file),
                    Err(e) => {
                        eprintln!("Error opening file {path}: {e}");
                        return;
                    }
                }
            } else {
                eprintln!("No input source specified");
                return;
            };

            if let Err(e) = self.process_xml(reader) {
                eprintln!("Error processing XML: {e}");
            }
            // Bus will be dropped when thread exits
        });
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::events::Event;
    use std::io::Cursor;

    #[test]
    fn test_from_stdin() {
        let emitter = XmlEmitter::from_stdin();
        assert!(emitter.file_path.is_none());
        assert!(emitter.use_stdin);
    }

    #[test]
    fn test_from_file() {
        let emitter = XmlEmitter::from_file("/tmp/test.xml").unwrap();
        assert_eq!(emitter.file_path.as_ref().unwrap(), "/tmp/test.xml");
        assert!(!emitter.use_stdin);
    }

    #[test]
    fn test_parse_simple_block() {
        let xml = r#"<block type="div" level="1" id="test1"></block>"#;
        let emitter = XmlEmitter::from_stdin();
        let receiver = emitter.subscribe();

        std::thread::spawn(move || {
            emitter.process_xml(Cursor::new(xml.as_bytes())).unwrap();
        });

        let events: Vec<Event> = receiver.collect();
        assert_eq!(events.len(), 2); // BlockStarted + BlockFinished
    }

    #[test]
    fn test_parse_block_with_text() {
        let xml = r#"<block type="p" level="1" id="1">
            <text>Hello World</text>
        </block>"#;
        let emitter = XmlEmitter::from_stdin();
        let receiver = emitter.subscribe();

        std::thread::spawn(move || {
            emitter.process_xml(Cursor::new(xml.as_bytes())).unwrap();
        });

        let events: Vec<Event> = receiver.collect();
        assert!(events.len() >= 2); // At least BlockStarted + BlockFinished
    }

    #[test]
    fn test_parse_context_line() {
        let xml = r#"<context-line type="id" level="1">doc123</context-line>"#;
        let emitter = XmlEmitter::from_stdin();
        let receiver = emitter.subscribe();

        std::thread::spawn(move || {
            emitter.process_xml(Cursor::new(xml.as_bytes())).unwrap();
        });

        let events: Vec<Event> = receiver.collect();
        assert_eq!(events.len(), 1);
        assert!(matches!(events[0], Event::ContextLine { .. }));
    }

    #[test]
    fn test_parse_void_element() {
        let xml = r#"<block type="hr" level="1" void="true"></block>"#;
        let emitter = XmlEmitter::from_stdin();
        let receiver = emitter.subscribe();

        std::thread::spawn(move || {
            emitter.process_xml(Cursor::new(xml.as_bytes())).unwrap();
        });

        let events: Vec<Event> = receiver.collect();
        assert_eq!(events.len(), 1);
        assert!(matches!(events[0], Event::VoidElement { .. }));
    }

    #[test]
    fn test_parse_block_with_args() {
        let xml = r#"<block type="div" level="1" id="1">
            <arg>arg1</arg>
            <arg quoted="true">arg with spaces</arg>
        </block>"#;
        let emitter = XmlEmitter::from_stdin();
        let receiver = emitter.subscribe();

        std::thread::spawn(move || {
            emitter.process_xml(Cursor::new(xml.as_bytes())).unwrap();
        });

        let events: Vec<Event> = receiver.collect();
        assert!(events.len() >= 2);
        if let Event::BlockStarted { meta_line } = &events[0] {
            assert_eq!(meta_line.arguments.args.len(), 2);
            assert_eq!(meta_line.arguments.args[0].content, "arg1");
            assert!(!meta_line.arguments.args[0].quoted);
            assert_eq!(meta_line.arguments.args[1].content, "arg with spaces");
            assert!(meta_line.arguments.args[1].quoted);
        } else {
            panic!("Expected BlockStarted event");
        }
    }

    #[test]
    fn test_parse_block_with_kwargs() {
        let xml = r#"<block type="div" level="1" id="1">
            <kwarg><key>name</key><value quoted="true">John Doe</value></kwarg>
        </block>"#;
        let emitter = XmlEmitter::from_stdin();
        let receiver = emitter.subscribe();

        std::thread::spawn(move || {
            emitter.process_xml(Cursor::new(xml.as_bytes())).unwrap();
        });

        let events: Vec<Event> = receiver.collect();
        assert!(events.len() >= 2);
        if let Event::BlockStarted { meta_line } = &events[0] {
            assert_eq!(meta_line.arguments.kwargs.len(), 1);
            assert_eq!(meta_line.arguments.kwargs[0].0.content, "name");
            assert_eq!(meta_line.arguments.kwargs[0].1.content, "John Doe");
            assert!(meta_line.arguments.kwargs[0].1.quoted);
        } else {
            panic!("Expected BlockStarted event");
        }
    }

    #[test]
    fn test_parse_nested_blocks() {
        let xml = r#"<block type="div" level="1" id="1">
            <block type="p" level="2" id="2">
                <text>Nested text</text>
            </block>
        </block>"#;
        let emitter = XmlEmitter::from_stdin();
        let receiver = emitter.subscribe();

        std::thread::spawn(move || {
            emitter.process_xml(Cursor::new(xml.as_bytes())).unwrap();
        });

        let events: Vec<Event> = receiver.collect();
        // Should have: BlockStarted(div), BlockStarted(p), TextFinished, BlockFinished(p), BlockFinished(div)
        assert!(events.len() >= 4);
    }

    #[test]
    fn test_text_with_leading_blank_line() {
        // This simulates the XML format with a blank line before content
        let xml = r#"<text>

    # Heading
    Content here

  </text>"#;
        let emitter = XmlEmitter::from_stdin();
        let receiver = emitter.subscribe();

        std::thread::spawn(move || {
            emitter.process_xml(Cursor::new(xml.as_bytes())).unwrap();
        });

        let events: Vec<Event> = receiver.collect();
        assert_eq!(events.len(), 1);
        if let Event::TextFinished { lines, .. } = &events[0] {
            // Should preserve the blank line before content
            // After stripping one leading (XML formatting) and one trailing (XML formatting)
            // we should have: ["", "# Heading", "Content here", ""]
            // where the first "" is the preserved blank line
            eprintln!("Lines: {:?}", lines);
            assert!(
                lines.len() >= 2,
                "Expected at least 2 lines, got {:?}",
                lines
            );
            // The blank line should be preserved
            assert!(
                lines.iter().any(|l| l.is_empty()),
                "Expected a blank line to be preserved, got {:?}",
                lines
            );
        } else {
            panic!("Expected TextFinished event, got {:?}", events[0]);
        }
    }
}
