use crate::command_prelude::*;

use cargo::CargoResult;
use cargo::core::compiler::future_incompat::OnDiskReports;
use cargo::core::compiler::future_incompat::REPORT_PREAMBLE;
use cargo::drop_println;
use cargo::ops;

pub fn cli() -> Command {
    subcommand("report")
        .about("Generate and display various kinds of reports")
        .after_help(color_print::cstr!(
            "Run `<bright-cyan,bold>cargo help report</>` for more detailed information.\n"
        ))
        .subcommand_required(true)
        .arg_required_else_help(true)
        .subcommand(
            subcommand("future-incompatibilities")
                .alias("future-incompat")
                .about("Reports any crates which will eventually stop compiling")
                .arg(
                    opt(
                        "id",
                        "identifier of the report generated by a Cargo command invocation",
                    )
                    .value_name("id"),
                )
                .arg_package("Package to display a report for")
                .after_help(color_print::cstr!(
                    "Run `<bright-cyan,bold>cargo help report future-incompatibilities</>` for more detailed information.\n"
                )),
        )
        .subcommand(
            subcommand("timings")
                .about("Reports the build timings of previous sessions (unstable)")
                .arg_manifest_path()
                .arg(flag("open", "Opens the timing report in a browser"))
                .arg(opt("id", "Session ID to report on").value_name("ID")),
        )
        .subcommand(
            subcommand("sessions")
                .about("Reports the previous sessions (unstable)")
                .arg_manifest_path()
                .arg(
                    opt("limit", "Limit the number of results")
                        .value_name("N")
                        .value_parser(clap::value_parser!(u64).range(1..))
                        .default_value("10"),
                ),
        )
        .subcommand(
            subcommand("rebuilds")
                .about("Reports rebuild reasons from previous sessions (unstable)")
                .arg_manifest_path()
                .arg(opt("id", "Session ID to report on").value_name("ID")),
        )
}

pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
    match args.subcommand() {
        Some(("future-incompatibilities", args)) => report_future_incompatibilities(gctx, args),
        Some(("timings", args)) => {
            gctx.cli_unstable().fail_if_stable_command(
                gctx,
                "report timings",
                15844,
                "build-analysis",
                gctx.cli_unstable().build_analysis,
            )?;
            let opts = timings_opts(gctx, args)?;
            let ws = args.workspace(gctx).ok();
            ops::report_timings(gctx, ws.as_ref(), opts)?;
            Ok(())
        }
        Some(("sessions", args)) => {
            gctx.cli_unstable().fail_if_stable_command(
                gctx,
                "report sessions",
                15844,
                "build-analysis",
                gctx.cli_unstable().build_analysis,
            )?;
            let ws = args.workspace(gctx).ok();
            let opts = sessions_opts(args)?;
            ops::report_sessions(gctx, ws.as_ref(), opts)?;
            Ok(())
        }
        Some(("rebuilds", args)) => {
            gctx.cli_unstable().fail_if_stable_command(
                gctx,
                "report rebuilds",
                15844,
                "build-analysis",
                gctx.cli_unstable().build_analysis,
            )?;
            let ws = args.workspace(gctx).ok();
            let opts = rebuilds_opts(args)?;
            ops::report_rebuilds(gctx, ws.as_ref(), opts)?;
            Ok(())
        }
        Some((cmd, _)) => {
            unreachable!("unexpected command {}", cmd)
        }
        None => {
            unreachable!("unexpected command")
        }
    }
}

fn report_future_incompatibilities(gctx: &GlobalContext, args: &ArgMatches) -> CliResult {
    let ws = args.workspace(gctx)?;
    let reports = OnDiskReports::load(&ws)?;
    let id = args
        .value_of_u32("id")?
        .unwrap_or_else(|| reports.last_id());
    let krate = args.get_one::<String>("package").map(String::as_str);
    let report = reports.get_report(id, krate)?;
    drop_println!(gctx, "{}", REPORT_PREAMBLE);
    drop(gctx.shell().print_ansi_stdout(report.as_bytes()));
    Ok(())
}

fn timings_opts<'a>(
    gctx: &'a GlobalContext,
    args: &ArgMatches,
) -> CargoResult<ops::ReportTimingsOptions<'a>> {
    let open_result = args.get_flag("open");
    let id = args
        .get_one::<String>("id")
        .map(|s| s.parse())
        .transpose()?;

    Ok(ops::ReportTimingsOptions {
        open_result,
        gctx,
        id,
    })
}

fn sessions_opts(args: &ArgMatches) -> CargoResult<ops::ReportSessionsOptions> {
    let limit = *args.get_one::<u64>("limit").unwrap_or(&10);
    let limit = limit.min(usize::MAX as u64) as usize;

    Ok(ops::ReportSessionsOptions { limit })
}

fn rebuilds_opts(args: &ArgMatches) -> CargoResult<ops::ReportRebuildsOptions> {
    let id = args
        .get_one::<String>("id")
        .map(|s| s.parse())
        .transpose()?;

    Ok(ops::ReportRebuildsOptions { id })
}
