// kk-sa — cria e exibe árvores simples em arquivos JSON.
//
// Uso:
//   kk --sa=teste
//   kk --sa teste
//   kk --sa=teste --ag=pais
//   kk --sa teste --ag pais
//   kk --sa=teste --ag=brasil --em=1.1
//   kk --sa teste --ag brasil --em 1.1
//   kk --sa teste --de 1.2 --para 1.1
//   kk --sa teste --dg 1.1.1.1
//   kk --sa teste --podar 1.1.1
//   kk --sa teste --em 1.1.1 --eg capitais
//   kk --sa teste --em 1.1.1 --replace '[capitais][capitais do pais]'
//   kk --sa teste --recolher 1.1
//   kk --sa teste --recolher
//   kk --sa teste --expandir 1.1
//   kk --sa teste --expandir
//   kk --sa teste --procurar estados
//   kk --sa teste --procurar estados --em 2.1
//   kk --sa teste --legenda

#include <algorithm>
#include <cctype>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <regex>
#include <sstream>
#include <string>
#include <vector>

namespace fs = std::filesystem;

static constexpr const char* ANSI_RESET = "\033[0m";
static constexpr const char* ANSI_CIANO = "\033[36m";
static constexpr const char* ANSI_ROSA_CHOQUE = "\033[1;38;5;201m";
static constexpr const char* ANSI_AMBAR_BRILHANTE = "\033[38;5;226m";

struct Node {
    std::string id;
    std::string nome;
};

static std::string trim(const std::string& s) {
    std::size_t a = 0;
    while (a < s.size() && std::isspace(static_cast<unsigned char>(s[a]))) ++a;
    std::size_t b = s.size();
    while (b > a && std::isspace(static_cast<unsigned char>(s[b - 1]))) --b;
    return s.substr(a, b - a);
}

static std::string json_escape(const std::string& s) {
    std::ostringstream out;
    for (unsigned char c : s) {
        switch (c) {
            case '"': out << "\\\""; break;
            case '\\': out << "\\\\"; break;
            case '\b': out << "\\b"; break;
            case '\f': out << "\\f"; break;
            case '\n': out << "\\n"; break;
            case '\r': out << "\\r"; break;
            case '\t': out << "\\t"; break;
            default:
                if (c < 0x20) {
                    const char* hex = "0123456789abcdef";
                    out << "\\u00" << hex[c >> 4] << hex[c & 0x0f];
                } else {
                    out << c;
                }
        }
    }
    return out.str();
}

static std::string json_unescape(const std::string& s) {
    std::string out;
    for (std::size_t i = 0; i < s.size(); ++i) {
        if (s[i] != '\\' || i + 1 >= s.size()) {
            out += s[i];
            continue;
        }
        const char e = s[++i];
        switch (e) {
            case '"': out += '"'; break;
            case '\\': out += '\\'; break;
            case '/': out += '/'; break;
            case 'b': out += '\b'; break;
            case 'f': out += '\f'; break;
            case 'n': out += '\n'; break;
            case 'r': out += '\r'; break;
            case 't': out += '\t'; break;
            default: out += e; break;
        }
    }
    return out;
}

static std::vector<int> id_parts(const std::string& id) {
    std::vector<int> parts;
    std::stringstream ss(id);
    std::string item;
    while (std::getline(ss, item, '.')) {
        if (item.empty()) return {};
        for (char c : item) {
            if (!std::isdigit(static_cast<unsigned char>(c))) return {};
        }
        parts.push_back(std::stoi(item));
    }
    return parts;
}

static bool valid_id(const std::string& id) {
    return !id_parts(id).empty();
}

static std::string auto_dot_id_arg(const std::string& id) {
    if (id.empty() || id.find('.') != std::string::npos) return id;
    if (!std::all_of(id.begin(), id.end(), [](unsigned char c) { return std::isdigit(c); })) return id;

    std::string out;
    out.reserve(id.size() * 2 - 1);
    for (std::size_t i = 0; i < id.size(); ++i) {
        if (i > 0) out += '.';
        out += id[i];
    }
    return out;
}

static std::string join_parts(const std::vector<int>& parts) {
    std::ostringstream out;
    for (std::size_t i = 0; i < parts.size(); ++i) {
        if (i > 0) out << '.';
        out << parts[i];
    }
    return out.str();
}

static std::string parent_id(const std::string& id) {
    const auto p = id.rfind('.');
    return p == std::string::npos ? "" : id.substr(0, p);
}

static bool id_less(const std::string& a, const std::string& b) {
    const auto pa = id_parts(a);
    const auto pb = id_parts(b);
    return std::lexicographical_compare(pa.begin(), pa.end(), pb.begin(), pb.end());
}

static bool has_id(const std::vector<Node>& nodes, const std::string& id) {
    return std::any_of(nodes.begin(), nodes.end(), [&](const Node& n) { return n.id == id; });
}

static bool has_children(const std::vector<Node>& nodes, const std::string& id) {
    return std::any_of(nodes.begin(), nodes.end(), [&](const Node& n) { return parent_id(n.id) == id; });
}

static bool contains_id(const std::vector<std::string>& ids, const std::string& id) {
    return std::find(ids.begin(), ids.end(), id) != ids.end();
}

static void add_unique_id(std::vector<std::string>& ids, const std::string& id) {
    if (!contains_id(ids, id)) ids.push_back(id);
}

static void remove_id(std::vector<std::string>& ids, const std::string& id) {
    ids.erase(std::remove(ids.begin(), ids.end(), id), ids.end());
}

static void normalize_collapsed(const std::vector<Node>& nodes, std::vector<std::string>& ids) {
    ids.erase(std::remove_if(ids.begin(), ids.end(), [&](const std::string& id) {
        return !valid_id(id) || !has_id(nodes, id) || !has_children(nodes, id);
    }), ids.end());
    std::sort(ids.begin(), ids.end(), id_less);
    ids.erase(std::unique(ids.begin(), ids.end()), ids.end());
}

static std::string next_root_id(const std::vector<Node>& nodes) {
    int max_id = 0;
    for (const auto& n : nodes) {
        const auto parts = id_parts(n.id);
        if (parts.size() == 1) max_id = std::max(max_id, parts[0]);
    }
    return std::to_string(max_id + 1);
}

static bool same_parent_at_level(const std::vector<int>& a, const std::vector<int>& b, std::size_t level) {
    if (a.size() <= level || b.size() <= level) return false;
    for (std::size_t i = 0; i < level; ++i) {
        if (a[i] != b[i]) return false;
    }
    return true;
}

static void shift_id_for_insert(std::string& node_id, const std::string& id) {
    const auto target = id_parts(id);
    auto parts = id_parts(node_id);
    if (target.empty() || parts.empty()) return;

    const std::size_t level = target.size() - 1;
    if (same_parent_at_level(parts, target, level) && parts[level] >= target.back()) {
        ++parts[level];
        node_id = join_parts(parts);
    }
}

static void shift_for_insert(std::vector<Node>& nodes, const std::string& id) {
    for (auto& n : nodes) shift_id_for_insert(n.id, id);
}

static void shift_for_insert(std::vector<std::string>& ids, const std::string& id) {
    for (auto& item : ids) shift_id_for_insert(item, id);
}

static bool is_descendant_or_self(const std::string& parent, const std::string& id) {
    return id == parent || id.rfind(parent + ".", 0) == 0;
}

static void close_gap_id_after_remove(std::string& node_id, const std::string& removed_id) {
    const auto removed = id_parts(removed_id);
    auto parts = id_parts(node_id);
    if (removed.empty() || parts.empty()) return;

    const std::size_t level = removed.size() - 1;
    if (same_parent_at_level(parts, removed, level) && parts[level] > removed[level]) {
        --parts[level];
        node_id = join_parts(parts);
    }
}

static void close_gap_after_remove(std::vector<Node>& nodes, const std::string& removed_id) {
    for (auto& n : nodes) close_gap_id_after_remove(n.id, removed_id);
}

static void close_gap_after_remove(std::vector<std::string>& ids, const std::string& removed_id) {
    for (auto& item : ids) close_gap_id_after_remove(item, removed_id);
}

static void rebase_id(std::string& id, const std::string& from_id, const std::string& to_id) {
    if (id == from_id) id = to_id;
    else if (is_descendant_or_self(from_id, id)) id = to_id + id.substr(from_id.size());
}

static void rebase_subtree(std::vector<Node>& subtree, const std::string& from_id, const std::string& to_id) {
    for (auto& n : subtree) rebase_id(n.id, from_id, to_id);
}

static void rebase_subtree(std::vector<std::string>& ids, const std::string& from_id, const std::string& to_id) {
    for (auto& item : ids) rebase_id(item, from_id, to_id);
}

static bool delete_node(std::vector<Node>& nodes, std::vector<std::string>& collapsed, const std::string& id, std::string& error) {
    if (!valid_id(id)) {
        error = "numeração inválida para deletar: " + id;
        return false;
    }
    if (!has_id(nodes, id)) {
        error = "galho não encontrado: " + id;
        return false;
    }

    std::vector<Node> remaining;
    for (const auto& n : nodes) {
        if (!is_descendant_or_self(id, n.id)) remaining.push_back(n);
    }

    collapsed.erase(std::remove_if(collapsed.begin(), collapsed.end(), [&](const std::string& item) {
        return is_descendant_or_self(id, item);
    }), collapsed.end());
    close_gap_after_remove(remaining, id);
    close_gap_after_remove(collapsed, id);
    nodes = remaining;
    return true;
}

static bool prune_node(std::vector<Node>& nodes, std::vector<std::string>& collapsed, const std::string& id, std::string& error) {
    if (!valid_id(id)) {
        error = "numeração inválida para podar: " + id;
        return false;
    }
    if (!has_id(nodes, id)) {
        error = "galho não encontrado: " + id;
        return false;
    }

    std::vector<Node> remaining;
    const std::string prefix = id + ".";
    for (const auto& n : nodes) {
        if (n.id.rfind(prefix, 0) != 0) remaining.push_back(n);
    }
    collapsed.erase(std::remove_if(collapsed.begin(), collapsed.end(), [&](const std::string& item) {
        return item == id || item.rfind(prefix, 0) == 0;
    }), collapsed.end());

    nodes = remaining;
    return true;
}

static bool edit_node(std::vector<Node>& nodes, const std::string& id, const std::string& name, std::string& error) {
    if (!valid_id(id)) {
        error = "numeração inválida para editar: " + id;
        return false;
    }
    for (auto& n : nodes) {
        if (n.id == id) {
            n.nome = name;
            return true;
        }
    }
    error = "galho não encontrado: " + id;
    return false;
}

static bool parse_replace_expr(const std::string& expr, std::string& from, std::string& to) {
    if (expr.size() < 4 || expr[0] != '[') return false;
    const std::size_t mid = expr.find("][", 1);
    if (mid == std::string::npos || expr.back() != ']') return false;
    from = expr.substr(1, mid - 1);
    to = expr.substr(mid + 2, expr.size() - mid - 3);
    return true;
}

static bool replace_node_text(std::vector<Node>& nodes, const std::string& id, const std::string& expr, std::string& error) {
    if (!valid_id(id)) {
        error = "numeração inválida para replace: " + id;
        return false;
    }

    std::string from;
    std::string to;
    if (!parse_replace_expr(expr, from, to)) {
        error = "formato inválido para --replace. use '[texto atual][texto novo]'";
        return false;
    }

    for (auto& n : nodes) {
        if (n.id == id) {
            const std::size_t pos = n.nome.find(from);
            if (pos == std::string::npos) {
                error = "texto não encontrado no galho: " + from;
                return false;
            }
            n.nome.replace(pos, from.size(), to);
            return true;
        }
    }
    error = "galho não encontrado: " + id;
    return false;
}

static bool move_node(std::vector<Node>& nodes, std::vector<std::string>& collapsed, const std::string& from_id, const std::string& to_id, std::string& error) {
    const auto from = id_parts(from_id);
    const auto to = id_parts(to_id);
    if (from.empty() || to.empty()) {
        error = "numeração inválida para mover";
        return false;
    }
    if (!has_id(nodes, from_id)) {
        error = "galho de origem não encontrado: " + from_id;
        return false;
    }
    if (from_id == to_id) return true;
    if (is_descendant_or_self(from_id, to_id)) {
        error = "destino dentro do próprio galho";
        return false;
    }

    std::vector<Node> remaining;
    std::vector<Node> subtree;
    for (const auto& n : nodes) {
        if (is_descendant_or_self(from_id, n.id)) subtree.push_back(n);
        else remaining.push_back(n);
    }

    std::vector<std::string> remaining_collapsed;
    std::vector<std::string> subtree_collapsed;
    for (const auto& id : collapsed) {
        if (is_descendant_or_self(from_id, id)) subtree_collapsed.push_back(id);
        else remaining_collapsed.push_back(id);
    }

    close_gap_after_remove(remaining, from_id);
    close_gap_after_remove(remaining_collapsed, from_id);

    const std::string parent = parent_id(to_id);
    if (!parent.empty() && !has_id(remaining, parent)) {
        error = "galho pai não encontrado: " + parent;
        return false;
    }

    if (has_id(remaining, to_id)) {
        shift_for_insert(remaining, to_id);
        shift_for_insert(remaining_collapsed, to_id);
    }

    rebase_subtree(subtree, from_id, to_id);
    rebase_subtree(subtree_collapsed, from_id, to_id);
    remaining.insert(remaining.end(), subtree.begin(), subtree.end());
    remaining_collapsed.insert(remaining_collapsed.end(), subtree_collapsed.begin(), subtree_collapsed.end());
    nodes = remaining;
    collapsed = remaining_collapsed;
    return true;
}

static bool load_file(const fs::path& path, std::string& nome, std::vector<Node>& nodes, std::vector<std::string>& collapsed) {
    std::ifstream in(path);
    if (!in) return false;
    std::string content((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());

    std::smatch m;
    std::regex nome_re(R"json("nome"\s*:\s*"((?:\\.|[^"])*)")json");
    if (std::regex_search(content, m, nome_re)) {
        nome = json_unescape(m[1].str());
    }

    std::regex node_re(R"json(\{\s*"id"\s*:\s*"((?:\\.|[^"])*)"\s*,\s*"nome"\s*:\s*"((?:\\.|[^"])*)"\s*\})json");
    for (auto it = std::sregex_iterator(content.begin(), content.end(), node_re);
         it != std::sregex_iterator(); ++it) {
        nodes.push_back({json_unescape((*it)[1].str()), json_unescape((*it)[2].str())});
    }

    std::regex collapsed_re(R"json("recolhidos"\s*:\s*\[([^\]]*)\])json");
    if (std::regex_search(content, m, collapsed_re)) {
        const std::string list = m[1].str();
        std::regex string_re(R"json("((?:\\.|[^"])*)")json");
        for (auto it = std::sregex_iterator(list.begin(), list.end(), string_re);
             it != std::sregex_iterator(); ++it) {
            collapsed.push_back(json_unescape((*it)[1].str()));
        }
    }
    return true;
}

static bool save_file(const fs::path& path, const std::string& nome, std::vector<Node> nodes, std::vector<std::string> collapsed) {
    static const std::string marca = "kk-sa";

    std::sort(nodes.begin(), nodes.end(), [](const Node& a, const Node& b) { return id_less(a.id, b.id); });
    normalize_collapsed(nodes, collapsed);
    std::ofstream out(path, std::ios::trunc);
    if (!out) return false;
    out << "{\n";
    out << "  \"marca\": \"" << json_escape(marca) << "\",\n";
    out << "  \"nome\": \"" << json_escape(nome) << "\",\n";
    out << "  \"galhos\": [\n";
    for (std::size_t i = 0; i < nodes.size(); ++i) {
        out << "    { \"id\": \"" << json_escape(nodes[i].id)
            << "\", \"nome\": \"" << json_escape(nodes[i].nome) << "\" }";
        if (i + 1 < nodes.size()) out << ',';
        out << "\n";
    }
    out << "  ],\n";
    out << "  \"recolhidos\": [\n";
    for (std::size_t i = 0; i < collapsed.size(); ++i) {
        out << "    \"" << json_escape(collapsed[i]) << "\"";
        if (i + 1 < collapsed.size()) out << ',';
        out << "\n";
    }
    out << "  ]\n";
    out << "}\n";
    return true;
}

static std::vector<Node> children_of(const std::vector<Node>& nodes, const std::string& parent) {
    std::vector<Node> children;
    for (const auto& n : nodes) {
        if (parent_id(n.id) == parent) children.push_back(n);
    }
    std::sort(children.begin(), children.end(), [](const Node& a, const Node& b) { return id_less(a.id, b.id); });
    return children;
}

static bool in_scope(const std::string& id, const std::string& scope) {
    return scope.empty() || is_descendant_or_self(scope, id);
}

static std::string highlight_text(const std::string& text, const std::string& term, bool enabled) {
    if (!enabled || term.empty()) return text;

    std::string out;
    std::size_t pos = 0;
    while (true) {
        const std::size_t found = text.find(term, pos);
        if (found == std::string::npos) {
            out += text.substr(pos);
            break;
        }
        out += text.substr(pos, found - pos);
        out += std::string(ANSI_ROSA_CHOQUE) + term + ANSI_RESET;
        pos = found + term.size();
    }
    return out;
}

static void expand_paths_to_matches(const std::vector<Node>& nodes, std::vector<std::string>& collapsed,
                                    const std::string& term, const std::string& scope, std::string& error) {
    if (term.empty()) return;
    if (!scope.empty() && (!valid_id(scope) || !has_id(nodes, scope))) {
        error = "galho não encontrado para procurar: " + scope;
        return;
    }

    for (const auto& n : nodes) {
        if (!in_scope(n.id, scope) || n.nome.find(term) == std::string::npos) continue;
        std::string p = parent_id(n.id);
        while (!p.empty()) {
            remove_id(collapsed, p);
            p = parent_id(p);
        }
    }
}

static std::string color_text(const std::string& text, const char* ansi) {
    return std::string(ansi) + text + ANSI_RESET;
}

static void print_color_legend() {
    std::cout << "Legenda de cores do kk-sa:\n";
    std::cout << "  " << color_text("ciano", ANSI_CIANO) << ": id de galho recolhido (filhos ocultos)\n";
    std::cout << "  " << color_text("rosa", ANSI_ROSA_CHOQUE) << ": trecho encontrado por --procurar\n";
    std::cout << "  " << color_text("âmbar brilhante", ANSI_AMBAR_BRILHANTE) << ": item criado/editado/movido nesta execução\n";
}

static void print_id(const std::string& id, bool cyan, bool amber) {
    if (amber) std::cout << ANSI_AMBAR_BRILHANTE << id << ANSI_RESET;
    else if (cyan) std::cout << ANSI_CIANO << id << ANSI_RESET;
    else std::cout << id;
}

static void print_children_tree(const std::vector<Node>& nodes, const std::string& parent, const std::string& prefix,
                                const std::vector<std::string>& collapsed_ids,
                                const std::string& search_term, const std::string& search_scope,
                                const std::string& highlighted_id) {
    const auto children = children_of(nodes, parent);
    for (std::size_t i = 0; i < children.size(); ++i) {
        const bool last = (i + 1 == children.size());
        const bool collapsed = contains_id(collapsed_ids, children[i].id) && has_children(nodes, children[i].id);

        const bool highlighted = children[i].id == highlighted_id;
        const std::string label = highlighted
            ? color_text(children[i].nome, ANSI_AMBAR_BRILHANTE)
            : highlight_text(children[i].nome, search_term, in_scope(children[i].id, search_scope));

        std::cout << prefix << (last ? "└── " : "├── ");
        print_id(children[i].id, collapsed, highlighted);
        std::cout << ' ' << label << "\n";

        if (!collapsed) {
            print_children_tree(nodes, children[i].id, prefix + (last ? "    " : "│   "), collapsed_ids, search_term, search_scope, highlighted_id);
        }
    }
}

static void print_tree(const std::string& nome, std::vector<Node> nodes, std::vector<std::string> collapsed_ids,
                       const std::string& search_term, const std::string& search_scope,
                       const std::string& highlighted_id) {
    std::sort(nodes.begin(), nodes.end(), [](const Node& a, const Node& b) { return id_less(a.id, b.id); });
    normalize_collapsed(nodes, collapsed_ids);
    std::cout << highlight_text(nome, search_term, search_scope.empty()) << "\n";
    print_children_tree(nodes, "", "", collapsed_ids, search_term, search_scope, highlighted_id);
}

static fs::path json_path_for(const std::string& nome) {
    fs::path p(nome);
    if (p.extension() != ".json") p += ".json";
    return p;
}

int main(int argc, char** argv) {
    std::string nome;
    std::string ag;
    std::string em;
    std::string de;
    std::string para;
    std::string dg;
    std::string podar;
    std::string eg;
    std::string replace;
    std::string recolher_id;
    std::string expandir_id;
    std::string procurar;
    bool recolher = false;
    bool expandir = false;
    bool legenda = false;
    std::string highlighted_id;

    for (int i = 1; i < argc; ++i) {
        const std::string arg = argv[i];
        if (arg.rfind("--sa=", 0) == 0) nome = arg.substr(5);
        else if (arg == "--sa" && i + 1 < argc) nome = argv[++i];
        else if (arg.rfind("--ag=", 0) == 0) ag = arg.substr(5);
        else if (arg == "--ag" && i + 1 < argc) ag = argv[++i];
        else if (arg.rfind("--em=", 0) == 0) em = arg.substr(5);
        else if (arg == "--em" && i + 1 < argc) em = argv[++i];
        else if (arg.rfind("--de=", 0) == 0) de = arg.substr(5);
        else if (arg == "--de" && i + 1 < argc) de = argv[++i];
        else if (arg.rfind("--para=", 0) == 0) para = arg.substr(7);
        else if (arg == "--para" && i + 1 < argc) para = argv[++i];
        else if (arg.rfind("--dg=", 0) == 0) dg = arg.substr(5);
        else if (arg == "--dg" && i + 1 < argc) dg = argv[++i];
        else if (arg.rfind("--podar=", 0) == 0) podar = arg.substr(8);
        else if (arg == "--podar" && i + 1 < argc) podar = argv[++i];
        else if (arg.rfind("--eg=", 0) == 0) eg = arg.substr(5);
        else if (arg == "--eg" && i + 1 < argc) eg = argv[++i];
        else if (arg.rfind("--replace=", 0) == 0) replace = arg.substr(10);
        else if (arg == "--replace" && i + 1 < argc) replace = argv[++i];
        else if (arg.rfind("--recolher=", 0) == 0) {
            recolher = true;
            recolher_id = arg.substr(11);
        } else if (arg == "--recolher") {
            recolher = true;
            if (i + 1 < argc && std::string(argv[i + 1]).rfind("--", 0) != 0) recolher_id = argv[++i];
        } else if (arg.rfind("--expandir=", 0) == 0) {
            expandir = true;
            expandir_id = arg.substr(11);
        } else if (arg == "--expandir") {
            expandir = true;
            if (i + 1 < argc && std::string(argv[i + 1]).rfind("--", 0) != 0) expandir_id = argv[++i];
        } else if (arg.rfind("--procurar=", 0) == 0) procurar = arg.substr(11);
        else if (arg == "--procurar" && i + 1 < argc) procurar = argv[++i];
        else if (arg == "--legenda") legenda = true;
        else if (nome.empty() && arg.rfind("--", 0) != 0) nome = arg;
    }

    nome = trim(nome);
    ag = trim(ag);
    em = auto_dot_id_arg(trim(em));
    de = auto_dot_id_arg(trim(de));
    para = auto_dot_id_arg(trim(para));
    dg = auto_dot_id_arg(trim(dg));
    podar = trim(podar);
    eg = trim(eg);
    replace = trim(replace);
    recolher_id = trim(recolher_id);
    expandir_id = trim(expandir_id);
    procurar = trim(procurar);

    if (nome.empty()) {
        if (legenda) {
            print_color_legend();
            return 0;
        }
        std::cerr << "uso: kk --sa=<nome> [--ag=<galho>] [--em=<numero>] [--legenda]\n";
        return 2;
    }

    std::vector<Node> nodes;
    std::vector<std::string> collapsed;
    const fs::path path = json_path_for(nome);
    std::string root_name = path.stem().string();
    load_file(path, root_name, nodes, collapsed);
    root_name = path.stem().string();
    if (root_name.empty()) root_name = nome;

    if (!replace.empty()) {
        if (em.empty()) {
            std::cerr << "kk-sa: use --em <numero> com --replace '[atual][novo]'\n";
            return 2;
        }
        std::string error;
        if (!replace_node_text(nodes, em, replace, error)) {
            std::cerr << "kk-sa: " << error << "\n";
            return 1;
        }
        highlighted_id = em;
    } else if (!eg.empty()) {
        if (em.empty()) {
            std::cerr << "kk-sa: use --em <numero> com --eg <nome>\n";
            return 2;
        }
        std::string error;
        if (!edit_node(nodes, em, eg, error)) {
            std::cerr << "kk-sa: " << error << "\n";
            return 1;
        }
        highlighted_id = em;
    } else if (!podar.empty()) {
        std::string error;
        if (!prune_node(nodes, collapsed, podar, error)) {
            std::cerr << "kk-sa: " << error << "\n";
            return 1;
        }
    } else if (!dg.empty()) {
        std::string error;
        if (!delete_node(nodes, collapsed, dg, error)) {
            std::cerr << "kk-sa: " << error << "\n";
            return 1;
        }
    } else if (!de.empty() || !para.empty()) {
        if (de.empty() || para.empty()) {
            std::cerr << "kk-sa: use --de <origem> e --para <destino>\n";
            return 2;
        }
        std::string error;
        if (!move_node(nodes, collapsed, de, para, error)) {
            std::cerr << "kk-sa: " << error << "\n";
            return 1;
        }
        highlighted_id = para;
    } else if (!ag.empty()) {
        const std::string id = em.empty() ? next_root_id(nodes) : em;
        if (!valid_id(id)) {
            std::cerr << "kk-sa: numeração inválida: " << id << "\n";
            return 2;
        }
        const std::string parent = parent_id(id);
        if (!parent.empty() && !has_id(nodes, parent)) {
            std::cerr << "kk-sa: galho pai não encontrado: " << parent << "\n";
            return 1;
        }
        if (has_id(nodes, id)) {
            shift_for_insert(nodes, id);
            shift_for_insert(collapsed, id);
        }
        nodes.push_back({id, ag});
        highlighted_id = id;
    }

    if (recolher) {
        if (recolher_id.empty()) {
            for (const auto& n : nodes) {
                if (parent_id(n.id).empty() && has_children(nodes, n.id)) add_unique_id(collapsed, n.id);
            }
        } else {
            if (!valid_id(recolher_id) || !has_id(nodes, recolher_id)) {
                std::cerr << "kk-sa: galho não encontrado para recolher: " << recolher_id << "\n";
                return 1;
            }
            if (has_children(nodes, recolher_id)) add_unique_id(collapsed, recolher_id);
        }
    }

    if (expandir) {
        if (expandir_id.empty()) {
            collapsed.clear();
        } else {
            if (!valid_id(expandir_id)) {
                std::cerr << "kk-sa: numeração inválida para expandir: " << expandir_id << "\n";
                return 2;
            }
            remove_id(collapsed, expandir_id);
        }
    }

    if (!procurar.empty()) {
        std::string error;
        expand_paths_to_matches(nodes, collapsed, procurar, em, error);
        if (!error.empty()) {
            std::cerr << "kk-sa: " << error << "\n";
            return 1;
        }
    }

    normalize_collapsed(nodes, collapsed);

    if (!save_file(path, root_name, nodes, collapsed)) {
        std::cerr << "kk-sa: não foi possível gravar: " << path.string() << "\n";
        return 1;
    }

    std::vector<std::string> display_collapsed = collapsed;
    std::string highlighted_parent = parent_id(highlighted_id);
    while (!highlighted_parent.empty()) {
        remove_id(display_collapsed, highlighted_parent);
        highlighted_parent = parent_id(highlighted_parent);
    }

    print_tree(root_name, nodes, display_collapsed, procurar, procurar.empty() ? "" : em, highlighted_id);
    if (legenda) {
        std::cout << "\n";
        print_color_legend();
    }
    return 0;
}
