// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause

#ifdef CLI11_SINGLE_FILE
#include "CLI11.hpp"
#else
#include "CLI/CLI.hpp"
#endif

#include "catch.hpp"
#include <fstream>
#include <memory>
#include <string>

class SimpleFormatter : public CLI::FormatterBase {
  public:
    SimpleFormatter() : FormatterBase() {}

    std::string make_help(const CLI::App *, std::string, CLI::AppFormatMode) const override {
        return "This is really simple";
    }
};

TEST_CASE("Formatter: Nothing", "[formatter]") {
    CLI::App app{"My prog"};

    app.formatter(std::make_shared<SimpleFormatter>());

    std::string help = app.help();

    CHECK("This is really simple" == help);
}

TEST_CASE("Formatter: NothingLambda", "[formatter]") {
    CLI::App app{"My prog"};

    app.formatter_fn(
        [](const CLI::App *, std::string, CLI::AppFormatMode) { return std::string("This is really simple"); });

    std::string help = app.help();

    CHECK("This is really simple" == help);
}

TEST_CASE("Formatter: OptCustomize", "[formatter]") {
    CLI::App app{"My prog"};

    auto optfmt = std::make_shared<CLI::Formatter>();
    optfmt->column_width(25);
    optfmt->label("REQUIRED", "(MUST HAVE)");
    app.formatter(optfmt);

    int v{0};
    app.add_option("--opt", v, "Something")->required();

    std::string help = app.help();

    CHECK_THAT(help, Contains("(MUST HAVE)"));
    CHECK_THAT(help, Contains("Something"));
    CHECK_THAT(help, Contains("--opt INT"));
    CHECK_THAT(help, Contains("-h,   --help           Print"));
}

TEST_CASE("Formatter: OptCustomizeSimple", "[formatter]") {
    CLI::App app{"My prog"};

    app.get_formatter()->column_width(25);
    app.get_formatter()->label("REQUIRED", "(MUST HAVE)");

    int v{0};
    app.add_option("--opt", v, "Something")->required();

    std::string help = app.help();

    CHECK_THAT(help, Contains("(MUST HAVE)"));
    CHECK_THAT(help, Contains("(MUST HAVE)"));
    CHECK_THAT(help, Contains("Something"));
    CHECK_THAT(help, Contains("--opt INT"));
    CHECK_THAT(help, Contains("-h,   --help           Print"));
}

TEST_CASE("Formatter: OptCustomizeOptionText", "[formatter]") {
    CLI::App app{"My prog"};

    app.get_formatter()->column_width(25);

    int v{0};
    app.add_option("--opt", v, "Something")->option_text("(ARG)");

    std::string help = app.help();

    CHECK_THAT(help, Contains("(ARG)"));
}

TEST_CASE("Formatter: OptBaseExample", "[formatter]") {
    CLI::App app{"My prog"};

    app.get_formatter()->column_width(25);

    std::string v{};
    app.add_option("--opt", v)->default_str("DEFFFF");

    int v2{0};
    app.add_option("-o,--opt2", v2, "this is a description for opt2");

    double v3{0.0};
    app.add_option("-f,-n,--opt3,--option-double", v3, "this is a description for option3");

    app.add_flag("--flag,!--no_flag", "a flag option with a negative flag as well");

    std::string help = app.help();

    CHECK_THAT(help, Contains("DEFFFF"));
    CHECK_THAT(help, Contains("{false"));
}
TEST_CASE("Formatter: OptDefaults", "[formatter]") {
    CLI::App app{"My prog"};

    app.get_formatter()->column_width(25);

    std::string v{};
    app.add_option("--opt", v)->default_str("DEFFFF");

    std::string help = app.help();

    CHECK_THAT(help, Contains("[DEFFFF]"));
    app.get_formatter()->enable_option_defaults(false);

    help = app.help();
    CHECK_THAT(help, !Contains("[DEFFFF]"));
    CHECK(!app.get_formatter()->is_option_defaults_enabled());
}

TEST_CASE("Formatter: OptTypes", "[formatter]") {
    CLI::App app{"My prog"};

    app.get_formatter()->column_width(25);

    std::string v{};
    app.add_option("--opt", v);

    std::string help = app.help();

    CHECK_THAT(help, Contains("TEXT"));
    app.get_formatter()->enable_option_type_names(false);

    help = app.help();
    CHECK_THAT(help, !Contains("TEXT"));
    CHECK(!app.get_formatter()->is_option_type_names_enabled());
}

TEST_CASE("Formatter: FalseFlagExample", "[formatter]") {
    CLI::App app{"My prog"};

    app.get_formatter()->column_width(25);
    app.get_formatter()->label("REQUIRED", "(MUST HAVE)");

    int v{0};
    app.add_flag("--opt,!--no_opt", v, "Something");

    bool flag{false};
    app.add_flag("!-O,--opt2,--no_opt2{false}", flag, "Something else");

    std::string help = app.help();

    CHECK_THAT(help, Contains("--no_opt{false}"));
    CHECK_THAT(help, Contains("--no_opt2{false}"));
    CHECK_THAT(help, Contains("-O{false}"));
}

TEST_CASE("Formatter: FalseFlagExampleDisable", "[formatter]") {
    CLI::App app{"My prog"};

    app.get_formatter()->column_width(25);

    int v{0};
    app.add_flag("--opt,!--no_opt", v, "Something");

    bool flag{false};
    app.add_flag("!-O,--opt2,--no_opt2{false}", flag, "Something else");

    std::string help = app.help();

    CHECK_THAT(help, Contains("--no_opt{false}"));
    CHECK_THAT(help, Contains("--no_opt2{false}"));
    CHECK_THAT(help, Contains("-O{false}"));

    app.get_formatter()->enable_default_flag_values(false);
    CHECK(!app.get_formatter()->is_default_flag_values_enabled());

    help = app.help();
    CHECK_THAT(help, !Contains("{false}"));
}

TEST_CASE("Formatter: AppCustomize", "[formatter]") {
    CLI::App app{"My prog"};
    app.add_subcommand("subcom1", "This");

    auto appfmt = std::make_shared<CLI::Formatter>();
    appfmt->column_width(20);
    appfmt->label("Usage", "Run");
    app.formatter(appfmt);

    app.add_subcommand("subcom2", "That");

    std::string help = app.help();
    CHECK_THAT(help, Contains("Run: [OPTIONS] [SUBCOMMANDS]\n\n"));
    CHECK_THAT(help, Contains("\nSUBCOMMANDS:\n"));
    CHECK_THAT(help, Contains("  subcom1           This\n"));
    CHECK_THAT(help, Contains("  subcom2           That\n"));
}

TEST_CASE("Formatter: AppCustomizeSimple", "[formatter]") {
    CLI::App app{"My prog"};
    app.add_subcommand("subcom1", "This");

    app.get_formatter()->column_width(20);
    app.get_formatter()->label("Usage", "Run");

    app.add_subcommand("subcom2", "That");

    std::string help = app.help();
    CHECK_THAT(help, Contains("Run: [OPTIONS] [SUBCOMMANDS]\n\n"));
    CHECK_THAT(help, Contains("\nSUBCOMMANDS:\n"));
    CHECK_THAT(help, Contains("  subcom1           This\n"));
    CHECK_THAT(help, Contains("  subcom2           That\n"));
}

TEST_CASE("Formatter: AllSub", "[formatter]") {
    CLI::App app{"My prog"};
    CLI::App *sub = app.add_subcommand("subcom", "This");
    sub->add_flag("--insub", "MyFlag");

    std::string help = app.help("", CLI::AppFormatMode::All);
    CHECK_THAT(help, Contains("--insub"));
    CHECK_THAT(help, Contains("subcom"));
}

TEST_CASE("Formatter: AllSubRequired", "[formatter]") {
    CLI::App app{"My prog"};
    CLI::App *sub = app.add_subcommand("subcom", "This");
    sub->add_flag("--insub", "MyFlag");
    sub->required();
    std::string help = app.help("", CLI::AppFormatMode::All);
    CHECK_THAT(help, Contains("--insub"));
    CHECK_THAT(help, Contains("subcom"));
    CHECK_THAT(help, Contains("REQUIRED"));
}

TEST_CASE("Formatter: NamelessSub", "[formatter]") {
    CLI::App app{"My prog"};
    CLI::App *sub = app.add_subcommand("", "This subcommand");
    sub->add_flag("--insub", "MyFlag");

    std::string help = app.help("", CLI::AppFormatMode::Normal);
    CHECK_THAT(help, Contains("--insub"));
    CHECK_THAT(help, Contains("This subcommand"));
}

TEST_CASE("Formatter: NamelessSubInGroup", "[formatter]") {
    CLI::App app{"My prog"};
    CLI::App *sub = app.add_subcommand("", "This subcommand");
    CLI::App *sub2 = app.add_subcommand("sub2", "subcommand2");
    sub->add_flag("--insub", "MyFlag");
    int val{0};
    sub2->add_option("pos", val, "positional");
    sub->group("group1");
    sub2->group("group1");
    std::string help = app.help("", CLI::AppFormatMode::Normal);
    CHECK_THAT(help, Contains("--insub"));
    CHECK_THAT(help, Contains("This subcommand"));
    CHECK_THAT(help, Contains("group1"));
    CHECK_THAT(help, Contains("sub2"));
    CHECK(help.find("pos") == std::string::npos);
}

TEST_CASE("Formatter: Footer", "[formatter]") {
    CLI::App app{"My prog"};
    std::string footer_string{"this is       a test of the footer "
                              "systemsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss to  Pr e  s "
                              " e  r  v  e  SPA C ES"};
    app.footer(footer_string);
    app.add_flag("--option", "MyFlag");
    app.get_formatter()->footer_paragraph_width(50);
    app.get_formatter()->enable_footer_formatting(false);
    CHECK(!app.get_formatter()->is_footer_paragraph_formatting_enabled());
    std::string help = app.help("", CLI::AppFormatMode::Normal);
    CHECK_THAT(help, Contains("is       a"));
    CHECK_THAT(help, Contains("to  Pr e  s  e  r  v  e  SPA C ES"));
    CHECK_THAT(help, Contains(footer_string));

    help = app.help("", CLI::AppFormatMode::Sub);
    CHECK_THAT(help, Contains("is       a"));
    CHECK_THAT(help, Contains("to  Pr e  s  e  r  v  e  SPA C ES"));
    CHECK_THAT(help, Contains(footer_string));

    app.get_formatter()->enable_footer_formatting(true);
    CHECK(app.get_formatter()->is_footer_paragraph_formatting_enabled());
    help = app.help("", CLI::AppFormatMode::Normal);
    CHECK_THAT(help, !Contains("is       a"));
    CHECK_THAT(help, !Contains("to  Pr e  s  e  r  v  e  SPA C ES"));
    CHECK_THAT(help, !Contains(footer_string));

    help = app.help("", CLI::AppFormatMode::Sub);
    CHECK_THAT(help, !Contains("is       a"));
    CHECK_THAT(help, !Contains("to  Pr e  s  e  r  v  e  SPA C ES"));
    CHECK_THAT(help, !Contains(footer_string));
}

TEST_CASE("Formatter: Description", "[formatter]") {
    CLI::App app{"My prog"};
    std::string desc_string{"this is       a test of the footer "
                            "systemsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss to  Pr e  s  "
                            "e  r  v  e  SPA C ES"};
    app.description(desc_string);
    app.add_flag("--option", "MyFlag");
    app.get_formatter()->description_paragraph_width(50);
    app.get_formatter()->enable_description_formatting(false);
    CHECK(!app.get_formatter()->is_description_paragraph_formatting_enabled());
    std::string help = app.help("", CLI::AppFormatMode::Normal);
    CHECK_THAT(help, Contains("is       a"));
    CHECK_THAT(help, Contains("to  Pr e  s  e  r  v  e  SPA C ES"));
    CHECK_THAT(help, Contains(desc_string));

    help = app.help("", CLI::AppFormatMode::Sub);
    CHECK_THAT(help, Contains("is       a"));
    CHECK_THAT(help, Contains("to  Pr e  s  e  r  v  e  SPA C ES"));
    CHECK_THAT(help, Contains(desc_string));

    app.get_formatter()->enable_description_formatting(true);
    CHECK(app.get_formatter()->is_description_paragraph_formatting_enabled());
    help = app.help("", CLI::AppFormatMode::Normal);
    CHECK_THAT(help, !Contains("is       a"));
    CHECK_THAT(help, !Contains("to  Pr e  s  e  r  v  e  SPA C ES"));
    CHECK_THAT(help, !Contains(desc_string));

    help = app.help("", CLI::AppFormatMode::Sub);
    CHECK_THAT(help, !Contains("is       a"));
    CHECK_THAT(help, !Contains("to  Pr e  s  e  r  v  e  SPA C ES"));
    CHECK_THAT(help, !Contains(desc_string));
}

TEST_CASE("Formatter: LongOptionAlignment", "[formatter]") {
    CLI::App app{"My prog"};

    app.get_formatter()->long_option_alignment_ratio(6 / 30.f);

    int v{0};
    app.add_option("--opt", v, "Something");

    std::string help = app.help();

    CHECK_THAT(help, Contains("\n  -h, --help                  Print"));
    CHECK_THAT(help, Contains("\n      --opt INT               Something"));
}

TEST_CASE("Formatter: HiddenPositionalUsage", "[formatter]") {
    // a positional in an empty (hidden) group must not render as an empty "[]" in the usage line
    CLI::App app{"My prog"};
    int v{0};
    app.add_option("pos", v, "a hidden positional")->group("");

    std::string help = app.help();
    CHECK_THAT(help, Contains("Usage:"));
    CHECK_THAT(help, !Contains("[]"));
    // the hidden positional should not appear in the usage line at all
    CHECK_THAT(help, !Contains("pos"));
}

TEST_CASE("Formatter: LongSubcommandName", "[formatter]") {
    // a subcommand name that fills the name column must wrap so its description does not glue onto it
    CLI::App app{"My prog"};
    app.add_subcommand("a-really-really-long-subcommand-name", "subcommand description text");

    std::string help = app.help();
    // name and description must be separated, not concatenated
    CHECK_THAT(help, !Contains("a-really-really-long-subcommand-namesubcommand"));
    CHECK_THAT(help, Contains("a-really-really-long-subcommand-name\n"));
    CHECK_THAT(help, Contains("subcommand description text"));
}

TEST_CASE("Formatter: DescriptionNoFormattingSpacing", "[formatter]") {
    // toggling description formatting off must not introduce an extra blank line before the usage
    CLI::App app{"My prog"};
    app.description("Short desc");

    app.get_formatter()->enable_description_formatting(false);
    std::string help = app.help();
    CHECK_THAT(help, Contains("Short desc\n\n\nUsage"));
    CHECK_THAT(help, !Contains("Short desc\n\n\n\nUsage"));
}
