#![doc = include_str!("../README.md")]

mod conn;
mod utils;
mod cmd {
    #[cfg(feature = "export")]
    pub mod compile;
    pub mod completion;
    pub mod cov;
    #[cfg(feature = "dap")]
    pub mod dap;
    pub mod generate_script;
    pub mod lint;
    pub mod lsp;
    #[cfg(feature = "preview")]
    pub mod preview;
    pub mod query;
    pub mod test;
    pub mod trace_lsp;

    #[cfg(feature = "lock")]
    pub mod doc;
    #[cfg(feature = "lock")]
    pub mod task;
}

use std::sync::LazyLock;

use clap::Parser;
#[cfg(feature = "l10n")]
use tinymist_l10n::{load_translations, set_translations};
use tinymist_std::error::prelude::*;

use crate::cmd::*;
#[cfg(feature = "export")]
use crate::compile::CompileArgs;
use crate::conn::client_root;
use crate::lint::LintArgs;
use crate::utils::*;

#[cfg(feature = "dhat-heap")]
#[global_allocator]
static ALLOC: dhat::Alloc = dhat::Alloc;

/// The runtimes used by the application.
pub struct Runtimes {
    /// The tokio runtime.
    pub tokio_runtime: tokio::runtime::Runtime,
}

impl Default for Runtimes {
    fn default() -> Self {
        let tokio_runtime = tokio::runtime::Builder::new_multi_thread()
            .enable_all()
            .build()
            .unwrap();

        Self { tokio_runtime }
    }
}

static RUNTIMES: LazyLock<Runtimes> = LazyLock::new(Runtimes::default);

#[derive(Debug, Clone, clap::Parser)]
#[clap(name = "tinymist", author, version, about, long_version(tinymist::LONG_VERSION.as_str()))]
struct Args {
    /// Configure log filter of tinymist
    #[clap(long = "log-filter", env = "TINYMIST_LOG")]
    pub log_filter: Option<String>,

    /// Mode of the binary
    #[clap(subcommand)]
    pub cmd: Option<Commands>,
}

#[derive(Debug, Clone, clap::Subcommand)]
#[clap(rename_all = "kebab-case")]
enum Commands {
    /// Probe existence (Nop run)
    Probe,

    /// Run language server
    Lsp(crate::lsp::LspArgs),
    /// Run debug adapter
    #[cfg(feature = "dap")]
    Dap(crate::dap::DapArgs),
    /// Run language server for tracing some typst program.
    #[clap(hide(true))]
    TraceLsp(crate::trace_lsp::TraceLspArgs),

    /// Run language query
    #[clap(hide(true))] // still in development
    #[clap(subcommand)]
    Query(crate::query::QueryCommands),
    /// Run preview server
    #[cfg(feature = "preview")]
    Preview(tinymist::tool::preview::PreviewCliArgs),
    /// Run compile command like `typst-cli compile`
    #[cfg(feature = "export")]
    #[clap(alias = "c")]
    Compile(CompileArgs),
    /// Run Tinymist lint checks
    Lint(LintArgs),

    /// Generate completion script to stdout
    Completion(crate::completion::ShellCompletionArgs),
    /// Generate build script for compilation
    #[clap(hide(true))] // still in development
    GenerateScript(crate::generate_script::GenerateScriptArgs),

    /// Run documents
    #[cfg(feature = "lock")]
    #[clap(hide(true))] // still in development
    #[clap(subcommand)]
    Doc(tinymist::project::DocCommands),
    /// Run tasks
    #[cfg(feature = "lock")]
    #[clap(hide(true))] // still in development
    #[clap(subcommand)]
    Task(crate::task::TaskCommands),

    /// Execute a document and collect coverage
    #[clap(hide(true))] // still in development
    Cov(crate::cov::CovArgs),
    /// Test a document and give summary
    Test(crate::test::TestArgs),
}

/// The main entry point.
fn main() -> Result<()> {
    // The root allocator for heap memory profiling.
    #[cfg(feature = "dhat-heap")]
    let _profiler = dhat::Profiler::new_heap();

    // Parses command line arguments
    let args = Args::parse();
    let cmd = args
        .cmd
        .unwrap_or_else(|| Commands::Lsp(Default::default()));

    // Probes soon to avoid other initializations causing errors
    if matches!(cmd, Commands::Probe) {
        return Ok(());
    }

    // Loads translations
    #[cfg(feature = "l10n")]
    set_translations(load_translations(tinymist_assets::L10N_DATA)?);
    // Starts logging
    let verbose = match &cmd {
        // Short-running commands, usually run from the CLI.
        Commands::Completion(..) | Commands::Probe => false,
        #[cfg(feature = "export")]
        Commands::Compile(..) => false,
        Commands::Lint(..) => false,

        // Long-running commands, usually run from the CLI.
        Commands::Test(test) => test.verbose,
        Commands::Preview(preview) => preview.verbose,

        // Long-running commands, usually run from an editor.
        Commands::Lsp(..) | Commands::Dap(..) => true,

        // Hidden commands.
        Commands::TraceLsp(..)
        | Commands::Query(..)
        | Commands::GenerateScript(..)
        | Commands::Doc(..)
        | Commands::Task(..)
        | Commands::Cov(..) => true,
    };
    let _ = tinymist::init_log(tinymist::InitLogOpts {
        verbose,
        filter: args.log_filter,
        output: None,
    });

    match cmd {
        Commands::Probe => Ok(()),

        Commands::Lsp(args) => crate::lsp::lsp_main(args),
        #[cfg(feature = "dap")]
        Commands::Dap(args) => crate::dap::dap_main(args),
        Commands::TraceLsp(args) => crate::trace_lsp::trace_lsp_main(args),

        Commands::Query(cmds) => crate::query::query_main(cmds),
        #[cfg(feature = "preview")]
        Commands::Preview(args) => block_on(crate::preview::preview_main(args)),
        #[cfg(feature = "export")]
        Commands::Compile(args) => block_on(crate::compile::compile_main(args)),
        Commands::Lint(args) => crate::lint::lint_main(args),

        Commands::Completion(args) => crate::completion::completion_main(args),
        Commands::GenerateScript(args) => crate::generate_script::generate_script_main(args),

        #[cfg(feature = "lock")]
        Commands::Doc(cmds) => crate::doc::doc_main(cmds),
        #[cfg(feature = "lock")]
        Commands::Task(cmds) => crate::task::task_main(cmds),

        Commands::Cov(args) => crate::cov::cov_main(args),
        Commands::Test(args) => block_on(crate::test::test_main(args)),
    }
}
