use std::io::IsTerminal;

use clap::{Parser, Subcommand};
mod ask;
mod example;
mod pod;

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

#[derive(Subcommand)]
enum Commands {
    /// Ask an interactive question and output the response
    Ask {
        /// The question to ask
        question: String,
        default: Option<String>,
        #[arg(short, long)]
        json: bool,
        #[arg(
            long,
            help = "Allow the user input to be blank, otherwise re-ask again"
        )]
        allow_blank: bool,
        #[arg(
            long,
            value_name = "JSON_ARRAY",
            help = "JSON serialized array of autocompletion strings to allow",
            default_value = "[]"
        )]
        suggestions: Option<String>,
        #[arg(short, long, help = "when canceling, use this exit code instead of 1")]
        cancel_code: Option<u8>,
    },
    /// Ask an interactive yes/no question
    Confirm {
        /// Ask a yes/no question
        question: String,
        /// Default answer yes/no
        default: Option<ask::Confirmation>,
        #[arg(short, long, help = "when canceling, use this exit code instead of 1")]
        cancel_code: Option<u8>,
    },
    /// Choose a single item from a list of choices
    Choose {
        /// Selection prompt
        question: String,
        /// Available choices
        options: Vec<String>,
        /// Default answer
        #[arg(short, long, value_name = "ITEM")]
        default: Option<String>,
        #[arg(short, long)]
        json: bool,
        #[arg(short, long, help = "return result as numeric value")]
        numeric: bool,
        #[arg(short, long, help = "when canceling, use this exit code instead of 1")]
        cancel_code: Option<u8>,
    },
    /// Select multiple items from a list of choices
    Select {
        /// Selection prompt
        question: String,
        /// Available choices
        options: Vec<String>,
        /// Default answer
        #[arg(short, long, value_name = "JSON_ARRAY")]
        default: Option<String>,
        #[arg(short, long)]
        json: bool,
        #[arg(short, long, help = "when canceling, use this exit code instead of 1")]
        cancel_code: Option<u8>,
    },
    /// Choose date
    Date {
        /// Selection prompt
        question: String,
        /// Default answer
        #[arg(default_value = "%Y-%m-%d", long, value_name = "FORMAT")]
        format: Option<String>,
        #[arg(long, value_name = "DATE")]
        default: Option<String>,
        #[arg(long, value_name = "DATE")]
        min_date: Option<String>,
        #[arg(long, value_name = "DATE")]
        max_date: Option<String>,
        #[arg(long, value_name = "DATE")]
        starting_date: Option<String>,
        #[arg(default_value = "sunday", long, value_name = "WEEKDAY")]
        week_start: Option<chrono::Weekday>,
        #[arg(long, value_name = "MESSAGE")]
        help_message: Option<String>,
        #[arg(short, long)]
        json: bool,
        #[arg(short, long, help = "when canceling, use this exit code instead of 1")]
        cancel_code: Option<u8>,
    },
    /// Full text editor box
    Editor {
        /// The question to ask
        message: String,
        #[arg(long, value_name = "TEXT")]
        default: Option<String>,
        #[arg(long, value_name = "MESSAGE")]
        help_message: Option<String>,
        #[arg(long, value_name = "EXTENSION")]
        file_extension: Option<String>,
        #[arg(short, long)]
        json: bool,
        #[arg(short, long, help = "when canceling, use this exit code instead of 1")]
        cancel_code: Option<u8>,
    },
    /// Run external commands from a menu system
    Menu {
        #[arg(value_name = "Menu Heading")]
        heading: String,
        #[arg(value_name = "Entry = command")]
        /// List of entries and commands split with " = "
        entries: Vec<String>,
        #[arg(short, long, value_name = "ENTRY")]
        /// Default answer
        default: Option<String>,
        #[arg(long)]
        /// Quit after the first command is selected+executed
        once: bool,
        #[arg(short, long, help = "when canceling, use this exit code instead of 0")]
        cancel_code: Option<u8>,
    },
    /// Show example Bash scripts that use script-wizard
    Example {
        /// The example to show
        name: Option<String>,
    },
    /// Run as a Babashka pod (speaks the pod protocol on stdin/stdout)
    Pod,
}

fn program() -> Result<u8, u8> {
    let cli = Cli::parse();
    match &cli.command {
        None => {
            if std::io::stdin().is_terminal() {
                // stdin is a TTY and no subcommand given: print help
                use clap::CommandFactory;
                Cli::command().print_help().unwrap();
                eprintln!();
                return Err(1);
            } else {
                // stdin is not a TTY: enter pod mode
                pod::run_pod();
                return Ok(0);
            }
        }
        Some(Commands::Ask {
            question,
            default,
            json,
            allow_blank,
            suggestions,
            cancel_code,
        }) => {
            let response = ask::ask!(
                question,
                default.clone().unwrap_or(String::from("")).as_str(),
                *allow_blank,
                suggestions.clone().unwrap_or("".to_string()).as_str(),
                cancel_code.unwrap_or(1)
            );
            if *json {
                println!(
                    "{}",
                    serde_json::to_string(&response).unwrap_or("".to_string())
                )
            } else {
                println!("{}", response);
            }
            Ok(0)
        }
        Some(Commands::Confirm {
            question,
            default,
            cancel_code,
        }) => match ask::confirm(question, default.clone(), cancel_code.unwrap_or(1)) {
            true => Ok(0),
            false => Err(1),
        },
        Some(Commands::Choose {
            question,
            options,
            default,
            json,
            numeric,
            cancel_code,
        }) => {
            let choice = ask::choose(
                question,
                default.clone().unwrap_or(String::from("")).as_str(),
                options.iter().map(String::as_str).collect(),
                numeric,
                cancel_code.unwrap_or(1),
            );
            if *json {
                println!(
                    "{}",
                    serde_json::to_string(&choice).unwrap_or("".to_string())
                )
            } else {
                println!("{}", choice);
            }
            Ok(0)
        }
        Some(Commands::Select {
            question,
            options,
            default,
            json,
            cancel_code,
        }) => {
            let selections = ask::select(
                question,
                default.clone().unwrap_or("".to_string()).as_str(),
                options.iter().map(String::as_str).collect(),
                cancel_code.unwrap_or(1),
            );
            if *json {
                println!(
                    "{}",
                    serde_json::to_string(&selections).unwrap_or("".to_string())
                )
            } else {
                for t in selections {
                    println!("{}", t);
                }
            }
            Ok(0)
        }
        Some(Commands::Date {
            question,
            default,
            min_date,
            max_date,
            starting_date,
            week_start,
            help_message,
            format,
            json,
            cancel_code,
        }) => {
            let date = ask::date(
                question,
                default.clone().unwrap_or("".to_string()).as_str(),
                min_date.clone().unwrap_or("".to_string()).as_str(),
                max_date.clone().unwrap_or("".to_string()).as_str(),
                starting_date.clone().unwrap_or("".to_string()).as_str(),
                week_start.clone().unwrap_or(chrono::Weekday::Sun),
                help_message.clone().unwrap_or("".to_string()).as_str(),
                format.clone().unwrap_or("".to_string()).as_str(),
                cancel_code.unwrap_or(1),
            );
            if *json {
                println!("{}", serde_json::to_string(&date).unwrap_or("".to_string()))
            } else {
                println!("{}", date);
            }
            Ok(0)
        }
        Some(Commands::Editor {
            message,
            default,
            help_message,
            file_extension,
            json,
            cancel_code,
        }) => {
            let text = ask::editor(
                message,
                default.clone().unwrap_or("".to_string()).as_str(),
                help_message.clone().unwrap_or("".to_string()).as_str(),
                file_extension.clone().unwrap_or("".to_string()).as_str(),
                cancel_code.unwrap_or(1),
            );
            if *json {
                println!("{}", serde_json::to_string(&text).unwrap())
            } else {
                println!("{}", text);
            }
            Ok(0)
        }
        Some(Commands::Example { name }) => {
            let n = name.clone().unwrap_or("".to_string());
            match example::example(&n) {
                Ok(name) => {
                    println!("{}", name);
                    Ok(0)
                }
                Err(e) => {
                    eprintln!("{:?}", e);
                    Err(1)
                }
            }
        }
        Some(Commands::Menu {
            heading,
            entries,
            default,
            once,
            cancel_code
        }) => match ask::menu(heading, entries, default, once, cancel_code.unwrap_or(0)) {
            Ok(2) => Err(2),
            Ok(_) => Ok(0),
            Err(_) => Err(1),
        },
        Some(Commands::Pod) => {
            pod::run_pod();
            Ok(0)
        }
    }
}

fn main() {
    match program() {
        Ok(_) => std::process::exit(0),
        Err(code) => std::process::exit(code.into()),
    };
}
