use std::{error::Error, path::PathBuf};

use clap::{Parser, Subcommand, ValueEnum};
use jpreprocess_dictionary::dictionary::to_dict::JPreprocessDictionaryBuilder;
use lindera::dictionary::{load_fs_dictionary, load_user_dictionary_from_bin};
use lindera_dictionary::{builder::DictionaryBuilder, dictionary::metadata::Metadata};

use crate::dict_query::QueryDict;

mod dict_query;

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand, Debug)]
enum Commands {
    /// Display detailed information on a dictionary
    Inspect {
        /// The Word id to display
        #[arg(short, long)]
        word_id: Option<u32>,

        input: PathBuf,
    },
    /// Build a dictionary for lindera or jpreprocess
    Build {
        /// Build user dictionary
        #[arg(short, long)]
        user: bool,
        /// The serializer to be used
        ///
        /// NOTE: `lindera` doesn't reverse `dict.vals`, but `jpreprocess` does (from v0.14.0), causing segmentation differences.
        /// To align them, build both from the same input and manually copy `dict.vals` from one to the other.
        serializer: Serializer,
        /// The path to the metadata file
        #[arg(short, long)]
        metadata: Option<PathBuf>,

        input: PathBuf,
        /// The directory(system dictionary) or file(user dictionary) to put the dictionary.
        /// For user dictionary, the parent directory of the output file should not exist.
        output: PathBuf,
    },
}

#[derive(Clone, ValueEnum, Debug)]
enum Serializer {
    /// Build lindera dictionary
    Lindera,
    /// Build jpreprocess dictionary
    Jpreprocess,
}

fn main() -> Result<(), Box<dyn Error>> {
    let cli = Cli::parse();

    match cli.command {
        Commands::Inspect { word_id, input } => {
            let is_system_dict = input.is_dir()
                && input.join("dict.wordsidx").exists()
                && input.join("dict.words").exists();
            let is_user_bin_dict =
                input.is_file() && matches!(input.extension(),Some(s) if s.to_str()==Some("bin"));

            if is_system_dict || is_user_bin_dict {
                let dict = if is_system_dict {
                    println!("Lindera/JPreprocess system dictionary.");
                    let dict = load_fs_dictionary(&input)?;
                    QueryDict::System(dict)
                } else {
                    println!("Lindera/JPreprocess user dictionary.");
                    if input.extension().unwrap() != "bin" {
                        eprintln!("User dictionary must be a `.bin` file.");
                        std::process::exit(-1);
                    }

                    let dict = load_user_dictionary_from_bin(&input)?;

                    QueryDict::User(dict)
                };

                let serializer = if let Some(identifier) = dict.identifier() {
                    println!("Dictionary identifier: {}", identifier);
                    if identifier.starts_with("jpreprocess") {
                        Serializer::Jpreprocess
                    } else {
                        Serializer::Lindera
                    }
                } else {
                    println!("No identifier found. Assuming lindera dictionary.");
                    Serializer::Lindera
                };

                if let Some(word_id) = word_id {
                    match serializer {
                        Serializer::Lindera => {
                            let Some(word_details) = dict.get_as_lindera(word_id) else {
                                eprintln!("Word not found");
                                std::process::exit(-1);
                            };
                            for detail in word_details {
                                println!("{}", detail);
                            }
                        }
                        Serializer::Jpreprocess => {
                            let Some(word_details) = dict.get_as_jpreprocess(word_id) else {
                                eprintln!("Word not found");
                                std::process::exit(-1);
                            };
                            println!("{}", word_details.to_str_vec("".to_owned()).join(","));
                        }
                    }
                }
            }
        }
        Commands::Build {
            user,
            serializer: serializer_config,
            metadata: metadata_path,
            input,
            output,
        } => {
            let metadata = if let Some(metadata_path) = metadata_path {
                println!("Loading metadata from {:?}", metadata_path);
                let data = std::fs::read(&metadata_path)?;
                Metadata::load(&data)?
            } else {
                match serializer_config {
                    Serializer::Lindera => {
                        println!("Using default lindera metadata.");
                        Metadata::default()
                    }
                    Serializer::Jpreprocess => {
                        println!("Using default jpreprocess metadata.");
                        JPreprocessDictionaryBuilder::default_metadata()
                    }
                }
            };

            println!("Building dictionary...");
            match serializer_config {
                Serializer::Lindera => {
                    let builder = DictionaryBuilder::new(metadata);

                    if user {
                        builder.build_user_dictionary(&input, &output)?;
                    } else {
                        builder.build_dictionary(&input, &output)?;
                    }
                }
                Serializer::Jpreprocess => {
                    let builder = JPreprocessDictionaryBuilder::new(metadata);

                    if user {
                        builder.build_user_dictionary(&input, &output)?;
                    } else {
                        builder.build_dictionary(&input, &output)?;
                    }
                }
            }
            println!("done.");
        }
    }

    Ok(())
}
