#![allow(clippy::too_many_arguments)]
#![allow(clippy::result_large_err)]

//! typedialog - Interactive forms and prompts CLI tool
//!
//! A powerful CLI tool for creating interactive forms and prompts using multiple backends.
//! Works with piped input for batch processing and scripts.

mod commands;

use clap::{Parser, Subcommand};
use serde_json::json;
use std::path::PathBuf;
use std::process::Command;
use typedialog_core::cli_common;
use typedialog_core::config::load_backend_config;
use typedialog_core::prelude::*;
use typedialog_core::prompts;

#[derive(Parser)]
#[command(
    name = "typedialog",
    version,
    about = "Interactive forms and prompts CLI tool with multiple backend support",
    long_about = cli_common::CLI_MAIN_LONG_ABOUT
)]
struct Cli {
    #[command(subcommand)]
    command: Commands,

    /// CLI backend configuration file (TOML)
    ///
    /// If provided, uses this file exclusively.
    /// If not provided, searches: ~/.config/typedialog/cli/{TYPEDIALOG_ENV}.toml → ~/.config/typedialog/cli/config.toml → defaults
    #[arg(global = true, short = 'c', long, value_name = "FILE")]
    config: Option<PathBuf>,

    /// Output format: json, yaml, toml, or text
    #[arg(global = true, short, long, default_value = "text", help = cli_common::FORMAT_FLAG_HELP)]
    format: String,

    /// Output file (if not specified, writes to stdout)
    #[arg(global = true, short, long, help = cli_common::OUT_FLAG_HELP)]
    out: Option<PathBuf>,

    /// Locale override for form localization
    #[arg(global = true, short, long, help = cli_common::LOCALE_FLAG_HELP)]
    locale: Option<String>,
}

#[derive(Subcommand)]
enum Commands {
    /// Prompt for text input
    Text {
        /// Prompt message
        prompt: String,

        /// Default value
        #[arg(short, long)]
        default: Option<String>,

        /// Placeholder text
        #[arg(short, long)]
        placeholder: Option<String>,
    },

    /// Prompt for confirmation (yes/no)
    Confirm {
        /// Prompt message
        prompt: String,

        /// Default value (true/false)
        #[arg(short, long)]
        default: Option<bool>,
    },

    /// Select a single option from a list
    Select {
        /// Prompt message
        prompt: String,

        /// Options to choose from
        options: Vec<String>,

        /// Page size (number of options per page)
        #[arg(short, long)]
        page_size: Option<usize>,

        /// Enable vim mode navigation
        #[arg(long)]
        vim_mode: bool,
    },

    /// Select multiple options from a list
    #[command(name = "multi-select")]
    MultiSelect {
        /// Prompt message
        prompt: String,

        /// Options to choose from
        options: Vec<String>,

        /// Page size (number of options per page)
        #[arg(short, long)]
        page_size: Option<usize>,

        /// Enable vim mode navigation
        #[arg(long)]
        vim_mode: bool,
    },

    /// Prompt for password input (masked)
    Password {
        /// Prompt message
        prompt: String,

        /// Allow showing/hiding password with Ctrl+R
        #[arg(long)]
        with_toggle: bool,
    },

    /// Prompt for custom typed input
    Custom {
        /// Prompt message
        prompt: String,

        /// Type name (i32, f64, ipv4, ipv6, uuid, etc.)
        #[arg(short, long)]
        type_name: String,

        /// Default value
        #[arg(short, long)]
        default: Option<String>,
    },

    /// Prompt for text using an external editor
    Editor {
        /// Prompt message
        prompt: String,

        /// File extension for the temp file
        #[arg(short, long)]
        file_extension: Option<String>,

        /// Default content (text or path to file to load)
        #[arg(short, long)]
        default: Option<String>,
    },

    /// Prompt for date selection using a calendar
    Date {
        /// Prompt message
        prompt: String,

        /// Default date (YYYY-MM-DD format)
        #[arg(short, long)]
        default: Option<String>,

        /// Minimum date (YYYY-MM-DD format)
        #[arg(long)]
        min_date: Option<String>,

        /// Maximum date (YYYY-MM-DD format)
        #[arg(long)]
        max_date: Option<String>,

        /// Week start day (Mon, Tue, Wed, Thu, Fri, Sat, Sun)
        #[arg(long, default_value = "Mon")]
        week_start: String,
    },

    /// Execute interactive form from TOML configuration
    Form {
        /// Path to TOML form configuration file
        config: PathBuf,

        /// Optional path to Nickel template (.ncl.j2) for direct Nickel generation
        #[arg(value_name = "TEMPLATE")]
        template: Option<PathBuf>,

        /// Path to JSON file with default field values
        #[arg(long)]
        defaults: Option<PathBuf>,

        /// Redact sensitive field values (output "[REDACTED]")
        #[arg(long, conflicts_with = "encrypt")]
        redact: bool,

        /// Encrypt sensitive field values
        #[arg(long)]
        encrypt: bool,

        /// Encryption backend (age, rustyvault, sops)
        #[arg(long, default_value = "age", requires = "encrypt")]
        backend: String,

        /// Age: private key file path (default: ~/.age/key.txt)
        #[arg(long)]
        key_file: Option<PathBuf>,

        /// RustyVault: vault address (can also use VAULT_ADDR env var)
        #[arg(long)]
        vault_addr: Option<String>,

        /// RustyVault: vault token (can also use VAULT_TOKEN env var)
        #[arg(long)]
        vault_token: Option<String>,

        /// RustyVault: key path (e.g., "secret/data/myapp")
        #[arg(long)]
        vault_key_path: Option<String>,
    },

    /// Convert Nickel schema to TOML form
    #[command(name = "nickel-to-form")]
    NickelToForm {
        /// Path to Nickel schema file (.ncl)
        schema: PathBuf,

        /// Optional path to current data file (.ncl or .json) for defaults
        #[arg(value_name = "CURRENT_DATA")]
        current_data: Option<PathBuf>,

        /// Flatten nested records into flat field names
        #[arg(long)]
        flatten: bool,

        /// Use semantic grouping for form organization
        #[arg(long)]
        groups: bool,

        /// Generate fragments for large schemas based on @fragment markers
        #[arg(long)]
        fragments: bool,

        /// Generate conditionals from optional fields and boolean dependencies
        #[arg(long)]
        conditionals: bool,

        /// Extract i18n translations from doc comments
        #[arg(long)]
        i18n: bool,

        /// Output directory for generated forms (default: stdout for single file)
        #[arg(long)]
        output: Option<PathBuf>,
    },

    /// Convert form results to Nickel output
    #[command(name = "form-to-nickel")]
    FormToNickel {
        /// Path to TOML form definition
        form: PathBuf,

        /// Path to results JSON file OR Nickel template (.ncl.j2)
        /// - If .json: Read pre-computed results (3-step workflow)
        /// - If .ncl.j2: Execute form + render template (2-step workflow)
        input: PathBuf,

        /// Validate output with nickel typecheck
        #[arg(long)]
        validate: bool,
    },

    /// Render Nickel template with form results
    #[command(name = "nickel-template")]
    NickelTemplate {
        /// Path to Nickel template file (.ncl.j2)
        template: PathBuf,

        /// Path to results JSON file
        results: PathBuf,
    },

    /// Complete roundtrip: .ncl → form → .ncl with preserved validators
    #[command(name = "nickel-roundtrip")]
    NickelRoundtrip {
        /// Path to input Nickel file (.ncl)
        input: PathBuf,

        /// Path to form definition (.toml)
        form: PathBuf,

        /// Path to output Nickel file (.ncl)
        #[arg(long)]
        output: PathBuf,

        /// Optional Jinja2 template file (.ncl.j2) for rendering output
        /// If provided, renders template with form results instead of parsing contracts
        #[arg(long)]
        ncl_template: Option<PathBuf>,

        /// Disable validation with nickel typecheck
        #[arg(long)]
        no_validate: bool,

        /// Print verbose progress messages
        #[arg(short, long)]
        verbose: bool,
    },

    #[command(
        name = "web",
        about = "Web backend\t- Browser-based forms\n\t\tUse: typedialog web form config.toml --port 8080\n\t\tFor options: typedialog web -h"
    )]
    Web,

    #[command(
        name = "tui",
        about = "TUI backend\t- Terminal UI with 3-panel interface\n\t\tUse: typedialog tui config.toml\n\t\tFor options: typedialog tui -h"
    )]
    Tui,

    #[command(
        name = "ai",
        about = "AI backend\t- AI-powered configuration assistant\n\t\tUse: typedialog ai serve\n\t\tFor options: typedialog ai -h"
    )]
    Ai,

    #[command(
        name = "ag",
        about = "Agent backend - Type-safe AI agents\n\t\tUse: typedialog ag run agent.ncl\n\t\tFor options: typedialog ag -h"
    )]
    Ag,

    #[command(
        name = "prov-gen",
        about = "Provisioning generator - Generate provisioning structures\n\t\tUse: typedialog prov-gen generate\n\t\tFor options: typedialog prov-gen -h"
    )]
    ProvGen,
}

/// Dispatch to a specific backend binary
fn dispatch_backend(backend: &str) -> ! {
    let binary_name = match backend {
        "web" => "typedialog-web",
        "tui" => "typedialog-tui",
        "ai" => "typedialog-ai",
        "ag" => "typedialog-ag",
        "prov-gen" => "typedialog-prov-gen",
        _ => {
            eprintln!("Error: Unknown backend '{}'", backend);
            std::process::exit(1);
        }
    };

    // Collect all remaining arguments (skip program name and backend name)
    let args: Vec<String> = std::env::args().skip(2).collect();

    // Execute backend binary with remaining arguments
    match Command::new(binary_name).args(&args).status() {
        Ok(status) => {
            std::process::exit(status.code().unwrap_or(1));
        }
        Err(e) => {
            eprintln!("Error: Failed to execute {}: {}", binary_name, e);
            std::process::exit(1);
        }
    }
}

/// Try to dispatch to backend if first argument is a backend name
fn try_dispatch_to_backend() -> Option<()> {
    let first_arg = std::env::args().nth(1)?;

    match first_arg.as_str() {
        "web" | "tui" | "ai" | "ag" | "prov-gen" => {
            dispatch_backend(&first_arg);
        }
        _ => None,
    }
}

async fn main_impl() -> Result<()> {
    // Initialize tracing subscriber with env filter (respects RUST_LOG)
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
        )
        .init();

    let cli = Cli::parse();

    // Load configuration with CLI override
    let config_path = cli.config.as_deref();
    let _config =
        load_backend_config::<TypeDialogConfig>("cli", config_path, TypeDialogConfig::default())?;

    if let Some(path) = config_path {
        eprintln!("📋 Using config: {}", path.display());
    }

    match cli.command {
        Commands::Text {
            prompt,
            default,
            placeholder,
        } => {
            let result = prompts::text(&prompt, default.as_deref(), placeholder.as_deref())?;
            commands::print_result("value", &result, &cli.format, &cli.out)?;
        }

        Commands::Confirm { prompt, default } => {
            let result = prompts::confirm(&prompt, default, None)?;
            commands::print_result("value", &result.to_string(), &cli.format, &cli.out)?;
        }

        Commands::Select {
            prompt,
            options,
            page_size,
            vim_mode,
        } => {
            let result = prompts::select(&prompt, options, page_size, vim_mode)?;
            commands::print_result("value", &result, &cli.format, &cli.out)?;
        }

        Commands::MultiSelect {
            prompt,
            options,
            page_size,
            vim_mode,
        } => {
            let results = prompts::multi_select(&prompt, options, page_size, vim_mode)?;
            let output = json!(results);
            commands::print_result("values", &output.to_string(), &cli.format, &cli.out)?;
        }

        Commands::Password {
            prompt,
            with_toggle,
        } => {
            let result = prompts::password(&prompt, with_toggle)?;
            commands::print_result("value", &result, &cli.format, &cli.out)?;
        }

        Commands::Custom {
            prompt,
            type_name,
            default,
        } => {
            let result = prompts::custom(&prompt, &type_name, default.as_deref())?;
            commands::print_result("value", &result, &cli.format, &cli.out)?;
        }

        Commands::Editor {
            prompt,
            file_extension,
            default,
        } => {
            let result = prompts::editor(&prompt, file_extension.as_deref(), default.as_deref())?;
            commands::print_result("value", &result, &cli.format, &cli.out)?;
        }

        Commands::Date {
            prompt,
            default,
            min_date,
            max_date,
            week_start,
        } => {
            let result = prompts::date(
                &prompt,
                default.as_deref(),
                min_date.as_deref(),
                max_date.as_deref(),
                &week_start,
            )?;
            commands::print_result("value", &result, &cli.format, &cli.out)?;
        }

        Commands::Form {
            config,
            template,
            defaults,
            redact,
            encrypt,
            backend: encryption_backend,
            key_file,
            vault_addr,
            vault_token,
            vault_key_path,
        } => {
            commands::execute_form(
                config,
                template,
                defaults,
                &cli.format,
                &cli.out,
                &cli.locale,
                redact,
                encrypt,
                encryption_backend,
                key_file,
                vault_addr,
                vault_token,
                vault_key_path,
            )
            .await?;
        }

        Commands::NickelToForm {
            schema,
            current_data,
            flatten,
            groups,
            fragments,
            conditionals,
            i18n,
            output,
        } => {
            commands::nickel_to_form_cmd(
                schema,
                current_data,
                flatten,
                groups,
                fragments,
                conditionals,
                i18n,
                output,
            )?;
        }

        Commands::FormToNickel {
            form,
            input,
            validate,
        } => {
            commands::form_to_nickel_cmd(form, input, &cli.out, validate)?;
        }

        Commands::NickelTemplate { template, results } => {
            commands::nickel_template_cmd(template, results, &cli.out)?;
        }

        Commands::NickelRoundtrip {
            input,
            form,
            output,
            ncl_template,
            no_validate,
            verbose,
        } => {
            commands::nickel_roundtrip_cmd(
                input,
                form,
                output,
                ncl_template,
                !no_validate,
                verbose,
            )?;
        }

        Commands::Web => {
            dispatch_backend("web");
        }

        Commands::Tui => {
            dispatch_backend("tui");
        }

        Commands::Ai => {
            dispatch_backend("ai");
        }

        Commands::Ag => {
            dispatch_backend("ag");
        }

        Commands::ProvGen => {
            dispatch_backend("prov-gen");
        }
    }

    Ok(())
}

fn main() -> Result<()> {
    // Try to dispatch to backend binaries before starting async runtime
    try_dispatch_to_backend();

    // If no backend dispatcher matched, run CLI normally
    let rt = tokio::runtime::Runtime::new()?;
    rt.block_on(main_impl())
}
