/*
 * This file is part of LuckPerms, licensed under the MIT License.
 *
 *  Copyright (c) lucko (Luck) <luck@lucko.me>
 *  Copyright (c) contributors
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in all
 *  copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *  SOFTWARE.
 */

package me.lucko.luckperms.common.locale;

import com.google.common.collect.Maps;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.HolderType;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.factory.NodeCommandFactory;
import me.lucko.luckperms.common.plugin.AbstractLuckPermsPlugin;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.plugin.bootstrap.LuckPermsBootstrap;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.storage.StorageMetadata;
import me.lucko.luckperms.common.util.DurationFormatter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.context.Context;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.node.ChatMetaType;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.metadata.types.InheritanceOriginMetadata;
import net.luckperms.api.node.types.ChatMetaNode;
import net.luckperms.api.node.types.InheritanceNode;
import net.luckperms.api.node.types.MetaNode;
import net.luckperms.api.util.Tristate;

import java.text.DecimalFormat;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.OptionalInt;
import java.util.stream.Collectors;

import static net.kyori.adventure.text.Component.empty;
import static net.kyori.adventure.text.Component.join;
import static net.kyori.adventure.text.Component.newline;
import static net.kyori.adventure.text.Component.space;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.translatable;
import static net.kyori.adventure.text.format.NamedTextColor.AQUA;
import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA;
import static net.kyori.adventure.text.format.NamedTextColor.DARK_GRAY;
import static net.kyori.adventure.text.format.NamedTextColor.DARK_GREEN;
import static net.kyori.adventure.text.format.NamedTextColor.DARK_RED;
import static net.kyori.adventure.text.format.NamedTextColor.GOLD;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
import static net.kyori.adventure.text.format.NamedTextColor.RED;
import static net.kyori.adventure.text.format.NamedTextColor.WHITE;
import static net.kyori.adventure.text.format.NamedTextColor.YELLOW;
import static net.kyori.adventure.text.format.Style.style;
import static net.kyori.adventure.text.format.TextDecoration.BOLD;

/**
 * A collection of formatted messages used by the plugin.
 */
public interface Message {

    static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd '@' HH:mm:ss")
            .withZone(ZoneId.systemDefault());

    TextComponent OPEN_BRACKET = Component.text('(');
    TextComponent CLOSE_BRACKET = Component.text(')');
    TextComponent FULL_STOP = Component.text('.');
    
    Component PREFIX_COMPONENT = text()
            .color(GRAY)
            .append(text('['))
            .append(text()
                    .decoration(BOLD, true)
                    .append(text('L', AQUA))
                    .append(text('P', DARK_AQUA))
            )
            .append(text(']'))
            .build();

    static TextComponent prefixed(ComponentLike component) {
        return text()
                .append(PREFIX_COMPONENT)
                .append(space())
                .append(component)
                .build();
    }

    Args1<LuckPermsBootstrap> STARTUP_BANNER = bootstrap -> {
        Component infoLine1 = text()
                .append(text(AbstractLuckPermsPlugin.getPluginName(), DARK_GREEN))
                .append(space())
                .append(text("v" + bootstrap.getVersion(), AQUA))
                .build();

        Component infoLine2 = text()
                .color(DARK_GRAY)
                .append(text("Running on "))
                .append(text(bootstrap.getType().getFriendlyName()))
                .append(text(" - "))
                .append(text(bootstrap.getServerBrand()))
                .build();

        // "        __    "
        // "  |    |__)   "
        // "  |___ |      "

        return joinNewline(
                text()
                        .append(text("       ", AQUA))
                        .append(text(" __    ", DARK_AQUA))
                        .build(),
                text()
                        .append(text("  |    ", AQUA))
                        .append(text("|__)   ", DARK_AQUA))
                        .append(infoLine1)
                        .build(),
                text()
                        .append(text("  |___ ", AQUA))
                        .append(text("|      ", DARK_AQUA))
                        .append(infoLine2)
                        .build(),
                empty()
        );
    };

    Args1<String> VIEW_AVAILABLE_COMMANDS_PROMPT = label -> prefixed(translatable()
            // "&3Use &a/{} help &3to view available commands."
            .key("luckperms.commandsystem.available-commands")
            .color(DARK_AQUA)
            .args(text('/' + label + " help", GREEN))
            .append(FULL_STOP)
    );

    Args0 NO_PERMISSION_FOR_SUBCOMMANDS = () -> prefixed(translatable()
            // "&3You do not have permission to use any sub commands."
            .key("luckperms.commandsystem.no-permission-subcommands")
            .color(DARK_AQUA)
            .append(FULL_STOP)
    );

    Args0 ALREADY_EXECUTING_COMMAND = () -> prefixed(translatable()
            // "&7Another command is being executed, waiting for it to finish..."
            .key("luckperms.commandsystem.already-executing-command")
            .color(GRAY)
    );

    Args0 COMMANDS_DISABLED = () -> prefixed(translatable()
            // "&3LuckPerms commands are disabled."
            .key("luckperms.commandsystem.commands-disabled")
            .color(DARK_AQUA)
            .append(FULL_STOP)
    );

    Args2<String, String> FIRST_TIME_SETUP = (label, username) -> joinNewline(
            // "&3It seems that no permissions have been setup yet!"
            // "&3Before you can use any of the LuckPerms commands in-game, you need to use the console to give yourself access."
            // "&3Open your console and run:"
            // " &3&l> &a{} user {} permission set luckperms.* true"
            // "&3After you've done this, you can begin to define your permission assignments and groups."
            // "&3Don't know where to start? Check here: &7https://luckperms.net/wiki/Usage"
            prefixed(translatable()
                    .key("luckperms.first-time.no-permissions-setup")
                    .color(DARK_AQUA)),
            prefixed(translatable()
                    .key("luckperms.first-time.use-console-to-give-access")
                    .color(DARK_AQUA)
                    .append(FULL_STOP)),
            prefixed(translatable()
                    .key("luckperms.first-time.console-command-prompt")
                    .color(DARK_AQUA)
                    .append(text(':'))),
            prefixed(text()
                    .color(DARK_AQUA)
                    .append(space())
                    .append(text('>', style(BOLD)))
                    .append(space())
                    .append(text(label + " user " + username + " permission set luckperms.* true", GREEN))
                    .append(newline())),
            prefixed(translatable()
                    .key("luckperms.first-time.next-step")
                    .color(DARK_AQUA)
                    .append(FULL_STOP)),
            prefixed(translatable()
                    .key("luckperms.first-time.wiki-prompt")
                    .color(DARK_AQUA)
                    .args(text()
                            .content("https://luckperms.net/wiki/Usage")
                            .color(GRAY)
                            .clickEvent(ClickEvent.openUrl("https://luckperms.net/wiki/Usage"))
                    ))
    );

    Args0 LOADING_DATABASE_ERROR = () -> prefixed(translatable()
            // "&cA database error occurred whilst loading permissions data. Please try again later. If you are a server admin, please check the console for any errors."
            .key("luckperms.login.loading-database-error")
            .color(RED)
            .append(FULL_STOP)
            .append(space())
            .append(translatable("luckperms.login.try-again"))
            .append(FULL_STOP)
            .append(space())
            .append(translatable("luckperms.login.server-admin-check-console-errors"))
            .append(FULL_STOP)
    );

    Args0 LOADING_STATE_ERROR = () -> prefixed(translatable()
            // "&cPermissions data for your user was not loaded during the pre-login stage - unable to continue. Please try again later. If you are a server admin, please check the console for any errors."
            .key("luckperms.login.data-not-loaded-at-pre")
            .color(RED)
            .append(text(" - "))
            .append(translatable("luckperms.login.unable-to-continue"))
            .append(FULL_STOP)
            .append(space())
            .append(translatable("luckperms.login.try-again"))
            .append(FULL_STOP)
            .append(space())
            .append(translatable("luckperms.login.server-admin-check-console-errors"))
            .append(FULL_STOP)
    );

    Args0 LOADING_STATE_ERROR_CB_OFFLINE_MODE = () -> prefixed(translatable()
            // "&cPermissions data for your user was not loaded during the pre-login stage - this is likely due to a conflict between CraftBukkit and the online-mode setting. Please check the server console for more information."
            .key("luckperms.login.data-not-loaded-at-pre")
            .color(RED)
            .append(text(" - "))
            .append(translatable("luckperms.login.craftbukkit-offline-mode-error"))
            .append(FULL_STOP)
            .append(space())
            .append(translatable("luckperms.login.server-admin-check-console-info"))
            .append(FULL_STOP)
    );

    Args0 LOADING_SETUP_ERROR = () -> prefixed(translatable()
            // "&cAn unexpected error occurred whilst setting up your permissions data. Please try again later."
            .key("luckperms.login.unexpected-error")
            .color(RED)
            .append(FULL_STOP)
            .append(space())
            .append(translatable("luckperms.login.try-again"))
            .append(FULL_STOP)
    );

    Args0 OP_DISABLED = () -> prefixed(translatable()
            // "&bThe vanilla OP system is disabled on this server."
            .key("luckperms.opsystem.disabled")
            .color(AQUA)
            .append(FULL_STOP)
    );

    Args0 OP_DISABLED_SPONGE = () -> prefixed(translatable()
            // "&2Please note that Server Operator status has no effect on Sponge permission checks when a permission plugin is installed, you must edit user data directly."
            .key("luckperms.opsystem.sponge-warning")
            .color(DARK_GREEN)
            .append(FULL_STOP)
    );

    Args1<LoggedAction> LOG = action -> joinNewline(
            // "&3LOG &3&l> &8(&e{}&8) [&a{}&8] (&b{}&8)"
            // "&3LOG &3&l> &f{}"
            prefixed(text()
                    .append(translatable("luckperms.logs.actionlog-prefix", DARK_AQUA))
                    .append(space())
                    .append(text('>', DARK_AQUA, BOLD))
                    .append(space())
                    .append(text()
                            .color(DARK_GRAY)
                            .append(OPEN_BRACKET)
                            .append(text(action.getSourceFriendlyString(), YELLOW))
                            .append(CLOSE_BRACKET)
                    )
                    .append(space())
                    .append(text()
                            .color(DARK_GRAY)
                            .append(text('['))
                            .append(text(LoggedAction.getTypeString(action.getTarget().getType()), GREEN))
                            .append(text(']'))
                    )
                    .append(space())
                    .append(text()
                            .color(DARK_GRAY)
                            .append(OPEN_BRACKET)
                            .append(text(action.getTargetFriendlyString(), AQUA))
                            .append(CLOSE_BRACKET)
                    )),
            prefixed(text()
                    .append(translatable("luckperms.logs.actionlog-prefix", DARK_AQUA))
                    .append(space())
                    .append(text('>', DARK_AQUA, BOLD))
                    .append(space())
                    .append(text(action.getDescription(), WHITE)))
    );

    Args3<String, String, Tristate> VERBOSE_LOG_PERMISSION = (target, permission, result) -> prefixed(text()
            // "&3VB &3&l> &a{}&7 - &a{}&7 - {}"
            .append(translatable("luckperms.logs.verbose-prefix", DARK_AQUA))
            .append(space())
            .append(text('>', DARK_AQUA, BOLD))
            .append(space())
            .append(text(target, GREEN))
            .append(text(" - ", GRAY))
            .append(text(permission, GREEN))
            .append(text(" - ", GRAY))
            .append(formatTristate(result))
    );

    Args3<String, String, String> VERBOSE_LOG_META = (target, metaKey, result) -> prefixed(text()
            // "&3VB &3&l> &a{}&7 - &bmeta: &a{}&7 - &7{}"
            .append(translatable("luckperms.logs.verbose-prefix", DARK_AQUA))
            .append(space())
            .append(text('>', DARK_AQUA, BOLD))
            .append(space())
            .append(text(target, GREEN))
            .append(text(" - ", GRAY))
            .append(text("meta: ", AQUA))
            .append(text(metaKey, GREEN))
            .append(text(" - ", GRAY))
            .append(text(result, GRAY))
    );

    Args1<String> VERBOSE_LOG_HOVER_TYPE = type -> translatable()
            // "&aType: &2{}"
            .key("luckperms.logs.verbose.hover.type-key")
            .color(GREEN)
            .append(text(": "))
            .append(text(type, DARK_GREEN))
            .build();

    Args1<String> VERBOSE_LOG_HOVER_ORIGIN = origin -> translatable()
            // "&bOrigin: &2{}"
            .key("luckperms.logs.verbose.hover.origin-key")
            .color(AQUA)
            .append(text(": "))
            .append(text(origin, DARK_GREEN))
            .build();

    Args1<Node> VERBOSE_LOG_HOVER_CAUSE = causeNode -> translatable()
            // "&bCause: {}"
            .key("luckperms.logs.verbose.hover.cause-key")
            .color(AQUA)
            .append(text(": "))
            .apply(builder -> {
                String origin = causeNode.getMetadata(InheritanceOriginMetadata.KEY)
                        .map(o -> o.getOrigin().getName()).orElse("?");

                builder.append(translatable()
                        .key("luckperms.command.generic.permission.check.info.directly")
                        .color(GRAY)
                        .args(
                                text().color(AQUA).content(origin),
                                text(causeNode.getKey(), AQUA),
                                formatBoolean(causeNode.getValue()),
                                formatContextSet(causeNode.getContexts())
                        )
                );
            })
            .build();

    Args1<MetaNode> VERBOSE_LOG_HOVER_CAUSE_META = causeNode -> translatable()
            // "&bCause: {}"
            .key("luckperms.logs.verbose.hover.cause-key")
            .color(AQUA)
            .append(text(": "))
            .apply(builder -> {
                String origin = causeNode.getMetadata(InheritanceOriginMetadata.KEY)
                        .map(o -> o.getOrigin().getName()).orElse("?");

                builder.append(translatable()
                        .key("luckperms.command.generic.permission.check.info.directly")
                        .color(GRAY)
                        .args(
                                text().color(AQUA).content(origin),
                                text(causeNode.getMetaKey(), AQUA),
                                formatColoredValue(causeNode.getMetaValue()),
                                formatContextSet(causeNode.getContexts())
                        )
                );
            })
            .build();

    Args1<ContextSet> VERBOSE_LOG_HOVER_CONTEXT = set -> translatable()
            // "&bContext: {}"
            .key("luckperms.logs.verbose.hover.context-key")
            .color(AQUA)
            .append(text(": "))
            .append(formatContextSet(set))
            .build();

    Args1<String> VERBOSE_LOG_HOVER_THREAD = threadName -> translatable()
            // "&bThread: &f{}"
            .key("luckperms.logs.verbose.hover.thread-key")
            .color(AQUA)
            .append(text(": "))
            .append(text(threadName, WHITE))
            .build();

    Args0 VERBOSE_LOG_HOVER_TRACE_TITLE = () -> translatable()
            // "&bTrace:"
            .key("luckperms.logs.verbose.hover.trace-key")
            .color(AQUA)
            .append(text(':'))
            .build();

    Args1<String> VERBOSE_LOG_HOVER_TRACE_CONTENT = content -> text(content, GRAY);

    Args1<Integer> VERBOSE_LOG_HOVER_TRACE_OVERFLOW = overflow -> text()
            // "&f... and {} more"
            .color(WHITE)
            .content("... ")
            .append(translatable()
                    .key("luckperms.logs.verbose.hover.overflow")
                    .args(text(overflow))
            )
            .build();

    Args1<String> VERBOSE_LOG_HOVER_PROCESSOR = processor -> translatable()
            .key("luckperms.logs.verbose.hover.processor-key")
            .color(AQUA)
            .append(text(": "))
            .append(text(processor, DARK_GREEN))
            .build();

    Args1<String> EXPORT_LOG = msg -> prefixed(text()
            // "&3EXPORT &3&l> &f{}"
            .append(translatable("luckperms.logs.export-prefix", DARK_AQUA))
            .append(space())
            .append(text('>', DARK_AQUA, BOLD))
            .append(space())
            .append(text(msg, WHITE))
    );

    Args1<String> EXPORT_LOG_PROGRESS = msg -> prefixed(text()
            // "&3EXPORT &3&l> &7{}"
            .append(translatable("luckperms.logs.export-prefix", DARK_AQUA))
            .append(space())
            .append(text('>', DARK_AQUA, BOLD))
            .append(space())
            .append(text(msg, GRAY))
    );

    Args0 COMMAND_NOT_RECOGNISED = () -> prefixed(translatable()
            // "&cCommand not recognised."
            .key("luckperms.commandsystem.command-not-recognised")
            .color(RED)
            .append(FULL_STOP)
    );

    Args0 COMMAND_NO_PERMISSION = () -> prefixed(translatable()
            // "&cYou do not have permission to use this command!"
            .key("luckperms.commandsystem.no-permission")
            .color(RED)
    );

    Args2<String, String> MAIN_COMMAND_USAGE_HEADER = (name, usage) -> prefixed(text()
            // "&b{} Sub Commands: &7({} ...)"
            .color(AQUA)
            .append(text(name))
            .append(space())
            .append(translatable("luckperms.commandsystem.usage.sub-commands-header"))
            .append(text(": "))
            .append(text()
                    .color(GRAY)
                    .append(OPEN_BRACKET)
                    .append(text(usage))
                    .append(text(" ..."))
                    .append(CLOSE_BRACKET)
            ));

    Args2<String, Component> COMMAND_USAGE_DETAILED_HEADER = (name, usage) -> joinNewline(
            // "&3&lCommand Usage &3- &b{}"
            // "&b> &7{}"
            prefixed(text()
                    .append(translatable("luckperms.commandsystem.usage.usage-header", DARK_AQUA, BOLD))
                    .append(text(" - ", DARK_AQUA))
                    .append(text(name, AQUA))),
            prefixed(text()
                    .append(text('>', AQUA))
                    .append(space())
                    .append(text().color(GRAY).append(usage)))
    );

    Args0 COMMAND_USAGE_DETAILED_ARGS_HEADER = () -> prefixed(translatable()
            // "&3Arguments:"
            .key("luckperms.commandsystem.usage.arguments-header")
            .color(DARK_AQUA)
            .append(text(':'))
    );

    Args2<Component, Component> COMMAND_USAGE_DETAILED_ARG = (arg, usage) -> prefixed(text()
            // "&b- {}&3 -> &7{}"
            .append(text('-', AQUA))
            .append(space())
            .append(arg)
            .append(text(" -> ", DARK_AQUA))
            .append(text().color(GRAY).append(usage))
    );

    Args1<String> REQUIRED_ARGUMENT = name -> text()
            .color(DARK_GRAY)
            .append(text('<'))
            .append(text(name, GRAY))
            .append(text('>'))
            .build();

    Args1<String> OPTIONAL_ARGUMENT = name -> text()
            .color(DARK_GRAY)
            .append(text('['))
            .append(text(name, GRAY))
            .append(text(']'))
            .build();

    Args1<String> USER_NOT_ONLINE = id -> prefixed(translatable()
            // "&aUser &b{}&a is not online."
            .key("luckperms.command.misc.loading.error.user-not-online")
            .color(GREEN)
            .args(text(id, AQUA))
            .append(FULL_STOP)
    );

    Args1<String> USER_NOT_FOUND = id -> prefixed(translatable()
            // "&cA user for &4{}&c could not be found."
            .key("luckperms.command.misc.loading.error.user-not-found")
            .color(RED)
            .args(text(id, DARK_RED))
            .append(FULL_STOP)
    );

    Args1<String> GROUP_NOT_FOUND = id -> prefixed(translatable()
            // "&cA group named &4{}&c could not be found."
            .key("luckperms.command.misc.loading.error.group-not-found")
            .color(RED)
            .args(text(id, DARK_RED))
            .append(FULL_STOP)
    );

    Args1<String> TRACK_NOT_FOUND = id -> prefixed(translatable()
            // "&cA track named &4{}&c could not be found."
            .key("luckperms.command.misc.loading.error.track-not-found")
            .color(RED)
            .args(text(id, DARK_RED))
            .append(FULL_STOP)
    );

    Args1<User> USER_SAVE_ERROR = user -> prefixed(translatable()
            // "&cThere was an error whilst saving user data for &4{}&c."
            .key("luckperms.command.misc.loading.error.user-save-error")
            .color(RED)
            .args(text().color(DARK_RED).append(user.getFormattedDisplayName()))
            .append(FULL_STOP)
    );

    Args1<Group> GROUP_SAVE_ERROR = group -> prefixed(translatable()
            // "&cThere was an error whilst saving group data for &4{}&c."
            .key("luckperms.command.misc.loading.error.group-save-error")
            .color(RED)
            .args(text().color(DARK_RED).append(group.getFormattedDisplayName()))
            .append(FULL_STOP)
    );

    Args1<String> TRACK_SAVE_ERROR = id -> prefixed(translatable()
            // "&cThere was an error whilst saving track data for &4{}&c."
            .key("luckperms.command.misc.loading.error.track-save-error")
            .color(RED)
            .args(text(id, DARK_RED))
            .append(FULL_STOP)
    );

    Args1<String> USER_INVALID_ENTRY = invalid -> prefixed(translatable()
            // "&4{}&c is not a valid username/uuid."
            .key("luckperms.command.misc.loading.error.user-invalid")
            .color(RED)
            .args(text(invalid, DARK_RED))
            .append(FULL_STOP)
    );

    Args1<String> GROUP_INVALID_ENTRY = invalid -> prefixed(translatable()
            // "&4{}&c is not a valid group name."
            .key("luckperms.command.misc.loading.error.group-invalid")
            .color(RED)
            .args(text(invalid, DARK_RED))
            .append(FULL_STOP)
    );

    Args1<String> TRACK_INVALID_ENTRY = invalid -> prefixed(translatable()
            // "&4{}&c is not a valid track name."
            .key("luckperms.command.misc.loading.error.track-invalid")
            .color(RED)
            .args(text(invalid, DARK_RED))
            .append(FULL_STOP)
    );

    Args2<String, String> VERBOSE_INVALID_FILTER = (invalid, error) -> prefixed(translatable()
            // "&4{}&c is not a valid verbose filter. &7({})"
            .key("luckperms.command.verbose.invalid-filter")
            .color(RED)
            .args(text(invalid, DARK_RED))
            .append(FULL_STOP)
            .append(space())
            .append(text()
                    .color(GRAY)
                    .append(OPEN_BRACKET)
                    .append(text(error))
                    .append(CLOSE_BRACKET)
            )
    );

    Args0 VERBOSE_ON = () -> prefixed(translatable()
            // "&bVerbose logging &aenabled &bfor checks matching &aANY&b."
            .key("luckperms.command.verbose.enabled")
            .color(AQUA)
            .args(translatable("luckperms.command.verbose.enabled-term", GREEN), translatable("luckperms.command.verbose.query-any", GREEN))
            .append(FULL_STOP)
    );

    Args1<String> VERBOSE_ON_QUERY = query -> prefixed(translatable()
            // "&bVerbose logging &aenabled &bfor checks matching &a{}&b."
            .key("luckperms.command.verbose.enabled")
            .color(AQUA)
            .args(translatable("luckperms.command.verbose.enabled-term", GREEN), text(query, GREEN))
            .append(FULL_STOP)
    );

    Args2<String, String> VERBOSE_ON_COMMAND = (user, command) -> prefixed(translatable()
            // "&bForcing &a{}&b to execute command &a/{}&b and reporting all checks made..."
            .key("luckperms.command.verbose.command-exec")
            .color(AQUA)
            .args(text(user, GREEN), text(command, GREEN))
            .append(FULL_STOP)
    );

    Args0 VERBOSE_OFF = () -> prefixed(translatable()
            // "&bVerbose logging &cdisabled&b."
            .key("luckperms.command.verbose.off")
            .color(AQUA)
            .args(translatable("luckperms.command.verbose.disabled-term", RED))
            .append(FULL_STOP)
    );

    Args0 VERBOSE_OFF_COMMAND = () -> prefixed(translatable()
            // "&bCommand execution complete."
            .key("luckperms.command.verbose.command-exec-complete")
            .color(AQUA)
            .append(FULL_STOP)
    );

    Args0 VERBOSE_OFF_COMMAND_NO_CHECKS = () -> joinNewline(
            // &bThe command execution completed, but no permission checks were made.
            // &7This might be because the plugin runs commands in the background (async). You can still use verbose manually to detect checks made like this.
            prefixed(translatable()
                    .key("luckperms.command.verbose.command.no-checks")
                    .color(AQUA)
                    .append(FULL_STOP)),
            prefixed(text()
                    .color(GRAY)
                    .append(translatable("luckperms.command.verbose.command.possibly-async"))
                    .append(FULL_STOP)
                    .append(space())
                    .append(translatable("luckperms.command.verbose.command.try-again-manually"))
                    .append(FULL_STOP))
    );

    Args0 VERBOSE_RECORDING_ON = () -> prefixed(translatable()
            // "&bVerbose recording &aenabled &bfor checks matching &aANY&b."
            .key("luckperms.command.verbose.enabled-recording")
            .color(AQUA)
            .args(translatable("luckperms.command.verbose.enabled-term", GREEN), translatable("luckperms.command.verbose.query-any", GREEN))
            .append(FULL_STOP)
    );

    Args1<String> VERBOSE_RECORDING_ON_QUERY = query -> prefixed(translatable()
            // "&bVerbose recording &aenabled &bfor checks matching &a{}&b."
            .key("luckperms.command.verbose.enabled-recording")
            .color(AQUA)
            .args(translatable("luckperms.command.verbose.enabled-term", GREEN), text(query, GREEN))
            .append(FULL_STOP)
    );

    Args0 VERBOSE_UPLOAD_START = () -> prefixed(translatable()
            // "&bVerbose logging &cdisabled&b, uploading results..."
            .key("luckperms.command.verbose.uploading")
            .color(AQUA)
            .args(translatable("luckperms.command.verbose.disabled-term", RED))
    );

    Args1<String> VERBOSE_RESULTS_URL = url -> joinNewline(
            // "&aVerbose results URL:"
            // <link>
            prefixed(translatable()
                    .key("luckperms.command.verbose.url")
                    .color(GREEN)
                    .append(text(':'))),
            text()
                    .content(url)
                    .color(AQUA)
                    .clickEvent(ClickEvent.openUrl(url))
    );

    Args0 TREE_UPLOAD_START = () -> prefixed(translatable()
            // "&bGenerating permission tree, please wait..."
            .key("luckperms.command.tree.start")
            .color(AQUA)
    );

    Args0 TREE_EMPTY = () -> prefixed(translatable()
            // "&cUnable to generate tree, no results were found."
            .key("luckperms.command.tree.empty")
            .color(RED)
            .append(FULL_STOP)
    );

    Args1<String> TREE_URL = url -> joinNewline(
            // "&aPermission tree URL:"
            // <link>
            prefixed(translatable()
                    .key("luckperms.command.tree.url")
                    .color(GREEN)
                    .append(text(':'))),
            text()
                    .content(url)
                    .color(AQUA)
                    .clickEvent(ClickEvent.openUrl(url))
    );

    Args2<Integer, String> GENERIC_HTTP_REQUEST_FAILURE = (code, message) -> prefixed(text()
            // "&cUnable to communicate with the web app. (response code &4{}&c, message='{}')"
            .color(RED)
            .append(translatable("luckperms.command.misc.webapp-unable-to-communicate"))
            .append(FULL_STOP)
            .append(space())
            .append(text()
                    .append(OPEN_BRACKET)
                    .append(translatable("luckperms.command.misc.response-code-key"))
                    .append(space())
                    .append(text(code))
                    .append(text(", "))
                    .append(translatable("luckperms.command.misc.error-message-key"))
                    .append(text("='"))
                    .append(text(message))
                    .append(text("'"))
                    .append(CLOSE_BRACKET)
            )
    );

    Args0 GENERIC_HTTP_UNKNOWN_FAILURE = () -> prefixed(text()
            // "&cUnable to communicate with the web app. Check the console for errors."
