/* SPDX-License-Identifier: GPL-2.0-only */

#include <ppm/ppm.h>

#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "cli.h"
#include "cmd/common.h"
#include "util/log.h"

typedef int (*cmd_fn)(int, char **);

struct subcommand {
    const char *name;
    const char *summary;
    cmd_fn      fn;
};

static const struct subcommand commands[] = {
    { "init",    "scaffold a ppm.toml in the current directory",      ppm_cmd_init    },
    { "search",  "search the registry for packages",                  ppm_cmd_search  },
    { "info",    "display the manifest for a package",                ppm_cmd_info    },
    { "install", "resolve, fetch, verify, and link packages",         ppm_cmd_install },
    { "remove",  "remove packages from the active profile",           ppm_cmd_remove  },
    { "update",  "fast-forward the registry and refresh the lockfile",ppm_cmd_update  },
    { "list",    "list packages in the active profile",               ppm_cmd_list    },
    { "verify",  "check the store layout for malformed entries",       ppm_cmd_verify  },
    { "gc",      "garbage-collect unreferenced store entries",        ppm_cmd_gc      },
};

static void print_usage(FILE *out) {
    fputs(
        "usage: ppm [--version] [--help] [-v|--verbose] [-q|--quiet] <command> [args...]\n"
        "\n"
        "ppm — Project-Tick Package Manager.\n"
        "  A small, fast, boring package manager that refuses to participate\n"
        "  in dependency hell.\n"
        "\n"
        "commands:\n",
        out);
    for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) {
        fprintf(out, "  %-10s  %s\n", commands[i].name, commands[i].summary);
    }
    fputs(
        "\n"
        "Run 'ppm <command> --help' for command-specific help.\n"
        "Report bugs at: https://git.projecttick.org/project-tick\n",
        out);
}

static const struct subcommand *find_command(const char *name) {
    for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) {
        if (strcmp(commands[i].name, name) == 0) return &commands[i];
    }
    return NULL;
}

int main(int argc, char **argv) {
    /* Parse global options before the subcommand name. */
    static const struct option opts[] = {
        { "help",    no_argument, NULL, 'h' },
        { "version", no_argument, NULL, 'V' },
        { "verbose", no_argument, NULL, 'v' },
        { "quiet",   no_argument, NULL, 'q' },
        { 0, 0, 0, 0 },
    };

    /*
     * Stop scanning at the first non-option so that the subcommand can
     * parse its own flags. The leading '+' in optstring enables that.
     */
    int c;
    while ((c = getopt_long(argc, argv, "+hVvq", opts, NULL)) != -1) {
        switch (c) {
        case 'h':
            print_usage(stdout);
            return 0;
        case 'V':
            printf("ppm %s\n%s\n\n",
                   ppm_version_string(), ppm_build_string());
            printf("  store:        %s\n", ppm_cmd_store_dir());
            printf("  registry:     %s\n", ppm_cmd_registry_dir());
            printf("  registry url: %s\n", ppm_cmd_registry_url());
            return 0;
        case 'v':
            ppm_log_set_level(PPM_LOG_DEBUG);
            break;
        case 'q':
            ppm_log_set_level(PPM_LOG_ERROR);
            break;
        default:
            print_usage(stderr);
            return 2;
        }
    }

    if (optind >= argc) {
        print_usage(stderr);
        return 2;
    }

    if (ppm_crypto_init() != PPM_OK) {
        ppm_error("failed to initialise libsodium");
        return 1;
    }

    const char *name = argv[optind];
    const struct subcommand *cmd = find_command(name);
    if (!cmd) {
        ppm_error("unknown command: %s", name);
        print_usage(stderr);
        return 2;
    }

    /* Shift argv so the subcommand sees itself at argv[0]. */
    int sub_argc = argc - optind;
    char **sub_argv = argv + optind;

    /*
     * Reset getopt state for the subcommand. Different platforms spell
     * this differently; the common subset is optind = 1.
     */
    optind = 1;

    return cmd->fn(sub_argc, sub_argv);
}
