// kk — roteador central de comandos CLI
//
// Uso:  kk --<comando> [argumentos...]
//
// O kk descobre, no mesmo diretório do seu binário, executáveis que
// implementam comandos. A convenção de nome é kk-<comando> (com fallback
// para <comando>). Exemplo: o comando "aa" é implementado por "kk-aa" e
// invocado como:
//
//     kk --aa [argumentos...]
//
// O primeiro token que começa com "--" (com pelo menos 2 caracteres) é
// interpretado como o nome do comando. Tokens no formato "--comando=valor"
// roteiam para "comando" e também são repassados ao executável do comando.
// Os demais argumentos são repassados intactos.

#include <algorithm>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <limits.h>
#include <regex>
#include <string>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>

namespace fs = std::filesystem;

// Caminho absoluto do executável atual (via /proc/self/exe).
static std::string self_exe_path() {
    char buf[PATH_MAX];
    ssize_t n = readlink("/proc/self/exe", buf, sizeof(buf) - 1);
    if (n < 0) {
        return "";
    }
    buf[n] = '\0';
    return std::string(buf);
}

// Lista os comandos disponíveis em um diretório.
// Aceita executáveis nomeados "kk-<comando>" ou "<comando>" (ignora "kk").
static std::vector<std::string> list_commands(const fs::path& dir) {
    std::vector<std::string> cmds;
    std::error_code ec;
    if (!fs::is_directory(dir, ec)) {
        return cmds;
    }
    for (const auto& entry : fs::directory_iterator(dir, ec)) {
        if (ec) {
            break;
        }
        if (!entry.is_regular_file(ec)) {
            continue;
        }
        const std::string name = entry.path().filename().string();
        std::string cmd;
        if (name.rfind("kk-", 0) == 0) {
            cmd = name.substr(3);
        } else {
            cmd = name;
        }
        if (cmd.empty() || cmd == "kk") {
            continue;
        }
        if (::access(entry.path().c_str(), X_OK) != 0) {
            continue;
        }
        cmds.push_back(cmd);
    }
    std::sort(cmds.begin(), cmds.end());
    cmds.erase(std::unique(cmds.begin(), cmds.end()), cmds.end());
    return cmds;
}

// Lista apenas os comandos cujo nome contém o texto informado.
static void print_commands_containing(const fs::path& dir,
                                      const std::string& needle,
                                      std::ostream& out) {
    const auto cmds = list_commands(dir);
    bool any = false;
    for (const auto& c : cmds) {
        if (c.find(needle) != std::string::npos) {
            out << "kk --" << c << "\n";
            any = true;
        }
    }
    if (!any) {
        out << "(nenhum comando contém \"" << needle << "\")\n";
    }
}

// Caminho do arquivo de ajuda de um comando: <dir>/ajuda/<cmd>.json.
static fs::path help_file_for(const fs::path& dir, const std::string& cmd) {
    return dir / "ajuda" / (cmd + ".json");
}

// Remove escapes básicos de strings JSON.
static std::string json_unescape(const std::string& s) {
    std::string out;
    out.reserve(s.size());
    for (std::size_t i = 0; i < s.size(); ++i) {
        if (s[i] == '\\' && i + 1 < s.size()) {
            const char n = s[++i];
            switch (n) {
                case 'n': out.push_back('\n'); break;
                case 't': out.push_back('\t'); break;
                default: out.push_back(n); break;
            }
        } else {
            out.push_back(s[i]);
        }
    }
    return out;
}

// Exibe o arquivo de ajuda (JSON com "galhos") de um comando.
// Retorna true se o arquivo existe e foi mostrado.
static bool show_help_file(const fs::path& dir, const std::string& cmd,
                           std::ostream& out) {
    const fs::path path = help_file_for(dir, cmd);
    std::ifstream in(path);
    if (!in) {
        return false;
    }
    const std::string content((std::istreambuf_iterator<char>(in)),
                              std::istreambuf_iterator<char>());

    std::regex node_re(
        R"json(\{\s*"id"\s*:\s*"((?:\\.|[^"])*)"\s*,\s*"nome"\s*:\s*"((?:\\.|[^"])*)"\s*\})json");
    bool any = false;
    for (std::sregex_iterator it(content.begin(), content.end(), node_re), end;
         it != end; ++it) {
        const std::string id = (*it)[1].str();
        const std::string nome = json_unescape((*it)[2].str());
        std::size_t depth = 0;
        for (char c : id) {
            if (c == '.') ++depth;
        }
        out << std::string(depth * 2, ' ') << id << ' ' << nome << '\n';
        any = true;
    }
    if (!any) {
        out << content << '\n';
    }
    return true;
}

static void print_usage(const fs::path& dir, std::ostream& out) {
    out << "kk — roteador de comandos CLI\n\n";
    out << "Uso: kk --<comando> [argumentos...]\n\n";
    out << "Comandos disponíveis:\n";
    const auto cmds = list_commands(dir);
    if (cmds.empty()) {
        out << "  (nenhum comando encontrado em " << dir.string() << ")\n";
    } else {
        for (const auto& c : cmds) {
            out << "  kk --" << c << "\n";
        }
    }
    out << "\nOpções globais:\n"
        << "  kk --help                         mostra esta ajuda\n"
        << "  kk --listar-comandos [texto]      lista comandos (filtra os que contêm [texto])\n"
        << "  kk --<comando> --ajuda            mostra o arquivo de ajuda do comando\n";
}

int main(int argc, char** argv) {
    std::vector<std::string> args(argv + 1, argv + argc);

    const fs::path self = self_exe_path();
    const fs::path dir = self.empty() ? fs::current_path() : self.parent_path();

    // Localiza o primeiro "--<comando>" ou "--<comando>=<valor>" na linha de comando.
    std::string cmd;
    std::size_t cmd_idx = 0;
    bool found = false;
    bool command_has_value = false;
    for (std::size_t i = 0; i < args.size(); ++i) {
        if (args[i].size() > 2 && args[i].rfind("--", 0) == 0) {
            std::string token = args[i].substr(2);
            const std::size_t eq = token.find('=');
            command_has_value = (eq != std::string::npos);
            cmd = command_has_value ? token.substr(0, eq) : token;
            cmd_idx = i;
            found = true;
            break;
        }
    }

    if (!found) {
        print_usage(dir, std::cerr);
        return 2;
    }

    if (cmd == "help" || cmd == "h") {
        print_usage(dir, std::cout);
        return 0;
    }

    if (cmd == "listar-comandos") {
        std::string needle;
        if (command_has_value) {
            const std::string token = args[cmd_idx].substr(2);
            const std::size_t eq = token.find('=');
            needle = token.substr(eq + 1);
        } else if (cmd_idx + 1 < args.size()) {
            needle = args[cmd_idx + 1];
        }
        print_commands_containing(dir, needle, std::cout);
        return 0;
    }

    // --ajuda mostra o arquivo de ajuda do comando (ajuda/<cmd>.json).
    // Se não houver arquivo, deixa o argumento seguir para o executável do comando.
    for (std::size_t i = 0; i < args.size(); ++i) {
        if (i == cmd_idx) {
            continue;
        }
        if (args[i] == "--ajuda") {
            if (show_help_file(dir, cmd, std::cout)) {
                return 0;
            }
            break;
        }
    }

    // Resolve o executável do comando: prefere "kk-<cmd>", depois "<cmd>".
    fs::path target;
    const fs::path prefixed = dir / ("kk-" + cmd);
    const fs::path plain = dir / cmd;
    if (fs::is_regular_file(prefixed) && ::access(prefixed.c_str(), X_OK) == 0) {
        target = prefixed;
    } else if (fs::is_regular_file(plain) && ::access(plain.c_str(), X_OK) == 0) {
        target = plain;
    } else {
        std::cerr << "kk: comando desconhecido: --" << cmd << "\n";
        std::cerr << "kk: use 'kk --help' para listar os comandos disponíveis.\n";
        return 127;
    }

    // Monta o argv do filho: argv[0] = nome do executável, depois os demais
    // argumentos. O token "--<comando>" puro é excluído; já
    // "--<comando>=<valor>" é mantido para o comando receber o valor.
    std::vector<std::string> child_args;
    child_args.push_back(target.filename().string());
    for (std::size_t i = 0; i < args.size(); ++i) {
        if (i == cmd_idx && !command_has_value) {
            continue;
        }
        child_args.push_back(args[i]);
    }

    std::vector<char*> cargv;
    cargv.reserve(child_args.size() + 1);
    for (auto& a : child_args) {
        cargv.push_back(const_cast<char*>(a.c_str()));
    }
    cargv.push_back(nullptr);

    pid_t pid = ::fork();
    if (pid < 0) {
        std::perror("kk: fork");
        return 1;
    }
    if (pid == 0) {
        ::execv(target.c_str(), cargv.data());
        std::perror("kk: execv");
        _exit(127);
    }

    int status = 0;
    if (::waitpid(pid, &status, 0) < 0) {
        std::perror("kk: waitpid");
        return 1;
    }
    if (WIFEXITED(status)) {
        return WEXITSTATUS(status);
    }
    if (WIFSIGNALED(status)) {
        return 128 + WTERMSIG(status);
    }
    return status;
}
