package de.gecheckt.pdf.umbenenner.bootstrap.startup;

import java.util.Optional;

/**
 * Parses the raw {@code String[]} arguments passed to {@code main(String[])}
 * into a structured {@link StartupArgumentsParseResult}.
 * <p>
 * Supported options:
 * <ul>
 *   <li>(none) — GUI startup mode is the default.</li>
 *   <li>{@code --headless} — Activates headless batch/scheduler startup mode.</li>
 *   <li>{@code --config <path>} — Overrides the configuration file path for
 *       either startup mode. The path token immediately follows the option token.</li>
 * </ul>
 * <p>
 * Options may appear in any order. Each option must appear at most once.
 * <p>
 * Invalid usages produce a {@link StartupArgumentsParseResult.Invalid} result:
 * <ul>
 *   <li>{@code --config} without a following path value.</li>
 *   <li>{@code --config} followed by another option token (starting with {@code --})
 *       instead of a path value.</li>
 *   <li>Duplicate occurrence of {@code --headless} or {@code --config}.</li>
 *   <li>Any unrecognised argument token.</li>
 * </ul>
 * <p>
 * This class is stateless and safe for concurrent use once instantiated.
 */
public class CliArgumentParser {
    private static final String OPTION_PREFIX = "Option ";



    private static final String OPTION_HEADLESS = "--headless";
    private static final String OPTION_CONFIG = "--config";

    /**
     * Creates a new {@code CliArgumentParser}.
     */
    public CliArgumentParser() {
        // No state to initialise
    }

    /**
     * Parses the given command-line arguments and returns a structured result.
     * <p>
     * Returns {@link StartupArgumentsParseResult.Valid} when the arguments are
     * syntactically correct. Returns {@link StartupArgumentsParseResult.Invalid}
     * when any argument is unrecognised, duplicated, or when {@code --config}
     * is missing its required path value.
     * <p>
     * The concrete treatment of the configuration path (e.g. whether the file
     * exists) is outside the scope of this parser and is handled downstream
     * by Bootstrap.
     *
     * @param args the raw command-line arguments; must not be {@code null}
     * @return the parse result; never {@code null}
     * @throws NullPointerException if {@code args} is {@code null}
     */
    public StartupArgumentsParseResult parse(String[] args) {
        java.util.Objects.requireNonNull(args, "args must not be null");

        StartupMode mode = StartupMode.GUI;
        Optional<String> configPath = Optional.empty();
        boolean headlessSeen = false;
        boolean configSeen = false;

        int i = 0;
        while (i < args.length) {
            String token = args[i];

            switch (token) {
                case OPTION_HEADLESS -> {
                    if (headlessSeen) {
                        return new StartupArgumentsParseResult.Invalid(
                                "Duplicate option: " + OPTION_HEADLESS);
                    }
                    headlessSeen = true;
                    mode = StartupMode.HEADLESS;
                    i++;
                }
                case OPTION_CONFIG -> {
                    if (configSeen) {
                        return new StartupArgumentsParseResult.Invalid(
                                "Duplicate option: " + OPTION_CONFIG);
                    }
                    StartupArgumentsParseResult validation = validateConfigPathToken(args, i);
                    if (validation != null) {
                        return validation;
                    }
                    configSeen = true;
                    configPath = Optional.of(args[i + 1]);
                    i += 2;
                }
                default -> {
                    return new StartupArgumentsParseResult.Invalid(
                            "Unknown argument: " + token);
                }
            }
        }

        return new StartupArgumentsParseResult.Valid(new StartupArguments(mode, configPath));
    }

    /**
     * Validates that a path token follows the {@code --config} option at position {@code i}.
     *
     * @param args the argument array
     * @param i    the index of the {@code --config} token
     * @return an {@link StartupArgumentsParseResult.Invalid} if the path token is missing or
     *         invalid, {@code null} if the token is acceptable
     */
    private StartupArgumentsParseResult validateConfigPathToken(String[] args, int i) {
        if (i + 1 >= args.length) {
            return new StartupArgumentsParseResult.Invalid(
                    OPTION_PREFIX + OPTION_CONFIG + " requires a path argument but none was provided");
        }
        String pathToken = args[i + 1];
        if (pathToken.startsWith("--")) {
            return new StartupArgumentsParseResult.Invalid(
                    OPTION_PREFIX + OPTION_CONFIG + " requires a path argument, but got option: " + pathToken);
        }
        if (pathToken.isBlank()) {
            return new StartupArgumentsParseResult.Invalid(
                    OPTION_PREFIX + OPTION_CONFIG + " requires a non-blank path argument");
        }
        return null;
    }
}
