// wasot-demo: Demo and experimental commands for Wasot
//
// Commands included:
// - demo: Parse a Wasot file and print events
// - xml-listener: Convert Wasot to XML
// - protobuf-listener: Convert Wasot to protobuf events (JSON output)
// - listen-demo: Listen to parsing events
// - facade: Parse using simple facade API
// - select: Select blocks matching CSS-like selectors
// - list-actions: List actions matching at a line
// - get-document-id: Extract document ID
// - links-demo: Demonstrate link syntax
// - links: Print all links found

use clap::{Parser, Subcommand};
use std::io;
use std::process;
use wasot_compiler::events::events::Emitter;
use wasot_compiler::events::run;
use wasot_compiler::{
    get_sample_actions, ActionConsumer, Consumer, Event, LinksPrinter, WasotFileEmitter,
    WasotReconstructor, XmlListener,
};

#[derive(Parser)]
#[command(name = "wasot-demo")]
#[command(about = "Demo and experimental commands for Wasot")]
#[command(version = "0.1.0")]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Parse a Wasot file and print events to stdout for demonstration
    Demo {
        /// Path to the Wasot file to parse
        file_path: String,
    },

    /// Convert a Wasot file to XML using the simple XML listener
    XmlListener {
        /// Path to the Wasot file to convert
        file_path: String,

        /// Pretty print the XML output
        #[arg(short = 'p', long)]
        pretty: bool,
    },

    /// Convert a Wasot file to protobuf events and output as JSON
    ProtobufListener {
        /// Path to the Wasot file to convert
        file_path: String,
    },

    /// Listen to parsing events and print them to stdout for demonstration
    ListenDemo {
        /// Path to the Wasot file to parse
        file_path: String,
    },

    /// Parse a Wasot file and print events using the simple facade API
    Facade {
        /// Path to the Wasot file to parse
        file_path: String,
    },

    /// Select blocks matching CSS-like selectors
    Select {
        /// Path to the Wasot file to parse (optional, reads from stdin if not provided)
        file_path: Option<String>,

        /// Selectors to match (can be specified multiple times)
        #[arg(short = 's', long)]
        selector: Vec<String>,
    },

    /// List actions that match at a specific line
    ListActions {
        /// Path to the Wasot file to parse
        file_path: String,

        /// Line number to check for matching actions
        #[arg(short = 'l', long)]
        line: usize,

        /// Path to the actions YAML file (defaults to actions.yaml)
        #[arg(short = 'a', long)]
        actions_file: Option<String>,
    },

    /// Extract the document ID from a Wasot file (context line of type 'id')
    GetDocumentId {
        /// Path to the Wasot file to extract ID from
        file_path: String,
    },

    /// Demonstrate the new ((link)) syntax with footnote conversion
    LinksDemo {
        /// Path to the Wasot file to convert (optional, creates demo if not provided)
        file_path: Option<String>,

        /// Output file path (optional, writes to stdout if not provided)
        #[arg(short = 'o', long)]
        output: Option<String>,
    },

    /// Print all links found in a Wasot file
    Links {
        /// Path to the Wasot file to analyze
        file_path: String,
    },
}

// Demo consumer that prints all events to stdout
struct DemoConsumer {
    #[allow(dead_code)]
    stack: Vec<String>,
    last_event_was_text: bool,
}

impl DemoConsumer {
    fn new() -> Self {
        DemoConsumer {
            stack: Vec::new(),
            last_event_was_text: false,
        }
    }
}

impl Consumer for DemoConsumer {
    fn handle(&mut self, event: Event) {
        match event {
            Event::BlockStarted { meta_line } => {
                self.last_event_was_text = false;
                let name = meta_line.type_name.clone();
                let level = self.stack.len();
                self.stack.push(name.clone());
                let prefix = "  ".repeat(level);
                println!("{prefix} <{name}>");
            }
            Event::BlockFinished => {
                self.last_event_was_text = false;
                let name = self.stack.pop().unwrap_or_default();
                let level = self.stack.len();
                let prefix = "  ".repeat(level);
                println!("{prefix} </{name}>");
            }
            Event::TextFinished {
                lines, links: _, ..
            } => {
                debug_assert!(
                    !self.last_event_was_text,
                    "Consecutive TextFinished events detected! Text batching failed."
                );

                self.last_event_was_text = true;
                let prefix = "  ".repeat(self.stack.len());
                println!("{prefix}<text>");
                for line in lines {
                    println!("{prefix}{line}");
                }
                println!("{prefix}</text>");
            }
            Event::ContextLine { meta_line } => {
                self.last_event_was_text = false;
                let prefix = "  ".repeat(self.stack.len());
                println!("{prefix}<context-line type=\"{}\"/>", meta_line.type_name);
            }
            Event::VoidElement { meta_line } => {
                self.last_event_was_text = false;
                let prefix = "  ".repeat(self.stack.len());
                println!("{prefix}<{} void=\"true\"/>", meta_line.type_name);
            }
        }
    }

    fn on_finish(&mut self) {
        if !self.stack.is_empty() {
            println!("Unclosed blocks detected:");
            println!("{:?}", self.stack);
        }
    }
}

fn process_with_event_demo(file_path: &str) -> Result<(), Box<dyn std::error::Error>> {
    use std::path::Path;

    if !Path::new(file_path).exists() {
        return Err(format!("File not found: {file_path}").into());
    }

    let demo_consumer = DemoConsumer::new();
    let emitter = WasotFileEmitter::from_file(file_path);

    let result = run(emitter, demo_consumer);

    if result.is_err() {
        return Err(format!("Error processing file: {file_path}").into());
    }

    Ok(())
}

fn process_with_xml_listener(
    file_path: &str,
    pretty: bool,
) -> Result<(), Box<dyn std::error::Error>> {
    use std::path::Path;
    use std::thread;

    if !Path::new(file_path).exists() {
        return Err(format!("File not found: {file_path}").into());
    }

    let emitter = WasotFileEmitter::from_file(file_path);
    let receiver = emitter.subscribe();

    let emitter_handle = thread::spawn(move || {
        emitter.run();
    });

    let mut xml_listener = XmlListener::new(pretty);
    xml_listener.run(receiver);

    emitter_handle
        .join()
        .map_err(|_| "Emitter thread panicked")?;

    Ok(())
}

fn process_with_protobuf_listener(_file_path: &str) -> Result<(), Box<dyn std::error::Error>> {
    Err("ProtobufListener temporarily disabled during bus migration".into())
}

fn process_facade(file_path: &str) -> Result<(), Box<dyn std::error::Error>> {
    use wasot_compiler::facade::{parse_wasot_file, to_wasot};

    println!("=== Parsing events from file ===");
    let events: Vec<_> = parse_wasot_file(file_path)?
        .map(|r| r.expect("Failed to parse event"))
        .collect();

    for event in &events {
        println!("{event:?}");
    }

    println!("\n=== Reconstructing back to Wasot format ===");
    for line in to_wasot(events.into_iter().map(Ok)) {
        println!("{line}");
    }

    Ok(())
}

fn process_select(
    file_path: Option<&str>,
    selectors: Vec<String>,
) -> Result<(), Box<dyn std::error::Error>> {
    use std::thread;
    use wasot_compiler::SelectorConsumer;

    let mut selector_consumer = SelectorConsumer::new(selectors);

    match file_path {
        Some(path) => {
            use std::path::Path;
            if !Path::new(path).exists() {
                return Err(format!("File not found: {path}").into());
            }

            let file_emitter = WasotFileEmitter::from_file(path);
            let receiver = file_emitter.subscribe();

            let emitter_handle = thread::spawn(move || {
                file_emitter.run();
            });

            let filtered_reader = selector_consumer.subscribe();

            thread::spawn(move || {
                let mut context = wasot_compiler::context::Context::new();
                for event in receiver {
                    selector_consumer.handle_with_context(event, &mut context);
                }
            });

            for event in filtered_reader {
                println!("{event:?}");
            }

            emitter_handle
                .join()
                .map_err(|_| "Emitter thread panicked")?;
        }
        None => {
            let stdin_emitter = WasotFileEmitter::from_reader(io::stdin());
            let receiver = stdin_emitter.subscribe();

            let emitter_handle = thread::spawn(move || {
                stdin_emitter.run();
            });

            let filtered_reader = selector_consumer.subscribe();

            thread::spawn(move || {
                let mut context = wasot_compiler::context::Context::new();
                for event in receiver {
                    selector_consumer.handle_with_context(event, &mut context);
                }
            });

            for event in filtered_reader {
                println!("{event:?}");
            }

            emitter_handle
                .join()
                .map_err(|_| "Emitter thread panicked")?;
        }
    }

    Ok(())
}

fn process_list_actions(
    file_path: &str,
    target_line: usize,
    actions_file: Option<&str>,
) -> Result<(), Box<dyn std::error::Error>> {
    use std::path::Path;
    use std::thread;
    use wasot_compiler::tools::Action;

    if !Path::new(file_path).exists() {
        return Err(format!("File not found: {file_path}").into());
    }

    let actions = match Action::load_actions(actions_file) {
        Ok(loaded_actions) if !loaded_actions.is_empty() => loaded_actions
            .into_iter()
            .map(|a| wasot_compiler::Action {
                id: a.id,
                name: a.name,
                description: a.description,
                selectors: a.selectors,
                handler: wasot_compiler::ActionHandler::Custom(String::new()),
            })
            .collect(),
        Ok(_) | Err(_) => get_sample_actions(),
    };

    let mut action_consumer = ActionConsumer::new(actions)?.with_target_line(target_line);
    let emitter = WasotFileEmitter::from_file(file_path);
    let receiver = emitter.subscribe();

    let emitter_handle = thread::spawn(move || {
        emitter.run();
    });

    let mut context = wasot_compiler::context::Context::new();
    for event in receiver {
        action_consumer.handle_with_context(event, &mut context);
    }

    emitter_handle
        .join()
        .map_err(|_| "Emitter thread panicked")?;

    let matching_actions = action_consumer.get_matching_actions();

    if matching_actions.is_empty() {
        println!("No actions match at line {target_line}");
    } else {
        println!("Actions matching at line {target_line}:");
        println!();
        for action in matching_actions {
            println!("  Action: {}", action.name);
            println!("    ID: {}", action.id);
            println!("    Selectors: {}", action.selectors.join(", "));
            println!("    Description: {}", action.description);
            println!("    Handler: {:?}", action.handler);
            println!();
        }
    }

    Ok(())
}

fn get_document_id(file_path: &str) -> Result<(), Box<dyn std::error::Error>> {
    use std::path::Path;
    use std::thread;

    if !Path::new(file_path).exists() {
        return Err(format!("File not found: {file_path}").into());
    }

    let emitter = WasotFileEmitter::from_file(file_path);
    let receiver = emitter.subscribe();

    let emitter_handle = thread::spawn(move || {
        emitter.run();
    });

    let mut document_id: Option<String> = None;
    for event in receiver {
        if let Event::ContextLine { meta_line } = event {
            if meta_line.type_name == "id" {
                document_id = Some(meta_line.inline_text.content.clone());
                break;
            }
        }
    }

    let _ = emitter_handle.join();

    match document_id {
        Some(id) => {
            println!("{id}");
            Ok(())
        }
        None => Err("No document ID found (no context line of type 'id')".into()),
    }
}

fn process_links(file_path: &str) -> Result<(), Box<dyn std::error::Error>> {
    use std::path::Path;

    if !Path::new(file_path).exists() {
        return Err(format!("File not found: {file_path}").into());
    }

    let emitter = WasotFileEmitter::from_file(file_path);
    let receiver = emitter.subscribe();

    let emitter_handle = std::thread::spawn(move || {
        emitter.run();
    });

    let mut links_printer = LinksPrinter::new();
    links_printer.run(receiver);

    emitter_handle
        .join()
        .map_err(|_| "Emitter thread panicked")?;

    Ok(())
}

fn process_links_demo(
    file_path: Option<&str>,
    output_path: Option<&str>,
) -> Result<(), Box<dyn std::error::Error>> {
    use std::fs;
    use std::io::Write;

    let demo_content = if let Some(path) = file_path {
        if !std::path::Path::new(path).exists() {
            return Err(format!("File not found: {path}").into());
        }
        fs::read_to_string(path)?
    } else {
        r#".document title="Links Demo"

.# New Link Syntax Demonstration

This demonstrates the new link syntax using double parentheses instead of .: and :.

.## Basic Links

Here's a link to ((my website :https://example.com)). And here's a reference to
((section one :section1)). You can also create ((footnote links :*1)).

.## Links with Arguments

You can add arguments to links: ((highlighted text .. class=highlight style=bold :important)).

Also, links can have ((complex names .. color=blue target=_blank :https://github.com/wasot)).

.## Links in Different Contexts

.quote author="((Jane Doe :author123))" source="((The Art of Programming :book456))"
This quote shows links in block arguments.
./

.list
((Item one :item1))
((Item with arguments .. priority=high :item2))
Regular text with ((embedded link :embed1)) in the middle
./

.## Complex Example

Visit ((our documentation .. type=help section=advanced :https://docs.example.com/advanced))
for detailed information. You can also check the ((FAQ :faq)) or contact
((support team .. email=help@example.com :contact)).

Multiple links in one line: ((first :link1)), ((second :*2)), and ((third :https://third.example)).

"#
        .to_string()
    };

    use std::thread;

    let emitter = WasotFileEmitter::from_string(demo_content);
    let receiver = emitter.subscribe();

    let emitter_handle = thread::spawn(move || {
        emitter.run();
    });

    let mut output_buffer = Vec::new();
    {
        let mut reconstructor = WasotReconstructor::new_with_writer(&mut output_buffer);
        for event in receiver {
            reconstructor.on_element_event(&event);
        }
    }

    emitter_handle.join().unwrap();

    let converted_content = String::from_utf8(output_buffer).unwrap();

    match output_path {
        Some(path) => {
            let mut file = fs::File::create(path)?;
            file.write_all(converted_content.as_bytes())?;
            println!("Links demo written to: {path}");
        }
        None => {
            println!("{converted_content}");
        }
    }

    Ok(())
}

fn main() {
    let cli = Cli::parse();

    match &cli.command {
        Commands::Demo { file_path } => {
            if let Err(err) = process_with_event_demo(file_path) {
                eprintln!("Error processing demo: {err}");
                process::exit(1);
            }
        }
        Commands::XmlListener { file_path, pretty } => {
            if let Err(err) = process_with_xml_listener(file_path, *pretty) {
                eprintln!("Error processing with XML listener: {err}");
                process::exit(1);
            }
        }
        Commands::ProtobufListener { file_path } => {
            if let Err(err) = process_with_protobuf_listener(file_path) {
                eprintln!("Error processing with protobuf listener: {err}");
                process::exit(1);
            }
        }
        Commands::ListenDemo { file_path } => {
            if let Err(err) = process_with_event_demo(file_path) {
                eprintln!("Error processing with event demo: {err}");
                process::exit(1);
            }
        }
        Commands::Facade { file_path } => {
            if let Err(err) = process_facade(file_path) {
                eprintln!("Error processing with facade: {err}");
                process::exit(1);
            }
        }
        Commands::Select {
            file_path,
            selector,
        } => {
            if let Err(err) = process_select(file_path.as_deref(), selector.clone()) {
                eprintln!("Error processing selectors: {err}");
                process::exit(1);
            }
        }
        Commands::ListActions {
            file_path,
            line,
            actions_file,
        } => {
            if let Err(err) = process_list_actions(file_path, *line, actions_file.as_deref()) {
                eprintln!("Error listing actions: {err}");
                process::exit(1);
            }
        }
        Commands::GetDocumentId { file_path } => {
            if let Err(err) = get_document_id(file_path) {
                eprintln!("Error: {err}");
                process::exit(1);
            }
        }
        Commands::LinksDemo { file_path, output } => {
            if let Err(err) = process_links_demo(file_path.as_deref(), output.as_deref()) {
                eprintln!("Error processing links demo: {err}");
                process::exit(1);
            }
        }
        Commands::Links { file_path } => {
            if let Err(err) = process_links(file_path) {
                eprintln!("Error processing links: {err}");
                process::exit(1);
            }
        }
    }
}
