/**
 * Copyright (c) 2022, Timothy Stack
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * * Neither the name of Timothy Stack nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <queue>

#include "lnav.management_cli.hh"

#include "base/itertools.hh"
#include "base/result.h"
#include "base/string_util.hh"
#include "fmt/format.h"
#include "itertools.similar.hh"
#include "log_format.hh"
#include "log_format_ext.hh"
#include "mapbox/variant.hpp"
#include "regex101.import.hh"
#include "session_data.hh"

using namespace lnav::roles::literals;

namespace lnav {

namespace management {

struct no_subcmd_t {
    CLI::App* ns_root_app{nullptr};
};

inline attr_line_t&
symbol_reducer(const std::string& elem, attr_line_t& accum)
{
    return accum.append("\n   ").append(lnav::roles::symbol(elem));
}

inline attr_line_t&
subcmd_reducer(const CLI::App* app, attr_line_t& accum)
{
    return accum.append("\n \u2022 ")
        .append(lnav::roles::keyword(app->get_name()))
        .append(": ")
        .append(app->get_description());
}

struct subcmd_format_t {
    using action_t = std::function<perform_result_t(const subcmd_format_t&)>;

    CLI::App* sf_format_app{nullptr};
    std::string sf_name;
    CLI::App* sf_regex_app{nullptr};
    std::string sf_regex_name;
    CLI::App* sf_regex101_app{nullptr};
    action_t sf_action;

    subcmd_format_t& set_action(action_t act)
    {
        if (!this->sf_action) {
            this->sf_action = std::move(act);
        }
        return *this;
    }

    Result<std::shared_ptr<log_format>, console::user_message> validate_format()
        const
    {
        if (this->sf_name.empty()) {
            auto um = console::user_message::error(
                "expecting a format name to operate on");
            um.with_note(
                (log_format::get_root_formats()
                 | lnav::itertools::map(&log_format::get_name)
                 | lnav::itertools::sort_with(intern_string_t::case_lt)
                 | lnav::itertools::map(&intern_string_t::to_string)
                 | lnav::itertools::fold(symbol_reducer, attr_line_t{}))
                    .add_header("the available formats are:"));

            return Err(um);
        }

        auto lformat = log_format::find_root_format(this->sf_name.c_str());
        if (lformat == nullptr) {
            auto um = console::user_message::error(
                attr_line_t("unknown format: ")
                    .append(lnav::roles::symbol(this->sf_name)));
            um.with_note(
                (log_format::get_root_formats()
                 | lnav::itertools::map(&log_format::get_name)
                 | lnav::itertools::similar_to(this->sf_name)
                 | lnav::itertools::map(&intern_string_t::to_string)
                 | lnav::itertools::fold(symbol_reducer, attr_line_t{}))
                    .add_header("did you mean one of the following?"));

            return Err(um);
        }

        return Ok(lformat);
    }

    Result<external_log_format*, console::user_message>
    validate_external_format() const
    {
        auto lformat = TRY(this->validate_format());
        auto* ext_lformat = dynamic_cast<external_log_format*>(lformat.get());

        if (ext_lformat == nullptr) {
            return Err(console::user_message::error(
                attr_line_t()
                    .append_quoted(lnav::roles::symbol(this->sf_name))
                    .append(" is an internal format that is not defined in a "
                            "configuration file")));
        }

        return Ok(ext_lformat);
    }

    Result<std::pair<external_log_format*,
                     std::shared_ptr<external_log_format::pattern>>,
           console::user_message>
    validate_regex() const
    {
        auto* ext_lformat = TRY(this->validate_external_format());

        if (this->sf_regex_name.empty()) {
            auto um = console::user_message::error(
                "expecting a regex name to operate on");
            um.with_note(
                ext_lformat->elf_pattern_order
                | lnav::itertools::map(&external_log_format::pattern::p_name)
                | lnav::itertools::map(&intern_string_t::to_string)
                | lnav::itertools::fold(
                    symbol_reducer, attr_line_t{"the available regexes are:"}));

            return Err(um);
        }

        for (const auto& pat : ext_lformat->elf_pattern_order) {
            if (pat->p_name == this->sf_regex_name) {
                return Ok(std::make_pair(ext_lformat, pat));
            }
        }

        auto um = console::user_message::error(
            attr_line_t("unknown regex: ")
                .append(lnav::roles::symbol(this->sf_regex_name)));
        um.with_note(
            (ext_lformat->elf_pattern_order
             | lnav::itertools::map(&external_log_format::pattern::p_name)
             | lnav::itertools::map(&intern_string_t::to_string)
             | lnav::itertools::similar_to(this->sf_regex_name)
             | lnav::itertools::fold(symbol_reducer, attr_line_t{}))
                .add_header("did you mean one of the following?"));

        return Err(um);
    }

    static perform_result_t default_action(const subcmd_format_t& sf)
    {
        auto validate_res = sf.validate_format();
        if (validate_res.isErr()) {
            return {validate_res.unwrapErr()};
        }

        auto lformat = validate_res.unwrap();
        auto* ext_format = dynamic_cast<external_log_format*>(lformat.get());

        attr_line_t ext_details;
        if (ext_format != nullptr) {
            ext_details.append("\n   ")
                .append("Regexes"_h3)
                .append(": ")
                .join(ext_format->elf_pattern_order
                          | lnav::itertools::map(
                              &external_log_format::pattern::p_name)
                          | lnav::itertools::map(&intern_string_t::to_string),
                      VC_ROLE.value(role_t::VCR_SYMBOL),
                      ", ");
        }

        auto um = console::user_message::error(
            attr_line_t("expecting an operation to perform on the ")
                .append(lnav::roles::symbol(sf.sf_name))
                .append(" format"));
        um.with_note(attr_line_t()
                         .append(lnav::roles::symbol(sf.sf_name))
                         .append(": ")
                         .append(lformat->lf_description)
                         .append(ext_details));
        um.with_help(
            sf.sf_format_app->get_subcommands({})
            | lnav::itertools::fold(
                subcmd_reducer, attr_line_t{"the available operations are:"}));

        return {um};
    }

    static perform_result_t default_regex_action(const subcmd_format_t& sf)
    {
        auto validate_res = sf.validate_regex();

        if (validate_res.isErr()) {
            return {validate_res.unwrapErr()};
        }

        auto um = console::user_message::error(
            attr_line_t("expecting an operation to perform on the ")
                .append(lnav::roles::symbol(sf.sf_regex_name))
                .append(" regular expression"));

        um.with_help(attr_line_t{"the available subcommands are:"}.append(
            sf.sf_regex_app->get_subcommands({})
            | lnav::itertools::fold(subcmd_reducer, attr_line_t{})));

        return {um};
    }

    static perform_result_t get_action(const subcmd_format_t& sf)
    {
        auto validate_res = sf.validate_format();

        if (validate_res.isErr()) {
            return {validate_res.unwrapErr()};
        }

        auto format = validate_res.unwrap();

        auto um = console::user_message::raw(
            attr_line_t()
                .append(lnav::roles::symbol(sf.sf_name))
                .append(": ")
                .append(on_blank(format->lf_description, "<no description>")));

        return {um};
    }

    static perform_result_t source_action(const subcmd_format_t& sf)
    {
        auto validate_res = sf.validate_external_format();

        if (validate_res.isErr()) {
            return {validate_res.unwrapErr()};
        }

        auto* format = validate_res.unwrap();

        if (format->elf_format_source_order.empty()) {
            return {
                console::user_message::error(
                    "format is builtin, there is no source file"),
            };
        }

        auto um = console::user_message::raw(
            format->elf_format_source_order[0].string());

        return {um};
    }

    static perform_result_t sources_action(const subcmd_format_t& sf)
    {
        auto validate_res = sf.validate_external_format();

        if (validate_res.isErr()) {
            return {validate_res.unwrapErr()};
        }

        auto* format = validate_res.unwrap();

        if (format->elf_format_source_order.empty()) {
            return {
                console::user_message::error(
                    "format is builtin, there is no source file"),
            };
        }

        auto um = console::user_message::raw(
            attr_line_t().join(format->elf_format_source_order,
                               VC_ROLE.value(role_t::VCR_TEXT),
                               "\n"));

        return {um};
    }

    static perform_result_t regex101_pull_action(const subcmd_format_t& sf)
    {
        auto validate_res = sf.validate_regex();
        if (validate_res.isErr()) {
            return {validate_res.unwrapErr()};
        }

        auto format_regex_pair = validate_res.unwrap();
        auto get_meta_res
            = lnav::session::regex101::get_entry(sf.sf_name, sf.sf_regex_name);

        return get_meta_res.match(
            [&sf](
                const lnav::session::regex101::error& err) -> perform_result_t {
                return {
                    console::user_message::error(
                        attr_line_t("unable to get DB entry for: ")
                            .append(lnav::roles::symbol(sf.sf_name))
                            .append("/")
                            .append(lnav::roles::symbol(sf.sf_regex_name)))
                        .with_reason(err.e_msg),
                };
            },
            [&sf](
                const lnav::session::regex101::no_entry&) -> perform_result_t {
                return {
                    console::user_message::error(
                        attr_line_t("regex ")
                            .append_quoted(
                                lnav::roles::symbol(sf.sf_regex_name))
                            .append(" of format ")
                            .append_quoted(lnav::roles::symbol(sf.sf_name))
                            .append(" has not been pushed to regex101.com"))
                        .with_help(
                            attr_line_t("use the ")
                                .append_quoted("push"_keyword)
                                .append(" subcommand to create the regex on "
                                        "regex101.com for easy editing")),
                };
            },
            [&](const lnav::session::regex101::entry& en) -> perform_result_t {
                auto retrieve_res = regex101::client::retrieve(en.re_permalink);

                return retrieve_res.match(
                    [&](const console::user_message& um) -> perform_result_t {
                        return {
                            console::user_message::error(
                                attr_line_t("unable to retrieve entry ")
                                    .append_quoted(
                                        lnav::roles::symbol(en.re_permalink))
                                    .append(" from regex101.com"))
                                .with_reason(um),
                        };
                    },
                    [&](const regex101::client::no_entry&) -> perform_result_t {
                        lnav::session::regex101::delete_entry(sf.sf_name,
                                                              sf.sf_regex_name);
                        return {
                            console::user_message::error(
                                attr_line_t("entry ")
                                    .append_quoted(
                                        lnav::roles::symbol(en.re_permalink))
                                    .append(
                                        " no longer exists on regex101.com"))
                                .with_help(attr_line_t("use the ")
                                               .append_quoted("delete"_keyword)
                                               .append(" subcommand to delete "
                                                       "the association")),
                        };
                    },
                    [&](const regex101::client::entry& remote_entry)
                        -> perform_result_t {
                        auto curr_entry = regex101::convert_format_pattern(
                            format_regex_pair.first, format_regex_pair.second);

                        if (curr_entry.e_regex == remote_entry.e_regex) {
                            return {
                                console::user_message::ok(
                                    attr_line_t("local regex is in sync "
                                                "with entry ")
                                        .append_quoted(lnav::roles::symbol(
                                            en.re_permalink))
                                        .append(" on regex101.com"))
                                    .with_help(
                                        attr_line_t("make edits on ")
                                            .append_quoted(lnav::roles::file(
                                                regex101::client::to_edit_url(
                                                    en.re_permalink)))
                                            .append(" and then run this "
                                                    "command again to update "
                                                    "the local values")),
                            };
                        }

                        auto patch_res
                            = regex101::patch(format_regex_pair.first,
                                              sf.sf_regex_name,
                                              remote_entry);

                        if (patch_res.isErr()) {
                            return {
                                console::user_message::error(
                                    attr_line_t(
                                        "unable to patch format regex: ")
                                        .append(lnav::roles::symbol(sf.sf_name))
                                        .append("/")
                                        .append(lnav::roles::symbol(
                                            sf.sf_regex_name)))
                                    .with_reason(patch_res.unwrapErr()),
                            };
                        }

                        auto um = console::user_message::ok(
                            attr_line_t("format patch file written to: ")
                                .append(lnav::roles::file(
                                    patch_res.unwrap().string())));
                        if (!format_regex_pair.first->elf_builtin_format) {
                            um.with_help(
                                attr_line_t("once the regex has been found "
                                            "to be working correctly, move the "
                                            "contents of the patch file to the "
                                            "original file at:\n   ")
                                    .append(lnav::roles::file(
                                        format_regex_pair.first
                                            ->elf_format_source_order.front()
                                            .string())));
                        }

                        return {um};
                    });
            });
    }

    static perform_result_t regex101_default_action(const subcmd_format_t& sf)
    {
        auto validate_res = sf.validate_regex();

        if (validate_res.isErr()) {
            return {validate_res.unwrapErr()};
        }

        auto um = console::user_message::error(
            attr_line_t("expecting an operation to perform on the ")
                .append(lnav::roles::symbol(sf.sf_regex_name))
                .append(" regex using regex101.com"));

        auto get_res
            = lnav::session::regex101::get_entry(sf.sf_name, sf.sf_regex_name);
        if (get_res.is<lnav::session::regex101::entry>()) {
            auto local_entry = get_res.get<lnav::session::regex101::entry>();
            um.with_note(
                attr_line_t("this regex is currently associated with the "
                            "following regex101.com entry:\n   ")
                    .append(lnav::roles::file(regex101::client::to_edit_url(
                        local_entry.re_permalink))));
        }

        um.with_help(attr_line_t{"the available subcommands are:"}.append(
            sf.sf_regex101_app->get_subcommands({})
            | lnav::itertools::fold(subcmd_reducer, attr_line_t{})));

        return {um};
    }

    static perform_result_t regex101_push_action(const subcmd_format_t& sf)
    {
        auto validate_res = sf.validate_regex();
        if (validate_res.isErr()) {
            return {validate_res.unwrapErr()};
        }

        auto format_regex_pair = validate_res.unwrap();
        auto entry = regex101::convert_format_pattern(format_regex_pair.first,
                                                      format_regex_pair.second);
        auto get_meta_res
            = lnav::session::regex101::get_entry(sf.sf_name, sf.sf_regex_name);

        if (get_meta_res.is<lnav::session::regex101::entry>()) {
            auto entry_meta
                = get_meta_res.get<lnav::session::regex101::entry>();
            auto retrieve_res
                = regex101::client::retrieve(entry_meta.re_permalink);

            if (retrieve_res.is<regex101::client::entry>()) {
                auto remote_entry = retrieve_res.get<regex101::client::entry>();

                if (remote_entry == entry) {
                    return {
                        console::user_message::ok(
                            attr_line_t("regex101 entry ")
                                .append(lnav::roles::symbol(
                                    entry_meta.re_permalink))
                                .append(" is already up-to-date")),
                    };
                }
            } else if (retrieve_res.is<console::user_message>()) {
                return {
                    retrieve_res.get<console::user_message>(),
                };
            }

            entry.e_permalink_fragment = entry_meta.re_permalink;
        }

        auto upsert_res = regex101::client::upsert(entry);
        auto upsert_info = upsert_res.unwrap();

        if (get_meta_res.is<lnav::session::regex101::no_entry>()) {
            lnav::session::regex101::insert_entry({
                format_regex_pair.first->get_name().to_string(),
                format_regex_pair.second->p_name.to_string(),
                upsert_info.cr_permalink_fragment,
                upsert_info.cr_delete_code,
            });
        }

        return {
            console::user_message::ok(
                attr_line_t("pushed regex to -- ")
                    .append(lnav::roles::file(regex101::client::to_edit_url(
                        upsert_info.cr_permalink_fragment))))
                .with_help(attr_line_t("use the ")
                               .append_quoted("pull"_keyword)
                               .append(" subcommand to update the format after "
                                       "you make changes on regex101.com")),
        };
    }

    static perform_result_t regex101_delete_action(const subcmd_format_t& sf)
    {
        auto get_res
            = lnav::session::regex101::get_entry(sf.sf_name, sf.sf_regex_name);

        return get_res.match(
            [&sf](
                const lnav::session::regex101::entry& en) -> perform_result_t {
                {
                    auto validate_res = sf.validate_external_format();

                    if (validate_res.isOk()) {
                        auto ppath = regex101::patch_path(validate_res.unwrap(),
                                                          en.re_permalink);

                        if (ghc::filesystem::exists(ppath)) {
                            return {
                                console::user_message::error(
                                    attr_line_t("cannot delete regex101 entry "
                                                "while patch file exists"))
                                    .with_note(attr_line_t("  ").append(
                                        lnav::roles::file(ppath.string())))
                                    .with_help(attr_line_t(
                                        "move the contents of the patch file "
                                        "to the main log format and then "
                                        "delete the file to continue")),
                            };
                        }
                    }
                }

                perform_result_t retval;
                if (en.re_delete_code.empty()) {
                    retval.emplace_back(
                        console::user_message::warning(
                            attr_line_t("not deleting regex101 entry ")
                                .append_quoted(
                                    lnav::roles::symbol(en.re_permalink)))
                            .with_reason(
                                "delete code is not known for this entry")
                            .with_note(
                                "formats created by importing a regex101.com "
                                "entry will not have a delete code"));
                } else {
                    auto delete_res
                        = regex101::client::delete_entry(en.re_delete_code);

                    if (delete_res.isErr()) {
                        return {
                            console::user_message::error(
                                "unable to delete regex101 entry")
                                .with_reason(delete_res.unwrapErr()),
                        };
                    }
                }

                lnav::session::regex101::delete_entry(sf.sf_name,
                                                      sf.sf_regex_name);

                retval.emplace_back(console::user_message::ok(
                    attr_line_t("deleted regex101 entry: ")
                        .append(lnav::roles::symbol(en.re_permalink))));

                return retval;
            },
            [&sf](
                const lnav::session::regex101::no_entry&) -> perform_result_t {
                return {
                    console::user_message::error(
                        attr_line_t("no regex101 entry for ")
                            .append(lnav::roles::symbol(sf.sf_name))
                            .append("/")
                            .append(lnav::roles::symbol(sf.sf_regex_name))),
                };
            },
            [&sf](
                const lnav::session::regex101::error& err) -> perform_result_t {
                return {
                    console::user_message::error(
                        attr_line_t("unable to get regex101 entry for ")
                            .append(lnav::roles::symbol(sf.sf_name))
                            .append("/")
                            .append(lnav::roles::symbol(sf.sf_regex_name)))
                        .with_reason(err.e_msg),
                };
            });
    }
};

struct subcmd_regex101_t {
    using action_t = std::function<perform_result_t(const subcmd_regex101_t&)>;

    CLI::App* sr_app{nullptr};
    action_t sr_action;
    std::string sr_import_url;
    std::string sr_import_name;
    std::string sr_import_regex_name{"std"};

    subcmd_regex101_t& set_action(action_t act)
    {
        if (!this->sr_action) {
            this->sr_action = std::move(act);
        }
        return *this;
    }

    static perform_result_t default_action(const subcmd_regex101_t& sr)
    {
        auto um = console::user_message::error(
            "expecting an operation related to the regex101.com integration");
        um.with_help(
            sr.sr_app->get_subcommands({})
            | lnav::itertools::fold(
                subcmd_reducer, attr_line_t{"the available operations are:"}));

        return {um};
    }

    static perform_result_t list_action(const subcmd_regex101_t&)
    {
        auto get_res = lnav::session::regex101::get_entries();

        if (get_res.isErr()) {
            return {
                console::user_message::error(
                    "unable to read regex101 entries from DB")
                    .with_reason(get_res.unwrapErr()),
            };
        }

        auto entries
            = get_res.unwrap() | lnav::itertools::map([](const auto& elem) {
                           return fmt::format(
                               FMT_STRING("   format {} regex {} regex101\n"),
                               elem.re_format_name,
                               elem.re_regex_name);
                       })
            | lnav::itertools::fold(
                  [](const auto& elem, auto& accum) {
                      return accum.append(elem);
                  },
                  attr_line_t{});

        auto um = console::user_message::ok(
            entries.add_header("the following regex101 entries were found:\n")
                .with_default("no regex101 entries found"));

        return {um};
    }

    static perform_result_t import_action(const subcmd_regex101_t& sr)
    {
        auto import_res = regex101::import(
            sr.sr_import_url, sr.sr_import_name, sr.sr_import_regex_name);

        if (import_res.isOk()) {
            return {
                lnav::console::user_message::ok(
                    attr_line_t("converted regex101 entry to format file: ")
                        .append(lnav::roles::file(import_res.unwrap())))
                    .with_note("the converted format may still have errors")
                    .with_help(
                        attr_line_t(
                            "use the following command to patch the regex as "
                            "more changes are made on regex101.com:\n")
                            .appendf(FMT_STRING("   lnav -m format {} regex {} "
                                                "regex101 pull"),
                                     sr.sr_import_name,
                                     sr.sr_import_regex_name)),
            };
        }

        return {
            import_res.unwrapErr(),
        };
    }
};

using operations_v
    = mapbox::util::variant<no_subcmd_t, subcmd_format_t, subcmd_regex101_t>;

class operations {
public:
    operations_v o_ops;
};

std::shared_ptr<operations>
describe_cli(CLI::App& app, int argc, char* argv[])
{
    auto retval = std::make_shared<operations>();

    retval->o_ops = no_subcmd_t{
        &app,
    };

    app.add_flag("-m", "Switch to the management CLI mode.");

    subcmd_format_t format_args;
    subcmd_regex101_t regex101_args;

    {
        auto* subcmd_format
            = app.add_subcommand("format",
                                 "perform operations on log file formats")
                  ->callback([&]() {
                      format_args.set_action(subcmd_format_t::default_action);
                      retval->o_ops = format_args;
                  });
        format_args.sf_format_app = subcmd_format;
        subcmd_format
            ->add_option(
                "format_name", format_args.sf_name, "the name of the format")
            ->expected(1);

        {
            subcmd_format
                ->add_subcommand("get", "print information about a format")
                ->callback([&]() {
                    format_args.set_action(subcmd_format_t::get_action);
                });
        }

        {
            subcmd_format
                ->add_subcommand("source",
                                 "print the path of the first source file "
                                 "containing this format")
                ->callback([&]() {
                    format_args.set_action(subcmd_format_t::source_action);
                });
        }

        {
            subcmd_format
                ->add_subcommand("sources",
                                 "print the paths of all source files "
                                 "containing this format")
                ->callback([&]() {
                    format_args.set_action(subcmd_format_t::sources_action);
                });
        }

        {
            auto* subcmd_format_regex
                = subcmd_format
                      ->add_subcommand(
                          "regex",
                          "operate on the format's regular expressions")
                      ->callback([&]() {
                          format_args.set_action(
                              subcmd_format_t::default_regex_action);
                      });
            format_args.sf_regex_app = subcmd_format_regex;
            subcmd_format_regex->add_option(
                "regex-name",
                format_args.sf_regex_name,
                "the name of the regular expression to operate on");

            {
                auto* subcmd_format_regex_regex101
                    = subcmd_format_regex
                          ->add_subcommand("regex101",
                                           "use regex101.com to edit this "
                                           "regular expression")
                          ->callback([&]() {
                              format_args.set_action(
                                  subcmd_format_t::regex101_default_action);
                          });
                format_args.sf_regex101_app = subcmd_format_regex_regex101;

                {
                    subcmd_format_regex_regex101
                        ->add_subcommand("push",
                                         "create/update an entry for "
                                         "this regex on regex101.com")
                        ->callback([&]() {
                            format_args.set_action(
                                subcmd_format_t::regex101_push_action);
                        });
                    subcmd_format_regex_regex101
                        ->add_subcommand(
                            "pull",
                            "create a patch format file for this "
                            "regular expression based on the entry in "
                            "regex101.com")
                        ->callback([&]() {
                            format_args.set_action(
                                subcmd_format_t::regex101_pull_action);
                        });
                    subcmd_format_regex_regex101
                        ->add_subcommand(
                            "delete",
                            "delete the entry regex101.com that was "
                            "created by a push operation")
                        ->callback([&]() {
                            format_args.set_action(
                                subcmd_format_t::regex101_delete_action);
                        });
                }
            }
        }
    }

    {
        auto* subcmd_regex101
            = app.add_subcommand("regex101",
                                 "create and edit log message regular "
                                 "expressions using regex101.com")
                  ->callback([&]() {
                      regex101_args.set_action(
                          subcmd_regex101_t::default_action);
                      retval->o_ops = regex101_args;
                  });
        regex101_args.sr_app = subcmd_regex101;

        {
            subcmd_regex101
                ->add_subcommand("list",
                                 "list the log format regular expression "
                                 "linked to entries on regex101.com")
                ->callback([&]() {
                    regex101_args.set_action(subcmd_regex101_t::list_action);
                });
        }
        {
            auto* subcmd_regex101_import
                = subcmd_regex101
                      ->add_subcommand("import",
                                       "create a new format from a regular "
                                       "expression on regex101.com")
                      ->callback([&]() {
                          regex101_args.set_action(
                              subcmd_regex101_t::import_action);
                      });

            subcmd_regex101_import->add_option(
                "url",
                regex101_args.sr_import_url,
                "The regex101.com url to construct a log format from");
            subcmd_regex101_import->add_option("name",
                                               regex101_args.sr_import_name,
                                               "The name for the log format");
            subcmd_regex101_import
                ->add_option("regex-name",
                             regex101_args.sr_import_regex_name,
                             "The name for the new regex")
                ->always_capture_default();
        }
    }

    app.parse(argc, argv);

    return retval;
}

perform_result_t
perform(std::shared_ptr<operations> opts)
{
    return opts->o_ops.match(
        [](const no_subcmd_t& ns) -> perform_result_t {
            auto um = console::user_message::error(
                attr_line_t("expecting an operation to perform"));
            um.with_help(ns.ns_root_app->get_subcommands({})
                         | lnav::itertools::fold(
                             subcmd_reducer,
                             attr_line_t{"the available operations are:"}));

            return {um};
        },
        [](const subcmd_format_t& sf) { return sf.sf_action(sf); },
        [](const subcmd_regex101_t& sr) { return sr.sr_action(sr); });
}

}  // namespace management
}  // namespace lnav
