cardinal-app 0.1.0

Thin CLI entrypoint for Cardinal.
mod logging;

use clap::{CommandFactory, Parser, Subcommand, ValueEnum};
use clap_complete::{
    generate,
    shells::{Bash, Elvish, Fish, PowerShell, Zsh},
};

use cardinal_core::parse_command;
use cardinal_tui::{run_tui, validate_runtime_config};

use crate::logging::StructuredLogger;

#[derive(Debug, Parser)]
#[command(name = "cardinal")]
#[command(about = "Command-first terminal mail/calendar workspace")]
struct Cli {
    #[command(subcommand)]
    command: Option<CliCommand>,
}

#[derive(Debug, Subcommand)]
enum CliCommand {
    /// Launch the interactive terminal workspace.
    Tui,
    /// Parse and print a Cardinal prompt command.
    Parse { input: String },
    /// Print MVP health information.
    Doctor,
    /// List canonical command examples.
    #[command(name = "commands")]
    CommandList,
    /// Validate runtime configuration from environment variables.
    Config {
        #[command(subcommand)]
        command: ConfigSubcommand,
    },
    /// Generate a shell completion script.
    Completions { shell: CompletionShell },
    /// Render a man page to stdout.
    Man,
}

#[derive(Debug, Subcommand)]
enum ConfigSubcommand {
    Validate,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
enum CompletionShell {
    Bash,
    Elvish,
    Fish,
    PowerShell,
    Zsh,
}

fn main() {
    let cli = Cli::parse();
    let mut logger = match StructuredLogger::from_env() {
        Ok(logger) => logger,
        Err(error) => {
            eprintln!("error: failed to initialize structured logger: {error}");
            std::process::exit(1);
        }
    };

    logger.event("info", "startup", &[("command", describe_command(&cli))]);
    let exit_code = run(cli, &mut logger);
    logger.event("info", "shutdown", &[("exit_code", exit_code.to_string())]);

    if exit_code != 0 {
        std::process::exit(exit_code);
    }
}

fn run(cli: Cli, logger: &mut StructuredLogger) -> i32 {
    match cli.command {
        Some(CliCommand::Tui) => run_tui_with_logging(logger),
        Some(CliCommand::Parse { input }) => {
            logger.event("info", "parse_command", &[("input", input.clone())]);
            match parse_command(&input) {
                Ok(command) => {
                    println!("{command:#?}");
                    0
                }
                Err(error) => {
                    eprintln!("error: {error}");
                    logger.event(
                        "error",
                        "parse_command_failed",
                        &[("error", error.to_string())],
                    );
                    2
                }
            }
        }
        Some(CliCommand::Doctor) => {
            let report = validate_runtime_config();
            println!("cardinal: pre-alpha scaffold");
            println!("core: command parser available");
            println!("tui: milestone 1-10 scaffold available");
            println!(
                "config: {}",
                if report.has_errors() {
                    "validation errors present"
                } else {
                    "validation clean or warnings only"
                }
            );
            println!("sync: external command integration available");
            println!("packaging: metadata and generation commands present");
            0
        }
        Some(CliCommand::CommandList) => {
            for command in COMMAND_EXAMPLES {
                println!("{command}");
            }
            0
        }
        Some(CliCommand::Config {
            command: ConfigSubcommand::Validate,
        }) => {
            let report = validate_runtime_config();
            println!("{}", report.summary());
            for issue in &report.issues {
                println!("- {:?}: {}", issue.severity, issue.message);
            }
            if report.has_errors() {
                1
            } else {
                0
            }
        }
        Some(CliCommand::Completions { shell }) => {
            logger.event(
                "info",
                "generate_completions",
                &[("shell", format!("{shell:?}"))],
            );
            print_completions(shell);
            0
        }
        Some(CliCommand::Man) => match print_man_page() {
            Ok(()) => 0,
            Err(error) => {
                eprintln!("error: failed to render man page: {error}");
                logger.event(
                    "error",
                    "render_man_page_failed",
                    &[("error", error.to_string())],
                );
                1
            }
        },
        None => run_tui_with_logging(logger),
    }
}

fn run_tui_with_logging(logger: &mut StructuredLogger) -> i32 {
    logger.event("info", "tui_start", &[]);
    match run_tui() {
        Ok(()) => 0,
        Err(error) => {
            eprintln!("error: failed to launch TUI: {error}");
            logger.event("error", "tui_failed", &[("error", error.to_string())]);
            1
        }
    }
}

fn print_completions(shell: CompletionShell) {
    let mut command = Cli::command();
    let binary_name = command.get_name().to_owned();

    match shell {
        CompletionShell::Bash => generate(Bash, &mut command, &binary_name, &mut std::io::stdout()),
        CompletionShell::Elvish => {
            generate(Elvish, &mut command, &binary_name, &mut std::io::stdout())
        }
        CompletionShell::Fish => generate(Fish, &mut command, &binary_name, &mut std::io::stdout()),
        CompletionShell::PowerShell => generate(
            PowerShell,
            &mut command,
            &binary_name,
            &mut std::io::stdout(),
        ),
        CompletionShell::Zsh => generate(Zsh, &mut command, &binary_name, &mut std::io::stdout()),
    }
}

fn print_man_page() -> Result<(), std::io::Error> {
    let command = Cli::command();
    let man = clap_mangen::Man::new(command);
    man.render(&mut std::io::stdout())
}

fn describe_command(cli: &Cli) -> String {
    match cli.command {
        Some(CliCommand::Tui) => "tui".to_owned(),
        Some(CliCommand::Parse { .. }) => "parse".to_owned(),
        Some(CliCommand::Doctor) => "doctor".to_owned(),
        Some(CliCommand::CommandList) => "commands".to_owned(),
        Some(CliCommand::Config { .. }) => "config".to_owned(),
        Some(CliCommand::Completions { .. }) => "completions".to_owned(),
        Some(CliCommand::Man) => "man".to_owned(),
        None => "default-tui".to_owned(),
    }
}

const COMMAND_EXAMPLES: &[&str] = &[
    ":list inboxes",
    ":list mail",
    ":open personal",
    ":open 4",
    ":reply",
    ":reply all",
    ":send",
    ":send confirm",
    ":archive",
    ":delete",
    ":spam",
    ":undo",
    ":calendar today",
    ":agenda week",
    ":event new",
    ":event edit",
    ":event delete",
    ":invite accept",
    ":sync",
    ":search calendar:work interview",
];