/*
 * sa - Gerencia árvores hierárquicas de galhos
 * Uso:
 *   pp --sa               lista arvores existentes
 *   pp --sa=<nome>          exibe uma arvore; se nao existir, pergunta antes de criar arquivo vazio
 *   pp --sa=<nome> --ag=<galho> pergunta antes de criar a arvore, se nao existir, e adiciona galho
 *
 * Flags:
 *   --ag=<nome>              adiciona galho na raiz ou em --em=<id>
 *                            quebras de linha em --ag/--eg viram espaço; não geram itens
 *   --em=<id>                nó pai/posição para --ag; com --mm aceita lista: --em=<id1,id2>
 *   --dg=<id>                remove galho e sub-galhos
 *   --podar --em=<id>        remove todos os sub-galhos de --em, mantendo o item
 *   --de=<id> --para=<id>    move galho
 *   --rel=<id>               relaciona o galho adicionado/editado a outro galho
 *   --drel=<id> --em=<id> remove a relação de --em com o alvo informado
 *   --vlr=<valor> --em=<id> vincula receita/valor positivo ao item informado em --em
 *   --vld=<valor> --em=<id> vincula despesa/valor negativo ao item informado em --em
 *   --vlt=<valor> --clt=<origem,destino> --em=<id> vincula transferencia; use 0 para mundo externo
 *   --cv=<id> --em=<id> adiciona vinculo do item em --em a um consolidado
 *   --dcv=<id> --em=<id> remove um vinculo especifico do item em --em
 *   --ra=<paths> --em=<id> referencia arquivos desde sisc/ no item informado em --em
 *   --dra[=<path>] --em=<id> remove referencias de arquivo; sem valor remove todas
 *   --prompt=<gc>            monta prompt especializado do GC, envia ao --parallm padrão (com HEB) e aciona agente-at automaticamente
 *   --replace=[x][y] --de=<id> --ate=<id>
 *                            substitui x por y nos nomes do intervalo
 *   --reorganizar            compacta/renumera índices
 *   --recolher=<id|total>    oculta os sub-galhos do item ou de todos
 *   --expandir=<id|total>    volta a exibir os sub-galhos do item ou de todos
 *   --json                   exibe JSON
 *   --dir                    recria a árvore com diretórios/arquivos recursivos a partir do diretório atual
 *   --status=<valor> --em=<id> aplica status ao item e a todos os filhos; use null para remover
 *                           --ag sempre cria item sem status (status NULL)
 *   --ajuda                  ajuda
 *
 * GCs: máximo de 1 por árvore; cor âmbar brilhante.
 *
 * Persistência: <diretório-atual>/<nome-normalizado>.json ou caminho informado em --sa=<caminho>
 */

#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <time.h>
#include <errno.h>
#include <unistd.h>
#include <limits.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdarg.h>
#include <fcntl.h>

typedef struct No {
    char *nome;
    char *cor;
    char *status;       /* pendente, andamento, aprovado, reprovado; NULL = sem status */
    char *rel;
    char *valor;
    char *vlt;
    char *clt;
    char *config_vin;
    int recolhido;
    char *gc;          /* grupo de comentario */
    char *mm;          /* mensagem do usuario (multiplas mensagens separadas por \n---\n) */
    char *itens;       /* itens relacionados separados por virgula */
    char *ra;          /* referencias a arquivos do sistema, desde sisc/, separadas por virgula */
    struct No **filhos;
    int total;
    int cap;
} No;

typedef struct {
    char *nome;
    char *criadoEm;
    char *atualizadoEm;
    No raiz;
} Arvore;

typedef struct {
    char *buf;
    size_t len;
    size_t cap;
} Str;

static void str_init(Str *s) { s->cap = 4096; s->len = 0; s->buf = malloc(s->cap); if (s->buf) s->buf[0] = '\0'; }
static int str_add(Str *s, const char *txt)
{
    size_t n = strlen(txt);
    if (s->len + n + 1 > s->cap) {
        while (s->len + n + 1 > s->cap) s->cap *= 2;
        char *novo = realloc(s->buf, s->cap);
        if (!novo) return -1;
        s->buf = novo;
    }
    memcpy(s->buf + s->len, txt, n + 1);
    s->len += n;
    return 0;
}

static int str_addf(Str *s, const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    va_list ap2;
    va_copy(ap2, ap);
    int n = vsnprintf(NULL, 0, fmt, ap);
    va_end(ap);
    if (n < 0) { va_end(ap2); return -1; }
    char *buf = malloc((size_t)n + 1);
    if (!buf) { va_end(ap2); return -1; }
    vsnprintf(buf, (size_t)n + 1, fmt, ap2);
    va_end(ap2);
    int r = str_add(s, buf);
    free(buf);
    return r;
}

static char *xstrdup(const char *s) { return s ? strdup(s) : strdup(""); }

static int sa_normalizar_quebras_item_inplace(char *txt)
{
    if (!txt) return 0;
    int teve_quebra = 0;
    char *r = txt;
    char *w = txt;
    int ultimo_espaco = 1;
    while (*r) {
        unsigned char c = (unsigned char)*r++;
        if (c == '\r' || c == '\n') {
            teve_quebra = 1;
            if (!ultimo_espaco) {
                *w++ = ' ';
                ultimo_espaco = 1;
            }
            continue;
        }
        *w++ = (char)c;
        ultimo_espaco = isspace(c) ? 1 : 0;
    }
    if (teve_quebra) {
        while (w > txt && isspace((unsigned char)w[-1])) w--;
    }
    *w = '\0';
    return teve_quebra;
}

static char *valor_com_sinal(const char *valor, int sinal)
{
    const char *p = valor ? valor : "";
    while (isspace((unsigned char)*p)) p++;
    if (!*p) return xstrdup("");
    if (*p == '+' || *p == '-') {
        p++;
        while (isspace((unsigned char)*p)) p++;
    }
    size_t n = strlen(p);
    if (sinal == 0) {
        char *out = malloc(n + 1);
        if (!out) return NULL;
        memcpy(out, p, n + 1);
        return out;
    }
    char *out = malloc(n + 2);
    if (!out) return NULL;
    out[0] = sinal < 0 ? '-' : '+';
    memcpy(out + 1, p, n + 1);
    return out;
}

static void no_init(No *n, const char *nome)
{
    n->nome = xstrdup(nome);
    n->cor = NULL;
    n->status = NULL;
    n->rel = NULL;
    n->valor = NULL;
    n->vlt = NULL;
    n->clt = NULL;
    n->config_vin = NULL;
    n->gc = NULL;
    n->mm = NULL;
    n->itens = NULL;
    n->ra = NULL;
    n->recolhido = 0;
    n->filhos = NULL;
    n->total = 0;
    n->cap = 0;
}

static void no_liberar(No *n)
{
    if (!n) return;
    for (int i = 0; i < n->total; i++) {
        no_liberar(n->filhos[i]);
        free(n->filhos[i]);
    }
    free(n->filhos);
    free(n->nome);
    free(n->cor);
    free(n->status);
    free(n->rel);
    free(n->valor);
    free(n->vlt);
    free(n->clt);
    free(n->config_vin);
    free(n->gc);
    free(n->mm);
    free(n->itens);
    free(n->ra);
    n->filhos = NULL; n->nome = NULL; n->cor = NULL; n->status = NULL; n->rel = NULL; n->valor = NULL; n->vlt = NULL; n->clt = NULL; n->config_vin = NULL; n->gc = NULL; n->mm = NULL; n->itens = NULL; n->ra = NULL; n->recolhido = 0; n->total = n->cap = 0;
}

static No *no_criar(const char *nome)
{
    No *n = malloc(sizeof(No));
    if (!n) return NULL;
    no_init(n, nome);
    return n;
}

static int no_add_filho(No *pai, No *filho)
{
    if (pai->total >= pai->cap) {
        int nova = pai->cap ? pai->cap * 2 : 4;
        No **tmp = realloc(pai->filhos, (size_t)nova * sizeof(No *));
        if (!tmp) return -1;
        pai->filhos = tmp;
        pai->cap = nova;
    }
    pai->filhos[pai->total++] = filho;
    return 0;
}

static int no_insert_filho(No *pai, int pos, No *filho)
{
    if (pos < 0) pos = 0;
    if (pos > pai->total) pos = pai->total;
    if (pai->total >= pai->cap) {
        int nova = pai->cap ? pai->cap * 2 : 4;
        No **tmp = realloc(pai->filhos, (size_t)nova * sizeof(No *));
        if (!tmp) return -1;
        pai->filhos = tmp;
        pai->cap = nova;
    }
    memmove(&pai->filhos[pos + 1], &pai->filhos[pos], (size_t)(pai->total - pos) * sizeof(No *));
    pai->filhos[pos] = filho;
    pai->total++;
    return 0;
}

static No *no_remove_filho(No *pai, int idx)
{
    if (!pai || idx < 0 || idx >= pai->total) return NULL;
    No *rem = pai->filhos[idx];
    memmove(&pai->filhos[idx], &pai->filhos[idx + 1], (size_t)(pai->total - idx - 1) * sizeof(No *));
    pai->total--;
    return rem;
}

static char *agora_iso(void)
{
    time_t t = time(NULL);
    struct tm tmv;
    gmtime_r(&t, &tmv);
    char *buf = malloc(32);
    if (!buf) return NULL;
    strftime(buf, 32, "%Y-%m-%dT%H:%M:%SZ", &tmv);
    return buf;
}

static int raiz_projeto(char *raiz, size_t tam)
{
    /* Cada comando trata o diretório de execução como raiz do sistema. */
    return getcwd(raiz, tam) ? 0 : -1;
}

static int caminho_comando_irmao(const char *nome, char *out, size_t tam)
{
    char exe[PATH_MAX];
    ssize_t n = readlink("/proc/self/exe", exe, sizeof(exe) - 1);
    if (n <= 0) return -1;
    exe[n] = '\0';
    char *slash = strrchr(exe, '/');
    if (!slash) return -1;
    *slash = '\0';
    size_t a = strlen(exe), b = strlen(nome);
    if (a + 1 + b + 1 > tam) return -1;
    memcpy(out, exe, a);
    out[a] = '/';
    memcpy(out + a + 1, nome, b + 1);
    return 0;
}

static int executar_atualizar_manual_funcional_alias(int argc, char **argv)
{
    char cmd[PATH_MAX];
    if (caminho_comando_irmao("atualizar-manual-funcional", cmd, sizeof(cmd)) != 0) {
        fprintf(stderr, "sa: não foi possível localizar o comando atualizar-manual-funcional.\n");
        return 1;
    }

    char **args = calloc((size_t)argc + 1, sizeof(char *));
    if (!args) return 1;
    args[0] = cmd;
    for (int i = 1; i < argc; i++) args[i] = argv[i];
    args[argc] = NULL;

    execv(cmd, args);
    perror("sa: execv atualizar-manual-funcional");
    free(args);
    return 127;
}

static char *normalizar_nome(const char *nome)
{
    size_t len = strlen(nome);
    char *out = malloc(len + 1);
    if (!out) return NULL;
    for (size_t i = 0; i < len; i++) {
        unsigned char c = (unsigned char)nome[i];
        out[i] = (isalnum(c) || c == '.' || c == '_' || c == '-') ? (char)c : '_';
    }
    out[len] = '\0';
    return out;
}

static int termina_com_json(const char *s)
{
    if (!s) return 0;
    size_t len = strlen(s);
    return len > 5 && strcmp(s + len - 5, ".json") == 0;
}

static char *nome_arvore_de_path(const char *path)
{
    const char *base = strrchr(path ? path : "", '/');
    base = base ? base + 1 : (path ? path : "");
    size_t len = strlen(base);
    if (len > 5 && strcmp(base + len - 5, ".json") == 0) len -= 5;
    char *out = malloc(len + 1);
    if (!out) return NULL;
    memcpy(out, base, len);
    out[len] = '\0';
    return out;
}

static int sincronizar_nome_arvore_com_arquivo(Arvore *a, const char *path)
{
    char *nome_arquivo = nome_arvore_de_path(path);
    if (!nome_arquivo || !*nome_arquivo) { free(nome_arquivo); return 0; }

    int alterou = 0;
    if (!a->nome || strcmp(a->nome, nome_arquivo) != 0) {
        free(a->nome);
        a->nome = xstrdup(nome_arquivo);
        alterou = 1;
    }
    if (!a->raiz.nome || strcmp(a->raiz.nome, nome_arquivo) != 0) {
        free(a->raiz.nome);
        a->raiz.nome = xstrdup(nome_arquivo);
        alterou = 1;
    }

    free(nome_arquivo);
    return alterou;
}

static int aplicar_cor_tronco(Arvore *a)
{
    (void)a;
    return 0;
}

static int garantir_dir(const char *dir)
{
    struct stat st;
    if (lstat(dir, &st) == 0) {
        if (S_ISLNK(st.st_mode) || !S_ISDIR(st.st_mode)) return -1;
        return 0;
    }
    if (mkdir(dir, 0775) != 0 && errno != EEXIST) return -1;
    chmod(dir, 0775);
    if (lstat(dir, &st) != 0 || S_ISLNK(st.st_mode) || !S_ISDIR(st.st_mode)) return -1;
    return 0;
}

static char *ler_arquivo(const char *path)
{
    FILE *fp = fopen(path, "rb");
    if (!fp) return NULL;
    fseek(fp, 0, SEEK_END);
    long tam = ftell(fp);
    if (tam < 0 || tam > 50 * 1024 * 1024) { fclose(fp); return NULL; }
    fseek(fp, 0, SEEK_SET);
    char *buf = malloc((size_t)tam + 1);
    if (!buf) { fclose(fp); return NULL; }
    size_t n = fread(buf, 1, (size_t)tam, fp);
    fclose(fp);
    buf[n] = '\0';
    return buf;
}

static int escrever_arquivo(const char *path, const char *conteudo)
{
    FILE *fp = fopen(path, "wb");
    if (!fp) return -1;
    size_t len = strlen(conteudo);
    size_t n = fwrite(conteudo, 1, len, fp);
    if (len == 0 || conteudo[len - 1] != '\n') fputc('\n', fp);
    fclose(fp);
    return n == len ? 0 : -1;
}

/* ---------------- JSON parser mínimo para o formato da árvore ---------------- */

typedef struct { const char *p; } Parser;
static void ps_ws(Parser *ps) { while (*ps->p && isspace((unsigned char)*ps->p)) ps->p++; }
static int ps_chr(Parser *ps, char c) { ps_ws(ps); if (*ps->p == c) { ps->p++; return 1; } return 0; }

static char *ps_string(Parser *ps)
{
    ps_ws(ps);
    if (*ps->p != '"') return NULL;
    ps->p++;
    Str s; str_init(&s);
    while (*ps->p && *ps->p != '"') {
        char tmp[2] = {0,0};
        if (*ps->p == '\\' && ps->p[1]) {
            ps->p++;
            switch (*ps->p) {
                case 'n': tmp[0] = '\n'; break;
                case 'r': tmp[0] = '\r'; break;
                case 't': tmp[0] = '\t'; break;
                default: tmp[0] = *ps->p; break;
            }
        } else tmp[0] = *ps->p;
        str_add(&s, tmp);
        ps->p++;
    }
    if (*ps->p == '"') ps->p++;
    return s.buf;
}

static void ps_skip_value(Parser *ps);

static int ps_bool(Parser *ps, int *out)
{
    ps_ws(ps);
    if (strncmp(ps->p, "true", 4) == 0) { ps->p += 4; if (out) *out = 1; return 1; }
    if (strncmp(ps->p, "false", 5) == 0) { ps->p += 5; if (out) *out = 0; return 1; }
    if (*ps->p == '1') { ps->p++; if (out) *out = 1; return 1; }
    if (*ps->p == '0') { ps->p++; if (out) *out = 0; return 1; }
    return 0;
}

static No *ps_node(Parser *ps)
{
    if (!ps_chr(ps, '{')) return NULL;
    No *n = no_criar("");
    if (!n) return NULL;

    while (1) {
        ps_ws(ps);
        if (ps_chr(ps, '}')) break;
        char *key = ps_string(ps);
        if (!key) { no_liberar(n); free(n); return NULL; }
        if (!ps_chr(ps, ':')) { free(key); no_liberar(n); free(n); return NULL; }

        if (strcmp(key, "nome") == 0) {
            char *v = ps_string(ps);
            if (v) { free(n->nome); n->nome = v; }
        } else if (strcmp(key, "cor") == 0) {
            char *v = ps_string(ps);
            if (v) { free(n->cor); n->cor = v; }
        } else if (strcmp(key, "status") == 0) {
            char *v = ps_string(ps);
            if (v) { free(n->status); n->status = v; }
        } else if (strcmp(key, "rel") == 0) {
            char *v = ps_string(ps);
            if (v) { free(n->rel); n->rel = v; }
        } else if (strcmp(key, "valor") == 0) {
            char *v = ps_string(ps);
            if (v) { free(n->valor); n->valor = v; }
        } else if (strcmp(key, "vlt") == 0 || strcmp(key, "valorTransferencia") == 0) {
            char *v = ps_string(ps);
            if (v) { free(n->vlt); n->vlt = v; }
        } else if (strcmp(key, "clt") == 0 || strcmp(key, "contasTransferencia") == 0) {
            char *v = ps_string(ps);
            if (v) { free(n->clt); n->clt = v; }
        } else if (strcmp(key, "configVin") == 0 || strcmp(key, "config_vin") == 0) {
            char *v = ps_string(ps);
            if (v) { free(n->config_vin); n->config_vin = v; }
        } else if (strcmp(key, "gc") == 0) {
            char *v = ps_string(ps);
            if (v) { free(n->gc); n->gc = v; }
        } else if (strcmp(key, "mm") == 0) {
            char *v = ps_string(ps);
            if (v) { free(n->mm); n->mm = v; }
        } else if (strcmp(key, "itens") == 0) {
            char *v = ps_string(ps);
            if (v) { free(n->itens); n->itens = v; }
        } else if (strcmp(key, "ra") == 0) {
            char *v = ps_string(ps);
            if (v) { free(n->ra); n->ra = v; }
        } else if (strcmp(key, "recolhido") == 0) {
            int v = 0;
            if (ps_bool(ps, &v)) n->recolhido = v ? 1 : 0;
            else ps_skip_value(ps);
        } else if (strcmp(key, "filhos") == 0) {
            if (ps_chr(ps, '[')) {
                while (1) {
                    ps_ws(ps);
                    if (ps_chr(ps, ']')) break;
                    No *f = ps_node(ps);
                    if (f) no_add_filho(n, f);
                    ps_ws(ps);
                    ps_chr(ps, ',');
                }
            } else ps_skip_value(ps);
        } else {
            ps_skip_value(ps);
        }
        free(key);
        ps_ws(ps);
        ps_chr(ps, ',');
    }
    return n;
}

static void ps_skip_value(Parser *ps)
{
    ps_ws(ps);
    if (*ps->p == '"') { char *x = ps_string(ps); free(x); return; }
    if (*ps->p == '{') {
        int nivel = 0, str = 0, esc = 0;
        do {
            char c = *ps->p++;
            if (str) { if (esc) esc = 0; else if (c == '\\') esc = 1; else if (c == '"') str = 0; }
            else { if (c == '"') str = 1; else if (c == '{') nivel++; else if (c == '}') nivel--; }
        } while (*ps->p && nivel > 0);
        return;
    }
    if (*ps->p == '[') {
        int nivel = 0, str = 0, esc = 0;
        do {
            char c = *ps->p++;
            if (str) { if (esc) esc = 0; else if (c == '\\') esc = 1; else if (c == '"') str = 0; }
            else { if (c == '"') str = 1; else if (c == '[') nivel++; else if (c == ']') nivel--; }
        } while (*ps->p && nivel > 0);
        return;
    }
    while (*ps->p && *ps->p != ',' && *ps->p != '}' && *ps->p != ']') ps->p++;
}

static int parse_arvore(const char *json, Arvore *a)
{
    Parser ps = { json };
    if (!ps_chr(&ps, '{')) return -1;
    memset(a, 0, sizeof(*a));
    no_init(&a->raiz, "");
    int tipo_sa = 0;

    while (1) {
        ps_ws(&ps);
        if (ps_chr(&ps, '}')) break;
        char *key = ps_string(&ps);
        if (!key) return -1;
        if (!ps_chr(&ps, ':')) { free(key); return -1; }

        if (strcmp(key, "tipo") == 0) {
            char *tipo = ps_string(&ps);
            if (tipo && strcmp(tipo, "arvore-sa") == 0) tipo_sa = 1;
            free(tipo);
        } else if (strcmp(key, "nome") == 0) {
            a->nome = ps_string(&ps);
            free(a->raiz.nome); a->raiz.nome = xstrdup(a->nome ? a->nome : "");
        } else if (strcmp(key, "criadoEm") == 0) {
            a->criadoEm = ps_string(&ps);
        } else if (strcmp(key, "atualizadoEm") == 0) {
            a->atualizadoEm = ps_string(&ps);
        } else if (strcmp(key, "filhos") == 0) {
            if (ps_chr(&ps, '[')) {
                while (1) {
                    ps_ws(&ps);
                    if (ps_chr(&ps, ']')) break;
                    No *f = ps_node(&ps);
                    if (f) no_add_filho(&a->raiz, f);
                    ps_ws(&ps);
                    ps_chr(&ps, ',');
                }
            } else ps_skip_value(&ps);
        } else ps_skip_value(&ps);
        free(key);
        ps_ws(&ps);
        ps_chr(&ps, ',');
    }
    if (!tipo_sa) {
        free(a->nome);
        free(a->criadoEm);
        free(a->atualizadoEm);
        no_liberar(&a->raiz);
        memset(a, 0, sizeof(*a));
        return -1;
    }
    if (!a->nome) a->nome = xstrdup(a->raiz.nome);
    if (!a->criadoEm) a->criadoEm = agora_iso();
    if (!a->atualizadoEm) a->atualizadoEm = agora_iso();
    return 0;
}

static void json_escape(Str *s, const char *v)
{
    str_add(s, "\"");
    for (const char *p = v ? v : ""; *p; p++) {
        char tmp[8];
        switch (*p) {
            case '"': str_add(s, "\\\""); break;
            case '\\': str_add(s, "\\\\"); break;
            case '\n': str_add(s, "\\n"); break;
            case '\r': str_add(s, "\\r"); break;
            case '\t': str_add(s, "\\t"); break;
            default: tmp[0] = *p; tmp[1] = '\0'; str_add(s, tmp); break;
        }
    }
    str_add(s, "\"");
}

static void json_indent(Str *s, int n) { for (int i = 0; i < n; i++) str_add(s, " "); }

static void json_no(Str *s, No *n, int ind)
{
    str_add(s, "{\n");
    json_indent(s, ind + 4); str_add(s, "\"nome\": "); json_escape(s, n->nome); str_add(s, ",\n");
    if (n->cor && n->cor[0]) {
        json_indent(s, ind + 4); str_add(s, "\"cor\": "); json_escape(s, n->cor); str_add(s, ",\n");
    }
    if (n->status && n->status[0]) {
        json_indent(s, ind + 4); str_add(s, "\"status\": "); json_escape(s, n->status); str_add(s, ",\n");
    }
    if (n->rel && n->rel[0]) {
        json_indent(s, ind + 4); str_add(s, "\"rel\": "); json_escape(s, n->rel); str_add(s, ",\n");
    }
    if (n->valor && n->valor[0]) {
        json_indent(s, ind + 4); str_add(s, "\"valor\": "); json_escape(s, n->valor); str_add(s, ",\n");
    }
    if (n->vlt && n->vlt[0]) {
        json_indent(s, ind + 4); str_add(s, "\"vlt\": "); json_escape(s, n->vlt); str_add(s, ",\n");
    }
    if (n->clt && n->clt[0]) {
        json_indent(s, ind + 4); str_add(s, "\"clt\": "); json_escape(s, n->clt); str_add(s, ",\n");
    }
    if (n->config_vin && n->config_vin[0]) {
        json_indent(s, ind + 4); str_add(s, "\"configVin\": "); json_escape(s, n->config_vin); str_add(s, ",\n");
    }
    if (n->gc && n->gc[0]) {
        json_indent(s, ind + 4); str_add(s, "\"gc\": "); json_escape(s, n->gc); str_add(s, ",\n");
    }
    if (n->mm && n->mm[0]) {
        json_indent(s, ind + 4); str_add(s, "\"mm\": "); json_escape(s, n->mm); str_add(s, ",\n");
    }
    if (n->itens && n->itens[0]) {
        json_indent(s, ind + 4); str_add(s, "\"itens\": "); json_escape(s, n->itens); str_add(s, ",\n");
    }
    if (n->ra && n->ra[0]) {
        json_indent(s, ind + 4); str_add(s, "\"ra\": "); json_escape(s, n->ra); str_add(s, ",\n");
    }
    if (n->recolhido) {
        json_indent(s, ind + 4); str_add(s, "\"recolhido\": true,\n");
    }
    json_indent(s, ind + 4); str_add(s, "\"filhos\": [");
    if (n->total > 0) str_add(s, "\n");
    for (int i = 0; i < n->total; i++) {
        json_indent(s, ind + 8);
        json_no(s, n->filhos[i], ind + 8);
        if (i < n->total - 1) str_add(s, ",");
        str_add(s, "\n");
    }
    if (n->total > 0) json_indent(s, ind + 4);
    str_add(s, "]\n");
    json_indent(s, ind); str_add(s, "}");
}

static char *arvore_json(Arvore *a)
{
    Str s; str_init(&s);
    str_add(&s, "{\n    \"tipo\": \"arvore-sa\",\n");
    str_add(&s, "    \"nome\": "); json_escape(&s, a->nome); str_add(&s, ",\n");
    str_add(&s, "    \"criadoEm\": "); json_escape(&s, a->criadoEm); str_add(&s, ",\n");
    str_add(&s, "    \"atualizadoEm\": "); json_escape(&s, a->atualizadoEm); str_add(&s, ",\n");
    str_add(&s, "    \"filhos\": [");
    if (a->raiz.total > 0) str_add(&s, "\n");
    for (int i = 0; i < a->raiz.total; i++) {
        str_add(&s, "        ");
        json_no(&s, a->raiz.filhos[i], 8);
        if (i < a->raiz.total - 1) str_add(&s, ",");
        str_add(&s, "\n");
    }
    if (a->raiz.total > 0) str_add(&s, "    ");
    str_add(&s, "]\n}\n");
    return s.buf;
}

static void arvore_liberar(Arvore *a)
{
    free(a->nome); free(a->criadoEm); free(a->atualizadoEm); no_liberar(&a->raiz);
}

typedef struct {
    const char *nome;
    const char *ansi;
    const char *aliases;
    const char *descricao;
} CorOpcao;

/* --- proc/search globals --- */
static char *g_proc_termo = NULL;  /* termo de busca ativo; NULL = sem filtro */
static const char *g_item_destaque_ids = NULL; /* IDs exibidos em rosa choque nesta execução */
#define SA_ITEM_DESTAQUE_ANSI "1;38;5;201"

static int id_em_lista_ci(const char *lista, const char *id)
{
    if (!lista || !*lista || !id || !*id) return 0;
    size_t id_len = strlen(id);
    const char *p = lista;
    while (*p) {
        while (*p == ',' || isspace((unsigned char)*p)) p++;
        const char *ini = p;
        while (*p && *p != ',') p++;
        const char *fim = p;
        while (fim > ini && isspace((unsigned char)fim[-1])) fim--;
        if ((size_t)(fim - ini) == id_len && strncmp(ini, id, id_len) == 0) return 1;
        if (*p == ',') p++;
    }
    return 0;
}

static int texto_contem_ci_static(const char *texto, const char *agulha)
{
    if (!texto || !agulha || !*agulha) return 0;
    size_t n = strlen(agulha);
    for (const char *p = texto; *p; p++) {
        size_t i = 0;
        while (i < n && p[i] && tolower((unsigned char)p[i]) == tolower((unsigned char)agulha[i])) i++;
        if (i == n) return 1;
    }
    return 0;
}

static int no_ou_filhos_contem_termo(No *n, const char *termo)
{
    if (!n || !termo || !*termo) return 0;
    if (texto_contem_ci_static(n->nome, termo)) return 1;
    for (int i = 0; i < n->total; i++) {
        if (no_ou_filhos_contem_termo(n->filhos[i], termo)) return 1;
    }
    /* tambem busca em mm, gc, rel, etc. */
    if (n->mm && texto_contem_ci_static(n->mm, termo)) return 1;
    if (n->gc && texto_contem_ci_static(n->gc, termo)) return 1;
    if (n->rel && texto_contem_ci_static(n->rel, termo)) return 1;
    if (n->valor && texto_contem_ci_static(n->valor, termo)) return 1;
    if (n->itens && texto_contem_ci_static(n->itens, termo)) return 1;
    if (n->ra && texto_contem_ci_static(n->ra, termo)) return 1;
    return 0;
}

#define SA_COR_ANDAMENTO_GC_BRILHANTE "38;5;226"

static const CorOpcao CORES_DISPONIVEIS[] = {
    { "padrao",        "32",        "default|normal|padrão",                 "cor padrao da arvore" },
    { "preto",         "30",        "black",                                 "preto" },
    { "vermelho",      "31",        "red|vermelha",                          "vermelho" },
    { "verde",         "32",        "green",                                 "verde" },
    { "amarelo",       "33",        "yellow|amarela",                        "amarelo" },
    { "azul",          "34",        "blue",                                  "azul" },
    { "magenta",       "35",        "roxo|roxa|purple",                      "magenta/roxo" },
    { "branco",        "37",        "white|branca",                          "branco" },
    { "cinza",         "90",        "gray|grey|bright_black",                "cinza" },
    { "vermelho-claro","91",        "bright_red|vermelha-clara",             "vermelho claro" },
    { "verde-claro",   "92",        "bright_green",                          "verde claro" },
    { "amarelo-claro", "93",        "bright_yellow|amarela-clara",           "amarelo claro" },
    { "azul-claro",    "94",        "bright_blue",                           "azul claro" },
    { "magenta-claro", "95",        "bright_magenta|roxo-claro|roxa-clara",  "magenta/roxo claro" },
    { "branco-claro",  "97",        "bright_white",                          "branco claro" },
    { "ambar",         SA_COR_ANDAMENTO_GC_BRILHANTE, "amber|amarelo-intenso", "âmbar/amarelo intenso brilhante" },
    { "laranja",       SA_COR_ANDAMENTO_GC_BRILHANTE, "orange",              "compatibilidade: usa âmbar brilhante" },
    { "rosa",          "38;5;205",  "pink",                                  "rosa" },
    { "violeta",       "38;5;99",   "violet",                                "violeta" },
    { "indigo",        "38;5;54",   "indigo|anil",                           "indigo/anil" },
    { "turquesa",      "38;5;45",   "turquoise",                             "turquesa" },
    { "lima",          "38;5;154",  "lime",                                  "lima" },
    { "marrom",        "38;5;94",   "brown",                                 "marrom" }
};

static int cor_alias_igual(const char *aliases, const char *cor)
{
    if (!aliases || !cor || !*cor) return 0;
    size_t n_cor = strlen(cor);
    const char *p = aliases;
    while (*p) {
        const char *fim = strchr(p, '|');
        size_t n = fim ? (size_t)(fim - p) : strlen(p);
        if (n == n_cor && strncasecmp(p, cor, n) == 0) return 1;
        if (!fim) break;
        p = fim + 1;
    }
    return 0;
}

static const CorOpcao *cor_encontrar(const char *cor)
{
    if (!cor || !*cor) return &CORES_DISPONIVEIS[0];
    size_t total = sizeof(CORES_DISPONIVEIS) / sizeof(CORES_DISPONIVEIS[0]);
    for (size_t i = 0; i < total; i++) {
        if (strcasecmp(cor, CORES_DISPONIVEIS[i].nome) == 0 || cor_alias_igual(CORES_DISPONIVEIS[i].aliases, cor)) return &CORES_DISPONIVEIS[i];
    }
    return NULL;
}

#define SA_MAX_GCS 1
#define SA_RA_ANSI "1;38;5;154"
static const char *SA_CORES_GC[SA_MAX_GCS] = { "laranja" };

/* Status para cor:
   sem status/null=branco, pendente=amarelo igual ao ID sem filhos, andamento=âmbar brilhante,
   aprovado=verde brilhante, reprovado=vermelho brilhante */
typedef struct { const char *status; const char *cor; } StatusCor;
static const StatusCor SA_STATUS_CORES[] = {
    { "pendente",  "1;33" },
    { "andamento", SA_COR_ANDAMENTO_GC_BRILHANTE },
    { "aprovado",  "92" },
    { "reprovado", "91" },
};

static int status_remove(const char *s)
{
    return s && strcasecmp(s, "null") == 0;
}

static const char *status_para_ansi(const char *status)
{
    if (!status || !*status || status_remove(status)) return "37";
    for (size_t i = 0; i < sizeof(SA_STATUS_CORES) / sizeof(SA_STATUS_CORES[0]); i++) {
        if (strcasecmp(status, SA_STATUS_CORES[i].status) == 0) return SA_STATUS_CORES[i].cor;
    }
    return CORES_DISPONIVEIS[0].ansi;
}

static int status_valido(const char *s)
{
    if (!s || !*s) return 0;
    /* Valores aceitos via --status (operador): aprovado, reprovado, andamento, null. */
    return (strcasecmp(s, "aprovado") == 0 || strcasecmp(s, "reprovado") == 0 || strcasecmp(s, "andamento") == 0 || status_remove(s));
}

static int no_definir_status_rec(No *n, const char *status)
{
    if (!n || !status) return 0;
    free(n->status);
    free(n->cor);
    if (status_remove(status)) {
        n->status = NULL;
        n->cor = NULL;
    } else {
        n->status = xstrdup(status);
        n->cor = xstrdup(status_para_ansi(status));
    }
    int total = 1;
    for (int i = 0; i < n->total; i++) total += no_definir_status_rec(n->filhos[i], status);
    return total;
}

static const char *cor_para_ansi(const char *cor)
{
    const CorOpcao *op = cor_encontrar(cor);
    if (op) return op->ansi;
    if (cor && (strcmp(cor, "1;38;5;214") == 0 || strcmp(cor, "38;5;214") == 0 || strcmp(cor, "38;5;208") == 0)) return SA_COR_ANDAMENTO_GC_BRILHANTE;
    if (cor && isdigit((unsigned char)cor[0])) return cor;
    return CORES_DISPONIVEIS[0].ansi;
}

static void legenda_linha(const char *ansi, const char *cor, const char *uso, const char *significado)
{
    printf("  \033[%sm%-18s\033[0m  %-32s  %s\n", ansi, cor, uso, significado);
}

static void imprimir_legenda(void)
{
    printf("Legenda de cores do pp --sa\n");
    printf("===============================================\n\n");

    printf("Status dos itens\n");
    legenda_linha(status_para_ansi("pendente"),  "amarelo", "status=pendente",  "item ainda não concluído; mesma cor de ID sem filhos");
    legenda_linha(status_para_ansi("andamento"), "âmbar brilhante", "status=andamento", "item em execução/análise");
    legenda_linha(status_para_ansi("aprovado"),  "verde claro",       "status=aprovado",  "item aprovado");
    legenda_linha(status_para_ansi("reprovado"), "vermelho claro",    "status=reprovado", "item reprovado");
    legenda_linha("37",                          "branco",            "sem status",       "texto de item sem status explícito");

    printf("\nEstrutura e destaque\n");
    legenda_linha("37",          "branco",           "nome da árvore",          "tronco/título da árvore");
    legenda_linha("1;36",        "ciano",            "ID com filhos",           "item expansível/galho");
    legenda_linha("1;33",        "amarelo",          "ID sem filhos",           "item folha");
    legenda_linha("1;38;5;201",  "rosa choque",      "resultado de --proc",     "item ou descendente que contém o termo procurado");
    legenda_linha(SA_ITEM_DESTAQUE_ANSI, "rosa choque", "criado/editado", "item criado ou editado nesta execução");
    legenda_linha("0;90",        "cinza",            "[recolhido]",            "subárvore ocultada na exibição");

    printf("\nGrupos de comentário (GC)\n");
    legenda_linha(cor_para_ansi(SA_CORES_GC[0]), "âmbar brilhante", "GC único / mm", "único grupo de comentário permitido na árvore");
    legenda_linha("1;97",                     "branco brilhante", "prefixo gc:", "rótulo do GC antes da mensagem");

    printf("\nMetadados exibidos entre colchetes\n");
    legenda_linha(SA_RA_ANSI, "verde-limão", "[sisc/arquivo]",      "referência a arquivo do sistema");
    legenda_linha("0;36",     "ciano",       "[rel] [vin] [clt]",   "relações, vínculos e contas de transferência");
    legenda_linha("0;35",     "magenta",     "[valor] [vlt]",       "valor direto ou valor de transferência");
    legenda_linha("0;35",     "magenta",     "[td] [tr] [tt] [sd]", "totais vinculados e saldo");
    legenda_linha("0;33",     "amarelo",     "[fd] [fr] [ft] [sd]", "totais dos filhos diretos e saldo");

    printf("\nUso: pp --sa --legenda\n");
}

static int caminho_valido(const char *c)
{
    if (!c || !*c) return 1;
    int espera_dig = 1;
    for (const char *p = c; *p; p++) {
        if (espera_dig) { if (!isdigit((unsigned char)*p) || *p == '0') return 0; espera_dig = 0; }
        else if (*p == '.') espera_dig = 1;
        else if (!isdigit((unsigned char)*p)) return 0;
    }
    return !espera_dig;
}

static int ra_lista_valida(const char *ra, char *erro, size_t erro_tam)
{
    if (!ra || !*ra) return 1; /* vazio remove a referência */
    char *dup = strdup(ra);
    if (!dup) { snprintf(erro, erro_tam, "erro de memória"); return 0; }
    char *tok, *save;
    tok = strtok_r(dup, ",", &save);
    while (tok) {
        while (isspace((unsigned char)*tok)) tok++;
        char *end = tok + strlen(tok);
        while (end > tok && isspace((unsigned char)end[-1])) end--;
        *end = '\0';
        if (!*tok) {
            snprintf(erro, erro_tam, "referência vazia em --ra");
            free(dup); return 0;
        }
        if (tok[0] == '/' || strncmp(tok, "sisc/", 5) != 0 || strstr(tok, "..")) {
            snprintf(erro, erro_tam, "referência inválida '%s'; use paths desde sisc/, sem caminho absoluto ou '..'", tok);
            free(dup); return 0;
        }
        tok = strtok_r(NULL, ",", &save);
    }
    free(dup);
    return 1;
}

static No *no_encontrar_por_gc_rec(No *n, const char *gc_name)
{
    if (!n || !gc_name || !*gc_name) return NULL;
    if (n->gc && n->gc[0] && strcmp(n->gc, gc_name) == 0) return n;
    for (int i = 0; i < n->total; i++) {
        No *e = no_encontrar_por_gc_rec(n->filhos[i], gc_name);
        if (e) return e;
    }
    return NULL;
}

static No *encontrar_no(No *raiz, const char *caminho, No **pai_out, int *idx_out);

static int gc_indice_lista(const char *gcs[], int total, const char *nome)
{
    if (!nome || !*nome) return -1;
    for (int i = 0; i < total; i++) {
        if (gcs[i] && strcmp(gcs[i], nome) == 0) return i;
    }
    return -1;
}

static int no_coletar_gcs_rec(No *n, const char *gcs[], int *total)
{
    if (!n || !total) return 0;
    if (n->gc && n->gc[0] && gc_indice_lista(gcs, *total, n->gc) < 0) {
        if (*total >= SA_MAX_GCS) return -1;
        gcs[(*total)++] = n->gc;
    }
    for (int i = 0; i < n->total; i++) {
        if (no_coletar_gcs_rec(n->filhos[i], gcs, total) != 0) return -1;
    }
    return 0;
}

static void no_definir_cor(No *n, const char *cor)
{
    if (!n) return;
    free(n->cor);
    n->cor = (cor && cor[0]) ? xstrdup(cor) : NULL;
}

static void aplicar_cor_ids(No *raiz, const char *ids, const char *cor, const char *gc_nome)
{
    if (!raiz || !ids || !*ids || !cor || !*cor) return;
    char *itens_copy = strdup(ids);
    if (!itens_copy) return;
    char *tok, *save;
    tok = strtok_r(itens_copy, ",", &save);
    while (tok) {
        while (isspace((unsigned char)*tok)) tok++;
        char *end = tok + strlen(tok);
        while (end > tok && isspace((unsigned char)end[-1])) end--;
        *end = '\0';
        if (*tok) {
            No *item_no = encontrar_no(raiz, tok, NULL, NULL);
            if (item_no && item_no != raiz &&
                (!item_no->gc || !item_no->gc[0] || (gc_nome && strcmp(item_no->gc, gc_nome) == 0))) {
                no_definir_cor(item_no, cor);
            }
        }
        tok = strtok_r(NULL, ",", &save);
    }
    free(itens_copy);
}

static void limpar_cor_ids_sem_gc(No *raiz, const char *ids)
{
    if (!raiz || !ids || !*ids) return;
    char *itens_copy = strdup(ids);
    if (!itens_copy) return;
    char *tok, *save;
    tok = strtok_r(itens_copy, ",", &save);
    while (tok) {
        while (isspace((unsigned char)*tok)) tok++;
        char *end = tok + strlen(tok);
        while (end > tok && isspace((unsigned char)end[-1])) end--;
        *end = '\0';
        if (*tok) {
            No *item_no = encontrar_no(raiz, tok, NULL, NULL);
            if (item_no && item_no != raiz && (!item_no->gc || !item_no->gc[0])) no_definir_cor(item_no, NULL);
        }
        tok = strtok_r(NULL, ",", &save);
    }
    free(itens_copy);
}

static int no_prepend_mm(No *n, const char *mm)
{
    if (!n || !mm || !mm[0]) return 0;
    if (n->mm && n->mm[0]) {
        size_t old_len = strlen(n->mm);
        size_t new_len = strlen(mm);
        char *novo = malloc(old_len + new_len + 6);
        if (!novo) return -1;
        memcpy(novo, mm, new_len);
        memcpy(novo + new_len, "\n---\n", 5);
        memcpy(novo + new_len + 5, n->mm, old_len + 1);
        free(n->mm);
        n->mm = novo;
    } else {
        char *novo = xstrdup(mm);
        if (!novo) return -1;
        free(n->mm);
        n->mm = novo;
    }
    return 0;
}

static int aplicar_mm_em_lista(No *raiz, const char *em_lista, const char *gc, const char *mm,
                               const char *itens, int *total_out, char *erro, size_t erro_tam)
{
    if (total_out) *total_out = 0;
    if (!raiz || !em_lista || !*em_lista || !mm || !*mm) {
        snprintf(erro, erro_tam, "--mm exige --em=<id> ou --em=<id1,id2,...>");
        return -1;
    }

    char *dup = strdup(em_lista);
    if (!dup) { snprintf(erro, erro_tam, "erro de memória"); return -1; }

    int total = 0;
    char *tok, *save;
    tok = strtok_r(dup, ",", &save);
    while (tok) {
        while (isspace((unsigned char)*tok)) tok++;
        char *end = tok + strlen(tok);
        while (end > tok && isspace((unsigned char)end[-1])) end--;
        *end = '\0';

        if (!*tok) {
            snprintf(erro, erro_tam, "--em contém item vazio");
            free(dup); return -1;
        }
        if (!caminho_valido(tok)) {
            snprintf(erro, erro_tam, "caminho inválido em --em: %s", tok);
            free(dup); return -1;
        }
        No *no = encontrar_no(raiz, tok, NULL, NULL);
        if (!no || no == raiz) {
            snprintf(erro, erro_tam, "galho não encontrado na posição %s", tok);
            free(dup); return -1;
        }

        if (gc && gc[0]) {
            char *novo_gc = xstrdup(gc);
            if (!novo_gc) { snprintf(erro, erro_tam, "erro de memória"); free(dup); return -1; }
            free(no->gc);
            no->gc = novo_gc;
        }
        if (no_prepend_mm(no, mm) != 0) {
            snprintf(erro, erro_tam, "erro de memória");
            free(dup); return -1;
        }
        /* item que recebe mm de GC automaticamente fica em andamento */
        if (gc && gc[0]) {
            free(no->status);
            no->status = xstrdup("andamento");
        }
        if (itens && itens[0]) {
            char *novo_itens = xstrdup(itens);
            if (!novo_itens) { snprintf(erro, erro_tam, "erro de memória"); free(dup); return -1; }
            limpar_cor_ids_sem_gc(raiz, no->itens);
            free(no->itens);
            no->itens = novo_itens;
        }
        total++;
        tok = strtok_r(NULL, ",", &save);
    }
    free(dup);

    if (total <= 0) {
        snprintf(erro, erro_tam, "--em contém item vazio");
        return -1;
    }
    if (total_out) *total_out = total;
    return 0;
}

static void no_aplicar_cores_gcs_rec(No *n, No *raiz, const char *gcs[], int total)
{
    if (!n || !raiz) return;
    if (n->gc && n->gc[0]) {
        int idx = gc_indice_lista(gcs, total, n->gc);
        if (idx >= 0 && idx < SA_MAX_GCS) {
            const char *cor_gc = SA_CORES_GC[idx];
            no_definir_cor(n, cor_gc);
            aplicar_cor_ids(raiz, n->itens, cor_gc, n->gc);
        }
    }
    for (int i = 0; i < n->total; i++) no_aplicar_cores_gcs_rec(n->filhos[i], raiz, gcs, total);
}

static int arvore_aplicar_cores_gcs(Arvore *arv, int *total_out)
{
    const char *gcs[SA_MAX_GCS] = { NULL };
    int total = 0;
    if (!arv) return 0;
    if (no_coletar_gcs_rec(&arv->raiz, gcs, &total) != 0) return -1;
    no_aplicar_cores_gcs_rec(&arv->raiz, &arv->raiz, gcs, total);
    if (total_out) *total_out = total;
    return 0;
}

static void erro_limite_gcs(void)
{
    fprintf(stderr, "sa: limite de %d gc por árvore excedido. Remova o GC existente com --lgc ou reutilize o mesmo --gc. Cor de GC disponível: âmbar brilhante.\n", SA_MAX_GCS);
}

static void no_limpar_gc_rec(No *n)
{
    if (!n) return;
    free(n->gc); n->gc = NULL;
    free(n->mm); n->mm = NULL;
    free(n->itens); n->itens = NULL;
    free(n->cor); n->cor = NULL;
    free(n->status); n->status = NULL;
    for (int i = 0; i < n->total; i++) no_limpar_gc_rec(n->filhos[i]);
}

static void no_limpar_gc_por_nome_rec(No *n, const char *nome_gc, No *raiz)
{
    if (!n || !nome_gc || !*nome_gc) return;
    if (n->gc && n->gc[0] && strcmp(n->gc, nome_gc) == 0) {
        /* Limpa os itens relacionados deste gc */
        if (n->itens && n->itens[0] && raiz) {
            char *itens_copy = strdup(n->itens);
            if (itens_copy) {
                char *tok, *save;
                tok = strtok_r(itens_copy, ",", &save);
                while (tok) {
                    while (*tok == ' ') tok++;
                    char *end = tok + strlen(tok);
                    while (end > tok && end[-1] == ' ') end--;
                    *end = '\0';
                    if (*tok) {
                        No *item_no = encontrar_no(raiz, tok, NULL, NULL);
                        if (item_no && item_no != raiz) {
                            /* So limpa a cor se o item nao tiver seu proprio gc */
                            if (!item_no->gc || !item_no->gc[0]) {
                                free(item_no->cor);
                                item_no->cor = NULL;
                                free(item_no->status);
                                item_no->status = NULL;
                            }
                        }
                    }
                    tok = strtok_r(NULL, ",", &save);
                }
                free(itens_copy);
            }
        }
        free(n->gc); n->gc = NULL;
        free(n->mm); n->mm = NULL;
        free(n->itens); n->itens = NULL;
        free(n->cor); n->cor = NULL;
        free(n->status); n->status = NULL;
    }
    for (int i = 0; i < n->total; i++) no_limpar_gc_por_nome_rec(n->filhos[i], nome_gc, raiz);
}

static No *encontrar_no(No *raiz, const char *caminho, No **pai_out, int *idx_out)
{
    if (pai_out) *pai_out = NULL;
    if (idx_out) *idx_out = -1;
    if (!caminho || !*caminho) return raiz;
    char *dup = strdup(caminho), *save = NULL, *tok = strtok_r(dup, ".", &save);
    No *atual = raiz, *pai = NULL;
    int idx = -1;
    while (tok) {
        int n = atoi(tok);
        idx = n - 1;
        if (idx < 0 || idx >= atual->total) { free(dup); return NULL; }
        pai = atual;
        atual = atual->filhos[idx];
        tok = strtok_r(NULL, ".", &save);
    }
    free(dup);
    if (pai_out) *pai_out = pai;
    if (idx_out) *idx_out = idx;
    return atual;
}

static void construir_id(char *buf, size_t tam, int *indices, int prof)
{
    buf[0] = '\0';
    for (int i = 0; i < prof; i++) {
        char tmp[32];
        snprintf(tmp, sizeof(tmp), "%s%d", i ? "." : "", indices[i] + 1);
        strncat(buf, tmp, tam - strlen(buf) - 1);
    }
}

static int no_contem(No *raiz, No *alvo)
{
    if (!raiz || !alvo) return 0;
    if (raiz == alvo) return 1;
    for (int i = 0; i < raiz->total; i++)
        if (no_contem(raiz->filhos[i], alvo)) return 1;
    return 0;
}

static int caminho_para_no_rec(No *atual, No *alvo, int *indices, int prof, char *out, size_t tam)
{
    if (atual == alvo) {
        construir_id(out, tam, indices, prof);
        return 1;
    }
    for (int i = 0; i < atual->total; i++) {
        indices[prof] = i;
        if (caminho_para_no_rec(atual->filhos[i], alvo, indices, prof + 1, out, tam)) return 1;
    }
    return 0;
}

static char *caminho_de_no(No *raiz, No *alvo)
{
    char buf[1024];
    int indices[256];
    buf[0] = '\0';
    if (!caminho_para_no_rec(raiz, alvo, indices, 0, buf, sizeof(buf))) return NULL;
    return xstrdup(buf);
}

static int config_vin_token_igual(const char *ini, size_t n, const char *id)
{
    while (n > 0 && isspace((unsigned char)*ini)) { ini++; n--; }
    while (n > 0 && isspace((unsigned char)ini[n - 1])) n--;
    return strlen(id ? id : "") == n && strncmp(ini, id, n) == 0;
}

static int config_vin_contem(const char *lista, const char *id)
{
    if (!lista || !*lista || !id || !*id) return 0;
    const char *p = lista;
    while (*p) {
        const char *fim = strchr(p, ',');
        size_t n = fim ? (size_t)(fim - p) : strlen(p);
        if (config_vin_token_igual(p, n, id)) return 1;
        if (!fim) break;
        p = fim + 1;
    }
    return 0;
}

static char *config_vin_adicionar(const char *lista, const char *id)
{
    if (!id || !*id) return xstrdup(lista ? lista : "");
    if (config_vin_contem(lista, id)) return xstrdup(lista);
    if (!lista || !*lista) return xstrdup(id);
    size_t a = strlen(lista), b = strlen(id);
    char *out = malloc(a + b + 2);
    if (!out) return NULL;
    memcpy(out, lista, a);
    out[a] = ',';
    memcpy(out + a + 1, id, b + 1);
    return out;
}

static char *config_vin_remover(const char *lista, const char *id)
{
    if (!lista || !*lista || !id || !*id) return xstrdup(lista ? lista : "");
    Str s; str_init(&s);
    const char *p = lista;
    int primeiro = 1;
    while (*p) {
        const char *fim = strchr(p, ',');
        size_t n = fim ? (size_t)(fim - p) : strlen(p);
        const char *ini = p;
        while (n > 0 && isspace((unsigned char)*ini)) { ini++; n--; }
        while (n > 0 && isspace((unsigned char)ini[n - 1])) n--;
        if (!config_vin_token_igual(ini, n, id) && n > 0) {
            if (!primeiro) str_add(&s, ",");
            char *tmp = malloc(n + 1);
            if (!tmp) { free(s.buf); return NULL; }
            memcpy(tmp, ini, n); tmp[n] = '\0';
            str_add(&s, tmp);
            free(tmp);
            primeiro = 0;
        }
        if (!fim) break;
        p = fim + 1;
    }
    return s.buf;
}

static int clt_id_mundo_externo(const char *id);

typedef struct {
    No **srcs;
    No **dsts;
    int *tipos; /* 0=rel, 1=config_vin, 2=clt */
    int total;
    int cap;
} RelMaps;

static void relmaps_liberar(RelMaps *m)
{
    free(m->srcs);
    free(m->dsts);
    free(m->tipos);
    m->srcs = NULL; m->dsts = NULL; m->tipos = NULL; m->total = m->cap = 0;
}

static int relmaps_add(RelMaps *m, No *src, No *dst, int tipo)
{
    if (m->total >= m->cap) {
        int nova = m->cap ? m->cap * 2 : 16;
        No **ns = malloc((size_t)nova * sizeof(No *));
        No **nd = malloc((size_t)nova * sizeof(No *));
        int *nt = malloc((size_t)nova * sizeof(int));
        if (!ns || !nd || !nt) { free(ns); free(nd); free(nt); return -1; }
        for (int i = 0; i < m->total; i++) { ns[i] = m->srcs[i]; nd[i] = m->dsts[i]; nt[i] = m->tipos[i]; }
        free(m->srcs); free(m->dsts); free(m->tipos);
        m->srcs = ns;
        m->dsts = nd;
        m->tipos = nt;
        m->cap = nova;
    }
    m->srcs[m->total] = src;
    m->dsts[m->total] = dst;
    m->tipos[m->total] = tipo;
    m->total++;
    return 0;
}

static void relmaps_coletar_rec(No *raiz, No *n, RelMaps *m)
{
    if (n->rel && n->rel[0]) {
        No *dst = encontrar_no(raiz, n->rel, NULL, NULL);
        if (dst && dst != raiz) relmaps_add(m, n, dst, 0);
    }
    if (n->config_vin && n->config_vin[0]) {
        const char *p = n->config_vin;
        while (*p) {
            const char *fim = strchr(p, ',');
            size_t tam = fim ? (size_t)(fim - p) : strlen(p);
            while (tam > 0 && isspace((unsigned char)*p)) { p++; tam--; }
            while (tam > 0 && isspace((unsigned char)p[tam - 1])) tam--;
            if (tam > 0 && tam < 256) {
                char id[256]; memcpy(id, p, tam); id[tam] = '\0';
                No *dst = encontrar_no(raiz, id, NULL, NULL);
                if (dst && dst != raiz) relmaps_add(m, n, dst, 1);
            }
            if (!fim) break;
            p = fim + 1;
        }
    }
    if (n->clt && n->clt[0]) {
        const char *p = n->clt;
        while (*p) {
            const char *fim = strchr(p, ',');
            size_t tam = fim ? (size_t)(fim - p) : strlen(p);
            while (tam > 0 && isspace((unsigned char)*p)) { p++; tam--; }
            while (tam > 0 && isspace((unsigned char)p[tam - 1])) tam--;
            if (tam > 0 && tam < 256) {
                char id[256]; memcpy(id, p, tam); id[tam] = '\0';
                if (clt_id_mundo_externo(id)) {
                    relmaps_add(m, n, NULL, 2);
                } else {
                    No *dst = encontrar_no(raiz, id, NULL, NULL);
                    if (dst && dst != raiz) relmaps_add(m, n, dst, 2);
                }
            }
            if (!fim) break;
            p = fim + 1;
        }
    }
    for (int i = 0; i < n->total; i++) relmaps_coletar_rec(raiz, n->filhos[i], m);
}

static void relmaps_coletar(No *raiz, RelMaps *m)
{
    memset(m, 0, sizeof(*m));
    relmaps_coletar_rec(raiz, raiz, m);
}

static void relmaps_aplicar(No *raiz, RelMaps *m)
{
    for (int i = 0; i < m->total; i++) {
        if (m->tipos[i] == 1 && no_contem(raiz, m->srcs[i])) {
            free(m->srcs[i]->config_vin);
            m->srcs[i]->config_vin = NULL;
        } else if (m->tipos[i] == 2 && no_contem(raiz, m->srcs[i])) {
            free(m->srcs[i]->clt);
            m->srcs[i]->clt = NULL;
        }
    }
    for (int i = 0; i < m->total; i++) {
        No *src = m->srcs[i];
        No *dst = m->dsts[i];
        if (!no_contem(raiz, src)) continue;
        if (m->tipos[i] != 2 && !no_contem(raiz, dst)) continue;
        if (m->tipos[i] == 2 && dst && !no_contem(raiz, dst)) continue;
        char *novo = (m->tipos[i] == 2 && !dst) ? xstrdup("0") : caminho_de_no(raiz, dst);
        if (!novo || !*novo) { free(novo); continue; }
        if (m->tipos[i] == 0) {
            free(src->rel);
            src->rel = novo;
        } else if (m->tipos[i] == 1) {
            char *lista = config_vin_adicionar(src->config_vin, novo);
            free(novo);
            free(src->config_vin);
            src->config_vin = lista;
        } else {
            char *lista = config_vin_adicionar(src->clt, novo);
            free(novo);
            free(src->clt);
            src->clt = lista;
        }
    }
}

static int rel_alvo_valido(Arvore *arv, const char *rel, No *src, No **dst_out)
{
    if (dst_out) *dst_out = NULL;
    if (!rel || !*rel) return 0;
    if (!caminho_valido(rel)) return 0;
    No *dst = encontrar_no(&arv->raiz, rel, NULL, NULL);
    if (!dst || dst == &arv->raiz || dst == src) return 0;
    if (dst_out) *dst_out = dst;
    return 1;
}

static int parse_cv_duplo(const char *arg, char ids[2][256])
{
    if (!arg || !*arg) return 0;
    const char *virg = strchr(arg, ',');
    if (!virg || strchr(virg + 1, ',')) return 0;
    const char *ini[2] = { arg, virg + 1 };
    size_t tam[2] = { (size_t)(virg - arg), strlen(virg + 1) };
    for (int i = 0; i < 2; i++) {
        while (tam[i] > 0 && isspace((unsigned char)*ini[i])) { ini[i]++; tam[i]--; }
        while (tam[i] > 0 && isspace((unsigned char)ini[i][tam[i] - 1])) tam[i]--;
        if (tam[i] == 0 || tam[i] >= 256) return 0;
        memcpy(ids[i], ini[i], tam[i]);
        ids[i][tam[i]] = '\0';
        if (!caminho_valido(ids[i])) return 0;
    }
    if (strcmp(ids[0], ids[1]) == 0) return 0;
    return 1;
}

static int cv_duplo_alvo_valido(Arvore *arv, char ids[2][256], No *src, No *dsts[2])
{
    for (int i = 0; i < 2; i++) {
        dsts[i] = NULL;
        if (!rel_alvo_valido(arv, ids[i], src, &dsts[i])) return 0;
    }
    return dsts[0] && dsts[1] && dsts[0] != dsts[1];
}

static int clt_id_mundo_externo(const char *id)
{
    return id && strcmp(id, "0") == 0;
}

static int parse_clt_duplo(const char *arg, char ids[2][256])
{
    if (!arg || !*arg) return 0;
    const char *virg = strchr(arg, ',');
    if (!virg || strchr(virg + 1, ',')) return 0;
    const char *ini[2] = { arg, virg + 1 };
    size_t tam[2] = { (size_t)(virg - arg), strlen(virg + 1) };
    for (int i = 0; i < 2; i++) {
        while (tam[i] > 0 && isspace((unsigned char)*ini[i])) { ini[i]++; tam[i]--; }
        while (tam[i] > 0 && isspace((unsigned char)ini[i][tam[i] - 1])) tam[i]--;
        if (tam[i] == 0 || tam[i] >= 256) return 0;
        memcpy(ids[i], ini[i], tam[i]);
        ids[i][tam[i]] = '\0';
        if (!clt_id_mundo_externo(ids[i]) && !caminho_valido(ids[i])) return 0;
    }
    if (strcmp(ids[0], ids[1]) == 0) return 0;
    return 1;
}

static int clt_duplo_alvo_valido(Arvore *arv, char ids[2][256], No *src, No *dsts[2])
{
    for (int i = 0; i < 2; i++) {
        dsts[i] = NULL;
        if (clt_id_mundo_externo(ids[i])) continue;
        if (!rel_alvo_valido(arv, ids[i], src, &dsts[i])) return 0;
    }
    if (!clt_id_mundo_externo(ids[0]) && !clt_id_mundo_externo(ids[1]) && dsts[0] == dsts[1]) return 0;
    return clt_id_mundo_externo(ids[0]) || clt_id_mundo_externo(ids[1]) || (dsts[0] && dsts[1]);
}

static int clt_adicionar_resolvido(No *raiz, char **lista, const char *id, No *dst)
{
    char *token = NULL;
    if (clt_id_mundo_externo(id)) token = xstrdup("0");
    else {
        token = caminho_de_no(raiz, dst);
        if (!token || !*token) { free(token); return -1; }
    }
    char *nova = config_vin_adicionar(*lista, token);
    free(token);
    if (!nova) return -1;
    free(*lista);
    *lista = nova;
    return 0;
}

static int deletar_bloqueado_por_relacoes(No *raiz, No *sub, const char *id_sub)
{
    int bloqueios = 0;
    for (int i = 0; i < raiz->total; i++) {
        No *src = raiz->filhos[i];
        int pilha[256] = { i };
        No *stack[256];
        int profs[256];
        int tops[256];
        int sp = 0;
        stack[sp] = src; profs[sp] = 1; tops[sp] = 0; sp++;
        while (sp > 0) {
            No *n = stack[sp - 1];
            int prof = profs[sp - 1];
            int prox = tops[sp - 1];
            if (prox == 0 && n->rel && n->rel[0]) {
                No *dst = encontrar_no(raiz, n->rel, NULL, NULL);
                if (dst && no_contem(sub, dst) && !no_contem(sub, n)) {
                    char src_id[1024]; construir_id(src_id, sizeof(src_id), pilha, prof);
                    if (bloqueios == 0) fprintf(stderr, "Não é possível excluir %s: há outros itens relacionados a ele.\n", id_sub);
                    if (bloqueios < 10) fprintf(stderr, "  - %s (%s) -> %s (%s)\n", src_id, n->nome, n->rel, dst->nome);
                    bloqueios++;
                }
            }
            if (prox == 0 && n->config_vin && n->config_vin[0]) {
                const char *p = n->config_vin;
                while (*p) {
                    const char *fim = strchr(p, ',');
                    size_t tam = fim ? (size_t)(fim - p) : strlen(p);
                    while (tam > 0 && isspace((unsigned char)*p)) { p++; tam--; }
                    while (tam > 0 && isspace((unsigned char)p[tam - 1])) tam--;
                    if (tam > 0 && tam < 256) {
                        char vin_id[256]; memcpy(vin_id, p, tam); vin_id[tam] = '\0';
                        No *dst = encontrar_no(raiz, vin_id, NULL, NULL);
                        if (dst && no_contem(sub, dst) && !no_contem(sub, n)) {
                            char src_id[1024]; construir_id(src_id, sizeof(src_id), pilha, prof);
                            if (bloqueios == 0) fprintf(stderr, "Não é possível excluir %s: há outros itens relacionados a ele.\n", id_sub);
                            if (bloqueios < 10) fprintf(stderr, "  - %s (%s) --cv-> %s (%s)\n", src_id, n->nome, vin_id, dst->nome);
                            bloqueios++;
                        }
                    }
                    if (!fim) break;
                    p = fim + 1;
                }
            }
            if (prox == 0 && n->clt && n->clt[0]) {
                const char *p = n->clt;
                while (*p) {
                    const char *fim = strchr(p, ',');
                    size_t tam = fim ? (size_t)(fim - p) : strlen(p);
                    while (tam > 0 && isspace((unsigned char)*p)) { p++; tam--; }
                    while (tam > 0 && isspace((unsigned char)p[tam - 1])) tam--;
                    if (tam > 0 && tam < 256) {
                        char clt_id[256]; memcpy(clt_id, p, tam); clt_id[tam] = '\0';
                        No *dst = encontrar_no(raiz, clt_id, NULL, NULL);
                        if (dst && no_contem(sub, dst) && !no_contem(sub, n)) {
                            char src_id[1024]; construir_id(src_id, sizeof(src_id), pilha, prof);
                            if (bloqueios == 0) fprintf(stderr, "Não é possível excluir %s: há outros itens relacionados a ele.\n", id_sub);
                            if (bloqueios < 10) fprintf(stderr, "  - %s (%s) --clt-> %s (%s)\n", src_id, n->nome, clt_id, dst->nome);
                            bloqueios++;
                        }
                    }
                    if (!fim) break;
                    p = fim + 1;
                }
            }
            if (prox < n->total && sp < 256) {
                tops[sp - 1]++;
                pilha[prof] = prox;
                stack[sp] = n->filhos[prox]; profs[sp] = prof + 1; tops[sp] = 0; sp++;
            } else sp--;
        }
    }
    if (bloqueios > 10) fprintf(stderr, "  ... e mais %d relação(ões).\n", bloqueios - 10);
    return bloqueios > 0;
}

typedef struct {
    No **nos;
    char **ids;
    int total;
    int cap;
} NoRefs;

static void norefs_liberar(NoRefs *r)
{
    if (!r) return;
    for (int i = 0; i < r->total; i++) free(r->ids[i]);
    free(r->ids);
    free(r->nos);
    r->ids = NULL; r->nos = NULL; r->total = r->cap = 0;
}

static int norefs_add(NoRefs *r, No *no, const char *id)
{
    if (r->total >= r->cap) {
        int nova = r->cap ? r->cap * 2 : 32;
        No **nn = realloc(r->nos, (size_t)nova * sizeof(No *));
        if (!nn) return -1;
        char **ni = realloc(r->ids, (size_t)nova * sizeof(char *));
        if (!ni) { r->nos = nn; return -1; }
        r->nos = nn;
        r->ids = ni;
        r->cap = nova;
    }
    r->nos[r->total] = no;
    r->ids[r->total] = xstrdup(id);
    if (!r->ids[r->total]) return -1;
    r->total++;
    return 0;
}

static int norefs_coletar_rec(No *n, NoRefs *r, int *indices, int prof)
{
    for (int i = 0; i < n->total; i++) {
        indices[prof] = i;
        char id[256]; construir_id(id, sizeof(id), indices, prof + 1);
        if (norefs_add(r, n->filhos[i], id) != 0) return -1;
        if (norefs_coletar_rec(n->filhos[i], r, indices, prof + 1) != 0) return -1;
    }
    return 0;
}

static int norefs_coletar(No *raiz, NoRefs *r)
{
    int indices[256];
    memset(r, 0, sizeof(*r));
    return norefs_coletar_rec(raiz, r, indices, 0);
}

static int parse_replace_arg(const char *arg, char **de_txt, char **para_txt, int *adicionar)
{
    if (de_txt) *de_txt = NULL;
    if (para_txt) *para_txt = NULL;
    if (adicionar) *adicionar = 0;
    if (!arg) return -1;

    /* Modo adicionar: --replace=[+]texto  (concatena texto ao nome atual) */
    if (strncmp(arg, "[+]", 3) == 0) {
        char *d = xstrdup("+");
        char *p = xstrdup(arg + 3);
        if (!d || !p) { free(d); free(p); return -1; }
        if (de_txt) *de_txt = d; else free(d);
        if (para_txt) *para_txt = p; else free(p);
        if (adicionar) *adicionar = 1;
        return 0;
    }

    /* Modo substituir: --replace=[texto_atual][texto_novo] */
    const char *a = strchr(arg, '[');
    if (!a) return -1;
    const char *b = strchr(a + 1, ']');
    if (!b || b[1] != '[') return -1;
    const char *c = strchr(b + 2, ']');
    if (!c || c[1] != '\0') return -1;
    size_t n_de = (size_t)(b - (a + 1));
    size_t n_para = (size_t)(c - (b + 2));
    if (n_de == 0) return -1;
    char *d = malloc(n_de + 1);
    char *p = malloc(n_para + 1);
    if (!d || !p) { free(d); free(p); return -1; }
    memcpy(d, a + 1, n_de); d[n_de] = '\0';
    memcpy(p, b + 2, n_para); p[n_para] = '\0';
    if (de_txt) *de_txt = d; else free(d);
    if (para_txt) *para_txt = p; else free(p);
    return 0;
}

static char *substituir_todos(const char *orig, const char *de_txt, const char *para_txt, int *qtd)
{
    if (qtd) *qtd = 0;
    if (!orig || !de_txt || !*de_txt || !para_txt) return xstrdup(orig ? orig : "");
    size_t n_de = strlen(de_txt), n_para = strlen(para_txt);
    int total = 0;
    const char *p = orig;
    while ((p = strstr(p, de_txt)) != NULL) { total++; p += n_de; }
    if (qtd) *qtd = total;
    if (total == 0) return xstrdup(orig);
    size_t n_orig = strlen(orig);
    size_t n_novo = n_orig;
    if (n_para >= n_de) n_novo += (size_t)total * (n_para - n_de);
    else n_novo -= (size_t)total * (n_de - n_para);
    n_novo++;
    char *out = malloc(n_novo);
    if (!out) return NULL;
    char *w = out;
    p = orig;
    const char *m;
    while ((m = strstr(p, de_txt)) != NULL) {
        size_t trecho = (size_t)(m - p);
        memcpy(w, p, trecho); w += trecho;
        memcpy(w, para_txt, n_para); w += n_para;
        p = m + n_de;
    }
    strcpy(w, p);
    return out;
}

static char *adicionar_conteudo(const char *orig, const char *conteudo)
{
    const char *o = orig ? orig : "";
    const char *c = conteudo ? conteudo : "";
    size_t no = strlen(o), nc = strlen(c);
    char *out = malloc(no + nc + 1);
    if (!out) return NULL;
    memcpy(out, o, no);
    memcpy(out + no, c, nc + 1);
    return out;
}

static int aplicar_replace_no(No *no, const char *de_txt, const char *para_txt, int adicionar, int *ocorrencias)
{
    if (ocorrencias) *ocorrencias = 0;
    if (!no) return 0;
    if (adicionar) {
        char *novo = adicionar_conteudo(no->nome, para_txt);
        if (!novo) return -1;
        free(no->nome);
        no->nome = novo;
        if (ocorrencias) *ocorrencias = 1;
        return 1;
    }

    int q = 0;
    char *novo = substituir_todos(no->nome, de_txt, para_txt, &q);
    if (!novo) return -1;
    if (q > 0) {
        free(no->nome);
        no->nome = novo;
        if (ocorrencias) *ocorrencias = q;
        return 1;
    }
    free(novo);
    return 0;
}

static int substituir_intervalo(No *raiz, const char *inicio, const char *fim, const char *de_txt, const char *para_txt, int adicionar, int *itens_alt, int *ocorrencias)
{
    if (itens_alt) *itens_alt = 0;
    if (ocorrencias) *ocorrencias = 0;
    if ((inicio && !caminho_valido(inicio)) || (fim && !caminho_valido(fim))) return -1;

    NoRefs refs;
    if (norefs_coletar(raiz, &refs) != 0) { norefs_liberar(&refs); return -1; }
    if (refs.total == 0) { norefs_liberar(&refs); return 0; }

    int ini = inicio ? -1 : 0;
    int fi = fim ? -1 : refs.total - 1;
    for (int i = 0; i < refs.total; i++) {
        if (inicio && strcmp(refs.ids[i], inicio) == 0) ini = i;
        if (fim && strcmp(refs.ids[i], fim) == 0) fi = i;
    }
    if (ini < 0 || fi < 0) { norefs_liberar(&refs); return -2; }
    if (ini > fi) { norefs_liberar(&refs); return -3; }

    int itens = 0, ocorr = 0;
    for (int i = ini; i <= fi; i++) {
        int q = 0;
        int r = aplicar_replace_no(refs.nos[i], de_txt, para_txt, adicionar, &q);
        if (r < 0) { norefs_liberar(&refs); return -1; }
        if (r > 0) { itens++; ocorr += q; }
    }

    norefs_liberar(&refs);
    if (itens_alt) *itens_alt = itens;
    if (ocorrencias) *ocorrencias = ocorr;
    return 0;
}

static int substituir_item(No *raiz, const char *id, const char *de_txt, const char *para_txt, int adicionar, int *itens_alt, int *ocorrencias)
{
    if (itens_alt) *itens_alt = 0;
    if (ocorrencias) *ocorrencias = 0;
    if (!caminho_valido(id)) return -1;
    No *no = encontrar_no(raiz, id, NULL, NULL);
    if (!no || no == raiz) return -2;
    int q = 0;
    int r = aplicar_replace_no(no, de_txt, para_txt, adicionar, &q);
    if (r < 0) return -1;
    if (itens_alt) *itens_alt = r > 0 ? 1 : 0;
    if (ocorrencias) *ocorrencias = q;
    return 0;
}

static int definir_recolhido_rec(No *n, int valor, int *alterados, int *total)
{
    if (!n) return 0;
    for (int i = 0; i < n->total; i++) {
        No *f = n->filhos[i];
        if (total) (*total)++;
        if (f->recolhido != valor) {
            f->recolhido = valor;
            if (alterados) (*alterados)++;
        }
        definir_recolhido_rec(f, valor, alterados, total);
    }
    return 0;
}

static int definir_recolhido_alvo(No *raiz, const char *alvo, int valor, int *alterados, int *total)
{
    if (alterados) *alterados = 0;
    if (total) *total = 0;
    if (!alvo || !*alvo) return -1;

    if (strcasecmp(alvo, "total") == 0) {
        definir_recolhido_rec(raiz, valor, alterados, total);
        return 0;
    }

    if (!caminho_valido(alvo)) return -1;
    No *no = encontrar_no(raiz, alvo, NULL, NULL);
    if (!no || no == raiz) return -2;
    if (total) *total = 1;
    if (no->recolhido != valor) {
        no->recolhido = valor;
        if (alterados) *alterados = 1;
    }
    return 0;
}

typedef struct {
    long long td;
    long long tr;
    long long tt;
    int tem;
    int tem_valor;
    int tem_tt;
} TotaisValor;

static int valor_para_centavos(const char *txt, long long *out)
{
    const char *p = txt ? txt : "";
    while (isspace((unsigned char)*p)) p++;
    int sinal = 1;
    if (*p == '-') { sinal = -1; p++; }
    else if (*p == '+') p++;
    while (isspace((unsigned char)*p)) p++;

    const char *virg = strrchr(p, ',');
    const char *ponto = strrchr(p, '.');
    const char *dec = NULL;
    if (virg) dec = virg;
    else if (ponto) {
        int apos = 0;
        for (const char *q = ponto + 1; *q; q++) if (isdigit((unsigned char)*q)) apos++;
        if (apos > 0 && apos <= 2) dec = ponto;
    }

    long long reais = 0, cent = 0;
    int digitos = 0, cd = 0;
    for (const char *q = p; *q; q++) {
        if (dec && q > dec) break;
        if (isdigit((unsigned char)*q)) { reais = reais * 10 + (*q - '0'); digitos++; }
    }
    if (dec) {
        for (const char *q = dec + 1; *q && cd < 2; q++) {
            if (isdigit((unsigned char)*q)) { cent = cent * 10 + (*q - '0'); cd++; digitos++; }
        }
        while (cd > 0 && cd < 2) { cent *= 10; cd++; }
    }
    if (!digitos) return 0;
    if (out) *out = (long long)sinal * (reais * 100 + cent);
    return 1;
}

static void acumular_valor(const char *valor, TotaisValor *tot)
{
    long long c = 0;
    if (!valor_para_centavos(valor, &c)) return;
    tot->tem = 1;
    tot->tem_valor = 1;
    if (c < 0) tot->td += -c;
    else if (c > 0) tot->tr += c;
}

static int clt_sentido_para_alvo(const char *lista, const char *id)
{
    if (!lista || !*lista || !id || !*id) return 0;
    const char *virg = strchr(lista, ',');
    if (!virg || strchr(virg + 1, ',')) return 0;
    const char *ini[2] = { lista, virg + 1 };
    size_t tam[2] = { (size_t)(virg - lista), strlen(virg + 1) };
    for (int i = 0; i < 2; i++) {
        while (tam[i] > 0 && isspace((unsigned char)*ini[i])) { ini[i]++; tam[i]--; }
        while (tam[i] > 0 && isspace((unsigned char)ini[i][tam[i] - 1])) tam[i]--;
        if (strlen(id) == tam[i] && strncmp(ini[i], id, tam[i]) == 0) return i == 0 ? -1 : 1;
    }
    return 0;
}

static void acumular_transferencia_alvo(No *n, const char *alvo_id, TotaisValor *tot)
{
    int sentido = clt_sentido_para_alvo(n ? n->clt : NULL, alvo_id);
    if (!sentido) return;
    long long c = 0;
    if (!valor_para_centavos(n->vlt, &c)) return;
    if (c < 0) c = -c;
    if (c == 0) return;
    tot->tt += sentido < 0 ? -c : c;
    tot->tem = 1;
    tot->tem_tt = 1;
}

static void calcular_totais_transferencias_rec(No *n, const char *alvo_id, TotaisValor *tot)
{
    if (!n || !alvo_id || !*alvo_id) return;
    for (int i = 0; i < n->total; i++) {
        acumular_transferencia_alvo(n->filhos[i], alvo_id, tot);
        calcular_totais_transferencias_rec(n->filhos[i], alvo_id, tot);
    }
}

static void calcular_totais_vinculados_rec(No *raiz, const char *alvo_id, No *ignorar_subarvore, const char **visitados, int total_visitados, TotaisValor *tot);
static TotaisValor calcular_totais_vinculados(No *raiz, const char *alvo_id);

static void acumular_valores_subarvore(No *raiz, No *n, const char *id, No *ignorar_subarvore, const char **visitados, int total_visitados, TotaisValor *tot)
{
    if (!n || !id || !*id) return;
    acumular_valor(n->valor, tot);
    calcular_totais_vinculados_rec(raiz, id, ignorar_subarvore, visitados, total_visitados, tot);
    for (int i = 0; i < n->total; i++) {
        char filho_id[256];
        snprintf(filho_id, sizeof(filho_id), "%s.%d", id, i + 1);
        acumular_valores_subarvore(raiz, n->filhos[i], filho_id, ignorar_subarvore, visitados, total_visitados, tot);
    }
}

static void acumular_total_filho_direto(No *raiz, No *filho, const char *filho_id, TotaisValor *tot)
{
    if (!filho) return;
    acumular_valor(filho->valor, tot);
    /* fd/fr deve refletir somente os filhos diretos. Para não trazer
       totais agregados de netos/bisnetos, só considera td/tr do filho
       direto quando ele é folha; filhos com subitens terão seu próprio
       fd/fr calculado na própria linha. */
    if (filho->total == 0) {
        TotaisValor vinculados = calcular_totais_vinculados(raiz, filho_id);
        tot->td += vinculados.td;
        tot->tr += vinculados.tr;
        tot->tt += vinculados.tt;
        if (vinculados.tem) tot->tem = 1;
        if (vinculados.tem_valor) tot->tem_valor = 1;
        if (vinculados.tem_tt) tot->tem_tt = 1;
    }
}

static TotaisValor calcular_totais_filhos(No *raiz, No *n, const char *id)
{
    TotaisValor tot = {0, 0, 0, 0, 0, 0};
    if (!n || !id || !*id) return tot;
    for (int i = 0; i < n->total; i++) {
        char filho_id[1024];
        snprintf(filho_id, sizeof(filho_id), "%s.%d", id, i + 1);
        acumular_total_filho_direto(raiz, n->filhos[i], filho_id, &tot);
    }
    return tot;
}

static void formatar_centavos(long long centavos, char *out, size_t outsz)
{
    if (centavos < 0) centavos = -centavos;
    long long reais = centavos / 100;
    int cents = (int)(centavos % 100);
    char dig[64];
    snprintf(dig, sizeof(dig), "%lld", reais);
    size_t len = strlen(dig), j = 0;
    for (size_t i = 0; i < len && j + 1 < outsz; i++) {
        if (i > 0 && ((len - i) % 3) == 0 && j + 1 < outsz) out[j++] = '.';
        out[j++] = dig[i];
    }
    if (j + 4 < outsz) snprintf(out + j, outsz - j, ",%02d", cents);
    else if (outsz) out[outsz - 1] = '\0';
}

static void formatar_centavos_sinal(long long centavos, char sinal, char *out, size_t outsz)
{
    char base[64];
    formatar_centavos(centavos, base, sizeof(base));
    if (centavos == 0 || sinal == '\0') snprintf(out, outsz, "%s", base);
    else if (outsz > 1) {
        size_t n = strlen(base);
        if (n > outsz - 2) n = outsz - 2;
        out[0] = sinal;
        memcpy(out + 1, base, n);
        out[n + 1] = '\0';
    }
    else if (outsz == 1) out[0] = '\0';
}

static void formatar_centavos_saldo(long long centavos, char *out, size_t outsz)
{
    if (!out || outsz == 0) return;
    char base[64];
    formatar_centavos(centavos, base, sizeof(base));
    if (centavos < 0) {
        out[0] = '-';
        snprintf(out + 1, outsz > 1 ? outsz - 1 : 0, "%s", base);
    } else {
        snprintf(out, outsz, "%s", base);
    }
}

static long long saldo_totais(const TotaisValor *tot)
{
    if (!tot) return 0;
    return tot->tr - tot->td + tot->tt;
}

static int id_ja_visitado(const char *id, const char **visitados, int total)
{
    for (int i = 0; i < total; i++) if (strcmp(id, visitados[i]) == 0) return 1;
    return 0;
}

static void coletar_vinculados_rec(No *raiz, No *n, int *indices, int prof, const char *alvo_id, No *ignorar_subarvore, const char **visitados, int total_visitados, TotaisValor *tot)
{
    for (int i = 0; i < n->total; i++) {
        indices[prof] = i;
        No *f = n->filhos[i];
        if (ignorar_subarvore && no_contem(ignorar_subarvore, f)) continue;
        char id[256]; construir_id(id, sizeof(id), indices, prof + 1);
        if (config_vin_contem(f->config_vin, alvo_id)) {
            tot->tem = 1;
            tot->tem_valor = 1;
            acumular_valores_subarvore(raiz, f, id, f, visitados, total_visitados, tot);
        } else {
            coletar_vinculados_rec(raiz, f, indices, prof + 1, alvo_id, ignorar_subarvore, visitados, total_visitados, tot);
        }
    }
}

static void calcular_totais_vinculados_rec(No *raiz, const char *alvo_id, No *ignorar_subarvore, const char **visitados, int total_visitados, TotaisValor *tot)
{
    if (!alvo_id || !*alvo_id || total_visitados >= 120 || id_ja_visitado(alvo_id, visitados, total_visitados)) return;
    visitados[total_visitados++] = alvo_id;
    int indices[128];
    coletar_vinculados_rec(raiz, raiz, indices, 0, alvo_id, ignorar_subarvore, visitados, total_visitados, tot);
}

static TotaisValor calcular_totais_vinculados(No *raiz, const char *alvo_id)
{
    TotaisValor tot = {0, 0, 0, 0, 0, 0};
    const char *visitados[128];
    calcular_totais_vinculados_rec(raiz, alvo_id, NULL, visitados, 0, &tot);
    calcular_totais_transferencias_rec(raiz, alvo_id, &tot);
    return tot;
}

static void exibir_filhos(No *raiz, No *n, const char *prefixo, int *indices, int prof)
{
    for (int i = 0; i < n->total; i++) {
        int ultimo = (i == n->total - 1);
        indices[prof] = i;
        char id[256]; construir_id(id, sizeof(id), indices, prof + 1);
        const char *ansi;
        if (n->filhos[i]->status)
            ansi = status_para_ansi(n->filhos[i]->status);
        else if (n->filhos[i]->cor)
            ansi = cor_para_ansi(n->filhos[i]->cor);
        else
            ansi = "37"; /* sem status: branco normal */
        int item_destacado = id_em_lista_ci(g_item_destaque_ids, id);
        /* --proc: destaca nomes que batem com o termo em rosa choque; criação/edição desta execução prevalece em branco-brilhante */
        int nome_bate = (g_proc_termo && g_proc_termo[0]) ? no_ou_filhos_contem_termo(n->filhos[i], g_proc_termo) : 0;
        const char *nome_ansi = item_destacado ? SA_ITEM_DESTAQUE_ANSI : (nome_bate ? "1;38;5;201" : ansi);
        const char *id_ansi = item_destacado ? "97" : (n->filhos[i]->total > 0 ? "36" : "33");
        const char *linha_meta_ansi = item_destacado ? SA_ITEM_DESTAQUE_ANSI : NULL;
        printf("%s%s\033[1;%sm%s\033[0m. \033[%sm%s\033[0m", prefixo, ultimo ? "└── " : "├── ", id_ansi, id, nome_ansi, n->filhos[i]->nome);
        if (n->filhos[i]->total > 0) {
            TotaisValor filhos = calcular_totais_filhos(raiz, n->filhos[i], id);
            if (filhos.tem_valor || filhos.tem_tt || filhos.td != 0 || filhos.tr != 0 || filhos.tt != 0) {
                char fd[80], fr[80], ft[80], sd[80];
                formatar_centavos_sinal(filhos.td, '-', fd, sizeof(fd));
                formatar_centavos_sinal(filhos.tr, '+', fr, sizeof(fr));
                formatar_centavos_saldo(filhos.tt, ft, sizeof(ft));
                formatar_centavos_saldo(saldo_totais(&filhos), sd, sizeof(sd));
                printf(" \033[%sm", linha_meta_ansi ? linha_meta_ansi : "0;33");
                if (filhos.tem_valor || filhos.td != 0 || filhos.tr != 0) printf("[fd:%s][fr:%s]", fd, fr);
                if (filhos.tem_tt || filhos.tt != 0) printf("[ft:%s]", ft);
                printf("[sd:%s]\033[0m", sd);
            }
        }
        TotaisValor totais = calcular_totais_vinculados(raiz, id);
        if (totais.tem) {
            char td[80], tr[80], tt[80], sd[80];
            formatar_centavos_sinal(totais.td, '-', td, sizeof(td));
            formatar_centavos_sinal(totais.tr, '+', tr, sizeof(tr));
            formatar_centavos_saldo(totais.tt, tt, sizeof(tt));
            formatar_centavos_saldo(saldo_totais(&totais), sd, sizeof(sd));
            printf(" \033[%sm", linha_meta_ansi ? linha_meta_ansi : "0;35");
            if (totais.tem_valor || totais.td != 0 || totais.tr != 0) printf("[td:%s][tr:%s]", td, tr);
            if (totais.tem_tt || totais.tt != 0) printf("[tt:%s]", tt);
            printf("[sd:%s]\033[0m", sd);
        }
        if (n->filhos[i]->rel && n->filhos[i]->rel[0]) printf(" \033[%sm[rel: %s]\033[0m", linha_meta_ansi ? linha_meta_ansi : "0;36", n->filhos[i]->rel);
        if (n->filhos[i]->config_vin && n->filhos[i]->config_vin[0]) printf(" \033[%sm[vin: %s]\033[0m", linha_meta_ansi ? linha_meta_ansi : "0;36", n->filhos[i]->config_vin);
        if (n->filhos[i]->valor && n->filhos[i]->valor[0]) printf(" \033[%sm[valor: %s]\033[0m", linha_meta_ansi ? linha_meta_ansi : "0;35", n->filhos[i]->valor);
        if (n->filhos[i]->vlt && n->filhos[i]->vlt[0]) printf(" \033[%sm[vlt: %s]\033[0m", linha_meta_ansi ? linha_meta_ansi : "0;35", n->filhos[i]->vlt);
        if (n->filhos[i]->clt && n->filhos[i]->clt[0]) printf(" \033[%sm[clt: %s]\033[0m", linha_meta_ansi ? linha_meta_ansi : "0;36", n->filhos[i]->clt);
        if (n->filhos[i]->ra && n->filhos[i]->ra[0]) printf(" \033[%sm[%s]\033[0m", linha_meta_ansi ? linha_meta_ansi : SA_RA_ANSI, n->filhos[i]->ra);
        if (n->filhos[i]->recolhido && n->filhos[i]->total > 0) printf(" \033[%sm[recolhido: %d]\033[0m", linha_meta_ansi ? linha_meta_ansi : "0;90", n->filhos[i]->total);
        printf("\n");
        if (n->filhos[i]->mm && n->filhos[i]->mm[0]) {
            const char *mm_ansi;
            if (n->filhos[i]->status)
                mm_ansi = status_para_ansi(n->filhos[i]->status);
            else if (n->filhos[i]->cor)
                mm_ansi = cor_para_ansi(n->filhos[i]->cor);
            else
                mm_ansi = "37"; /* sem status: branco normal */
            char *mm_dup = strdup(n->filhos[i]->mm);
            if (mm_dup) {
                char *line, *save;
                line = strtok_r(mm_dup, "\n", &save);
                while (line) {
                    if (n->filhos[i]->gc && n->filhos[i]->gc[0] && strcmp(line, "---") != 0)
                        printf("%s%s  \033[1;97m%s:\033[%sm%s\033[0m\n", prefixo, ultimo ? "    " : "│   ", n->filhos[i]->gc, mm_ansi, line);
                    else
                        printf("%s%s  \033[%sm%s\033[0m\n", prefixo, ultimo ? "    " : "│   ", mm_ansi, line);
                    line = strtok_r(NULL, "\n", &save);
                }
                free(mm_dup);
            }
        }
        if (!n->filhos[i]->recolhido) {
            char novo[1024];
            snprintf(novo, sizeof(novo), "%s%s", prefixo, ultimo ? "    " : "│   ");
            exibir_filhos(raiz, n->filhos[i], novo, indices, prof + 1);
        }
    }
}

static void exibir_arvore(Arvore *a)
{
    printf("\033[3J\033[H\033[2J\n");
    if (g_proc_termo && g_proc_termo[0])
        printf("\033[1;38;5;201m[procura: \"%s\"]\033[0m\n", g_proc_termo);
    printf("\033[37m%s\033[0m\n", a->nome);
    int indices[128];
    exibir_filhos(&a->raiz, &a->raiz, "", indices, 0);
    printf("\n");
}

static void prompt_add_texto(Str *s, const char *titulo, const char *txt)
{
    if (!s || !txt || !*txt) return;
    if (titulo && *titulo) str_addf(s, "%s\n", titulo);
    char *dup = strdup(txt);
    if (!dup) return;
    char *line, *save;
    line = strtok_r(dup, "\n", &save);
    while (line) {
        str_addf(s, "  %s\n", line);
        line = strtok_r(NULL, "\n", &save);
    }
    free(dup);
}

#define SA_PROMPT_ARVORE_JSON_MAX_BYTES (768 * 1024)
#define SA_PROMPT_ARVORE_OUTLINE_MAX_NOS 6000
#define SA_PROMPT_SUBARVORE_MAX_PROF 4
#define SA_CHECKPOINT_GITHUB_CONTA "jrcostarrear"
static void texto_trim_inplace(char *s)
{
    if (!s) return;
    char *ini = s;
    while (*ini && isspace((unsigned char)*ini)) ini++;
    if (ini != s) memmove(s, ini, strlen(ini) + 1);
    size_t n = strlen(s);
    while (n > 0 && isspace((unsigned char)s[n - 1])) s[--n] = '\0';
}

static void prompt_add_no_tags(Str *s, No *n)
{
    if (!s || !n) return;
    if (n->gc && n->gc[0]) str_addf(s, " [gc=%s]", n->gc);
    if (n->rel && n->rel[0]) str_addf(s, " [rel=%s]", n->rel);
    if (n->config_vin && n->config_vin[0]) str_addf(s, " [vin=%s]", n->config_vin);
    if (n->valor && n->valor[0]) str_addf(s, " [valor=%s]", n->valor);
    if (n->vlt && n->vlt[0]) str_addf(s, " [vlt=%s]", n->vlt);
    if (n->clt && n->clt[0]) str_addf(s, " [clt=%s]", n->clt);
    if (n->ra && n->ra[0]) str_addf(s, " [ra=%s]", n->ra);
    if (n->itens && n->itens[0]) str_addf(s, " [itens=%s]", n->itens);
    if (n->mm && n->mm[0]) str_add(s, " [mm]");
    if (n->recolhido) str_add(s, " [recolhido]");
}

static void prompt_add_resolucao_id(Str *s, No *raiz, const char *id, const char *rotulo)
{
    if (!s || !id || !*id) return;
    char tmp[256];
    snprintf(tmp, sizeof(tmp), "%s", id);
    texto_trim_inplace(tmp);
    if (!tmp[0]) return;
    if (strcmp(tmp, "0") == 0) {
        str_addf(s, "  - %s%s: mundo externo\n", rotulo ? rotulo : "", tmp);
        return;
    }
    No *ref = encontrar_no(raiz, tmp, NULL, NULL);
    if (ref && ref != raiz) {
        str_addf(s, "  - %s%s: %s", rotulo ? rotulo : "", tmp, ref->nome ? ref->nome : "");
        prompt_add_no_tags(s, ref);
        str_add(s, "\n");
    } else {
        str_addf(s, "  - %s%s: [item não encontrado]\n", rotulo ? rotulo : "", tmp);
    }
}

static void prompt_add_lista_ids_resolvida(Str *s, No *raiz, const char *titulo, const char *ids)
{
    if (!s || !ids || !*ids) return;
    if (titulo && *titulo) str_addf(s, "%s\n", titulo);
    char *dup = strdup(ids);
    if (!dup) return;
    char *tok, *save;
    tok = strtok_r(dup, ",", &save);
    while (tok) {
        prompt_add_resolucao_id(s, raiz, tok, "");
        tok = strtok_r(NULL, ",", &save);
    }
    free(dup);
}

static void prompt_add_ra_lista(Str *s, const char *ra)
{
    if (!s || !ra || !*ra) return;
    str_add(s, "Referências --ra declaradas no item:\n");
    char *dup = strdup(ra);
    if (!dup) return;
    char *tok, *save;
    tok = strtok_r(dup, ",", &save);
    while (tok) {
        texto_trim_inplace(tok);
        if (*tok) str_addf(s, "  - %s\n", tok);
        tok = strtok_r(NULL, ",", &save);
    }
    free(dup);
}

static void prompt_add_no_detalhado(Str *s, No *raiz, No *n, const char *id)
{
    if (!s || !n || !id) return;
    str_addf(s, "ID: %s\n", id);
    str_addf(s, "Nome: %s\n", n->nome ? n->nome : "");
    str_addf(s, "Filhos diretos: %d\n", n->total);
    if (n->gc && n->gc[0]) str_addf(s, "GC: %s\n", n->gc);
    if (n->cor && n->cor[0]) str_addf(s, "Cor do GC/item: %s\n", n->cor);
    if (n->valor && n->valor[0]) str_addf(s, "Valor direto: %s\n", n->valor);
    if (n->vlt && n->vlt[0]) str_addf(s, "Transferência --vlt: %s\n", n->vlt);
    if (n->ra && n->ra[0]) prompt_add_ra_lista(s, n->ra);
    if (n->mm && n->mm[0]) prompt_add_texto(s, "Mensagens --mm do usuário neste item:", n->mm);
    if (n->rel && n->rel[0]) {
        str_addf(s, "Relação --rel: %s\n", n->rel);
        prompt_add_resolucao_id(s, raiz, n->rel, "alvo ");
    }
    if (n->config_vin && n->config_vin[0]) {
        str_addf(s, "Vínculos de consolidação --vin/configVin: %s\n", n->config_vin);
        prompt_add_lista_ids_resolvida(s, raiz, "Vínculos resolvidos:", n->config_vin);
    }
    if (n->clt && n->clt[0]) {
        str_addf(s, "Contas de transferência --clt: %s\n", n->clt);
        prompt_add_lista_ids_resolvida(s, raiz, "Contas resolvidas:", n->clt);
    }
    if (n->itens && n->itens[0]) {
        str_addf(s, "Itens relacionados declarados em --itens: %s\n", n->itens);
        prompt_add_lista_ids_resolvida(s, raiz, "Itens relacionados resolvidos:", n->itens);
    }
    if (n->total > 0) {
        int limite = n->total < 25 ? n->total : 25;
        str_add(s, "Filhos diretos do item:\n");
        for (int i = 0; i < limite; i++) {
            char filho_id[320];
            snprintf(filho_id, sizeof(filho_id), "%s.%d", id, i + 1);
            str_addf(s, "  - %s: %s", filho_id, n->filhos[i]->nome ? n->filhos[i]->nome : "");
            prompt_add_no_tags(s, n->filhos[i]);
            str_add(s, "\n");
        }
        if (n->total > limite) str_addf(s, "  - ... %d filho(s) adicional(is) omitido(s) nesta lista direta\n", n->total - limite);
    }
}

static void prompt_add_caminho_id(Str *s, No *raiz, const char *id)
{
    if (!s || !raiz || !id || !*id) return;
    str_addf(s, "Caminho do tronco até %s:\n", id);
    str_addf(s, "  - tronco: %s\n", raiz->nome ? raiz->nome : "");
    char *dup = strdup(id);
    if (!dup) return;
    char atual[256] = "";
    char *tok, *save;
    tok = strtok_r(dup, ".", &save);
    while (tok) {
        if (atual[0]) strncat(atual, ".", sizeof(atual) - strlen(atual) - 1);
        strncat(atual, tok, sizeof(atual) - strlen(atual) - 1);
        No *no = encontrar_no(raiz, atual, NULL, NULL);
        if (no && no != raiz) {
            str_addf(s, "  - %s: %s", atual, no->nome ? no->nome : "");
            prompt_add_no_tags(s, no);
            str_add(s, "\n");
        } else {
            str_addf(s, "  - %s: [item não encontrado]\n", atual);
        }
        tok = strtok_r(NULL, ".", &save);
    }
    free(dup);
}

static void prompt_add_subarvore_rec(Str *s, No *n, const char *id, int prof, int max_prof, int *linhas, int max_linhas)
{
    if (!s || !n || !id || !linhas || *linhas >= max_linhas) return;
    for (int k = 0; k < prof; k++) str_add(s, "  ");
    str_addf(s, "- %s: %s", id, n->nome ? n->nome : "");
    prompt_add_no_tags(s, n);
    str_add(s, "\n");
    (*linhas)++;
    if (prof >= max_prof) {
        if (n->total > 0) {
            for (int k = 0; k <= prof; k++) str_add(s, "  ");
            str_addf(s, "- ... %d descendente(s) abaixo deste ponto omitido(s) pelo limite de profundidade\n", n->total);
        }
        return;
    }
    for (int i = 0; i < n->total && *linhas < max_linhas; i++) {
        char filho_id[320];
        snprintf(filho_id, sizeof(filho_id), "%s.%d", id, i + 1);
        prompt_add_subarvore_rec(s, n->filhos[i], filho_id, prof + 1, max_prof, linhas, max_linhas);
    }
}

static void prompt_add_refs_reversas_rec(No *raiz, No *n, const char *alvo_id, int *indices, int prof, Str *s, int *total)
{
    if (!raiz || !n || !alvo_id || !indices || !s || !total) return;
    for (int i = 0; i < n->total; i++) {
        indices[prof] = i;
        No *f = n->filhos[i];
        char id[256]; construir_id(id, sizeof(id), indices, prof + 1);
        if (strcmp(id, alvo_id) != 0) {
            if (f->rel && strcmp(f->rel, alvo_id) == 0) {
                str_addf(s, "  - %s: %s aponta para este item via --rel\n", id, f->nome ? f->nome : "");
                (*total)++;
            }
            if (f->config_vin && config_vin_contem(f->config_vin, alvo_id)) {
                str_addf(s, "  - %s: %s referencia este item via --vin/configVin\n", id, f->nome ? f->nome : "");
                (*total)++;
            }
            if (f->clt && config_vin_contem(f->clt, alvo_id)) {
                str_addf(s, "  - %s: %s referencia este item via --clt\n", id, f->nome ? f->nome : "");
                (*total)++;
            }
            if (f->itens && config_vin_contem(f->itens, alvo_id)) {
                str_addf(s, "  - %s: %s cita este item em --itens\n", id, f->nome ? f->nome : "");
                (*total)++;
            }
        }
        prompt_add_refs_reversas_rec(raiz, f, alvo_id, indices, prof + 1, s, total);
    }
}

static void prompt_coletar_dossie_gc_rec(No *raiz, No *n, const char *gc_nome, int *indices, int prof, Str *s, int *total)
{
    for (int i = 0; i < n->total; i++) {
        indices[prof] = i;
        No *f = n->filhos[i];
        char id[256]; construir_id(id, sizeof(id), indices, prof + 1);
        if (f->gc && f->gc[0] && strcmp(f->gc, gc_nome) == 0) {
            (*total)++;
            str_addf(s, "## Item-alvo %d do GC '%s'\n", *total, gc_nome);
            prompt_add_caminho_id(s, raiz, id);
            str_add(s, "\nDados completos do item-alvo:\n");
            prompt_add_no_detalhado(s, raiz, f, id);
            str_add(s, "\nSubárvore local do item-alvo (para preservar escopo e dependências internas):\n");
            int linhas_sub = 0;
            prompt_add_subarvore_rec(s, f, id, 0, SA_PROMPT_SUBARVORE_MAX_PROF, &linhas_sub, 350);
            str_add(s, "\nReferências reversas encontradas na árvore para este item:\n");
            int refs = 0;
            int idx_refs[128];
            prompt_add_refs_reversas_rec(raiz, raiz, id, idx_refs, 0, s, &refs);
            if (refs == 0) str_add(s, "  - nenhuma referência reversa explícita encontrada\n");
            str_add(s, "\n");
        }
        prompt_coletar_dossie_gc_rec(raiz, f, gc_nome, indices, prof + 1, s, total);
    }
}

static void prompt_add_arvore_compacta_rec(No *n, int *indices, int prof, Str *s, int *total)
{
    if (!n || !indices || !s || !total || *total >= SA_PROMPT_ARVORE_OUTLINE_MAX_NOS) return;
    for (int i = 0; i < n->total && *total < SA_PROMPT_ARVORE_OUTLINE_MAX_NOS; i++) {
        indices[prof] = i;
        No *f = n->filhos[i];
        char id[256]; construir_id(id, sizeof(id), indices, prof + 1);
        for (int k = 0; k < prof; k++) str_add(s, "  ");
        str_addf(s, "- %s: %s", id, f->nome ? f->nome : "");
        prompt_add_no_tags(s, f);
        str_add(s, "\n");
        (*total)++;
        prompt_add_arvore_compacta_rec(f, indices, prof + 1, s, total);
    }
}

static char *prompt_montar_dossie_gc(Arvore *arv, const char *gc_nome, int *total_out)
{
    if (total_out) *total_out = 0;
    if (!arv || !gc_nome || !*gc_nome) return NULL;

    Str d;
    str_init(&d);
    if (!d.buf) return NULL;

    str_addf(&d,
        "# Dossiê do GC '%s' na árvore '%s'\n\n"
        "Este dossiê foi montado determinísticamente pelo comando --sa antes de chamar o --parallm.\n"
        "Ele é usado diretamente para criar o prompt final qualificado, sem LLM montadora intermediária.\n\n",
        gc_nome, arv->nome ? arv->nome : "");

    int total = 0;
    int indices[128];
    prompt_coletar_dossie_gc_rec(&arv->raiz, &arv->raiz, gc_nome, indices, 0, &d, &total);
    if (total == 0) {
        free(d.buf);
        return NULL;
    }
    if (total_out) *total_out = total;

    str_add(&d, "# Visão compacta da árvore inteira\n");
    str_add(&d, "Use esta visão para entender irmãos, ancestrais, dependências laterais e localização sem precisar reabrir a árvore.\n");
    int total_outline = 0;
    int idx_outline[128];
    prompt_add_arvore_compacta_rec(&arv->raiz, idx_outline, 0, &d, &total_outline);
    if (total_outline >= SA_PROMPT_ARVORE_OUTLINE_MAX_NOS)
        str_addf(&d, "- ... árvore truncada após %d itens no resumo compacto\n", SA_PROMPT_ARVORE_OUTLINE_MAX_NOS);
    str_add(&d, "\n");

    char *json = arvore_json(arv);
    if (json) {
        size_t tam = strlen(json);
        if (tam <= SA_PROMPT_ARVORE_JSON_MAX_BYTES) {
            str_addf(&d,
                "# JSON completo da árvore\n"
                "Incluído para preservar todos os campos, inclusive os que não aparecerem no resumo textual.\n"
                "```json\n%s```\n",
                json);
        } else {
            str_addf(&d,
                "# JSON completo da árvore\n"
                "O JSON completo tem %zu bytes e foi omitido para evitar exceder o contexto; use o resumo compacto e os itens-alvo acima.\n",
                tam);
        }
        free(json);
    }

    return d.buf;
}

static char *prompt_montar_final_fallback(const char *arvore_nome, const char *gc_nome, const char *dossie)
{
    Str s;
    str_init(&s);
    if (!s.buf) return NULL;
    str_addf(&s,
        "Você é um assistente técnico. Responda em português, com clareza, precisão e objetividade.\n"
        "Objetivo: atender às mensagens do usuário registradas no GC '%s' da árvore '%s'.\n\n"
        "Use prioritariamente o dossiê abaixo, que já inclui os itens do GC, caminhos desde o tronco, relações, vínculos, valores, referências e visão da árvore.\n"
        "Use também o contexto HEB/HRB do projeto para localizar arquivos e informações adicionais quando os nomes, IDs, paths --ra ou palavras-chave indicarem necessidade.\n"
        "Não invente dados. Se algo não estiver no dossiê nem no contexto HEB/HRB disponível, diga explicitamente que falta informação.\n\n"
        "Ao responder:\n"
        "- identifique quais itens/IDs do GC embasaram a resposta;\n"
        "- preserve relações hierárquicas e vínculos declarados;\n"
        "- cite arquivos --ra relevantes quando existirem;\n"
        "- entregue uma resposta prática, com próximos passos ou decisões quando cabível.\n\n"
        "=== DOSSIÊ DO GC ===\n%s\n=== FIM DO DOSSIÊ DO GC ===\n",
        gc_nome ? gc_nome : "", arvore_nome ? arvore_nome : "", dossie ? dossie : "");
    return s.buf;
}

static int sa_mkdir_p(const char *path)
{
    if (!path || !*path) return -1;
    char tmp[PATH_MAX * 2];
    snprintf(tmp, sizeof(tmp), "%s", path);

    char *inicio = tmp + 1;
    char cwd[PATH_MAX];
    if (getcwd(cwd, sizeof(cwd))) {
        size_t n = strlen(cwd);
        if (strncmp(tmp, cwd, n) == 0 && tmp[n] == '/') inicio = tmp + n + 1;
    }

    for (char *p = inicio; *p; p++) {
        if (*p == '/') {
            *p = '\0';
            if (garantir_dir(tmp) != 0) return -1;
            *p = '/';
        }
    }
    return garantir_dir(tmp);
}

static int caminho_pp(char *out, size_t tam)
{
    char exe[PATH_MAX];
    ssize_t n = readlink("/proc/self/exe", exe, sizeof(exe) - 1);
    if (n <= 0) return -1;
    exe[n] = '\0';
    char *slash = strrchr(exe, '/');
    if (!slash) return -1;
    *slash = '\0';
    return snprintf(out, tam, "%s/../../pp", exe) < (int)tam ? 0 : -1;
}

static int sa_run_exec(char *const args[], const char *cwd, int silencioso)
{
    pid_t pid = fork();
    if (pid < 0) return -1;
    if (pid == 0) {
        if (cwd && chdir(cwd) != 0) _exit(127);
        if (silencioso) {
            int fd = open("/dev/null", O_RDWR);
            if (fd >= 0) { dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); if (fd > STDERR_FILENO) close(fd); }
        }
        execvp(args[0], args);
        _exit(127);
    }
    int st = 0;
    if (waitpid(pid, &st, 0) < 0) return -1;
    return WIFEXITED(st) ? WEXITSTATUS(st) : -1;
}

static int sa_run_capture(char *const args[], const char *cwd, char **saida_out)
{
    if (saida_out) *saida_out = NULL;
    int fd[2];
    if (pipe(fd) != 0) return -1;
    pid_t pid = fork();
    if (pid < 0) { close(fd[0]); close(fd[1]); return -1; }
    if (pid == 0) {
        close(fd[0]);
        if (cwd && chdir(cwd) != 0) _exit(127);
        dup2(fd[1], STDOUT_FILENO);
        close(fd[1]);
        execvp(args[0], args);
        _exit(127);
    }
    close(fd[1]);
    size_t cap = 4096, len = 0;
    char *buf = malloc(cap);
    if (!buf) { close(fd[0]); waitpid(pid, NULL, 0); return -1; }
    char tmp[2048];
    ssize_t r;
    while ((r = read(fd[0], tmp, sizeof(tmp))) > 0) {
        if (len + (size_t)r + 1 > cap) {
            while (len + (size_t)r + 1 > cap) cap *= 2;
            char *nb = realloc(buf, cap);
            if (!nb) { free(buf); close(fd[0]); waitpid(pid, NULL, 0); return -1; }
            buf = nb;
        }
        memcpy(buf + len, tmp, (size_t)r);
        len += (size_t)r;
    }
    close(fd[0]);
    buf[len] = '\0';
    int st = 0;
    waitpid(pid, &st, 0);
    if (saida_out) *saida_out = buf; else free(buf);
    return WIFEXITED(st) ? WEXITSTATUS(st) : -1;
}

static char *sa_git_head(const char *raiz)
{
    char *args[] = { "git", "rev-parse", "HEAD", NULL };
    int fd[2];
    if (pipe(fd) != 0) return NULL;
    pid_t pid = fork();
    if (pid < 0) { close(fd[0]); close(fd[1]); return NULL; }
    if (pid == 0) {
        close(fd[0]);
        if (raiz && chdir(raiz) != 0) _exit(127);
        dup2(fd[1], STDOUT_FILENO);
        close(fd[1]);
        execvp(args[0], args);
        _exit(127);
    }
    close(fd[1]);
    char buf[256];
    ssize_t r = read(fd[0], buf, sizeof(buf) - 1);
    close(fd[0]);
    int st = 0;
    waitpid(pid, &st, 0);
    if (r <= 0 || !WIFEXITED(st) || WEXITSTATUS(st) != 0) return NULL;
    buf[r] = '\0';
    texto_trim_inplace(buf);
    return strdup(buf);
}

static char *sa_json_get_string_simples(const char *json, const char *chave)
{
    char pat[128];
    snprintf(pat, sizeof(pat), "\"%s\"", chave);
    char *p = strstr((char *)(json ? json : ""), pat);
    if (!p) return NULL;
    p = strchr(p, ':');
    if (!p) return NULL;
    while (*++p && isspace((unsigned char)*p));
    if (*p != '"') return NULL;
    p++;
    char *q = p;
    while (*q && *q != '"') { if (*q == '\\' && q[1]) q++; q++; }
    size_t n = (size_t)(q - p);
    char *out = malloc(n + 1);
    if (!out) return NULL;
    memcpy(out, p, n);
    out[n] = '\0';
    return out;
}

static int carregar_arvore(const char *path, Arvore *a, int *nome_corrigido);
static int salvar_arvore(const char *path, Arvore *a);
static void arvore_inicializar_vazia(Arvore *a, const char *nome_arvore);

static char *sa_git_arquivos_commit(const char *raiz, const char *hash, const char *hash_anterior)
{
    if (!hash || !*hash) return xstrdup("");
    if (hash_anterior && *hash_anterior && strcmp(hash, hash_anterior) == 0) return xstrdup("");
    char *args[] = { "git", "diff-tree", "--no-commit-id", "--name-status", "-r", "--root", (char *)hash, NULL };
    char *out = NULL;
    if (sa_run_capture(args, raiz, &out) != 0 || !out) return xstrdup("");
    return out;
}

static char *sa_git_arquivos_desde_commit(const char *raiz, const char *hash)
{
    if (!hash || !*hash) return xstrdup("");
    char *args[] = { "git", "diff", "--name-status", (char *)hash, "--", ".", NULL };
    char *out = NULL;
    if (sa_run_capture(args, raiz, &out) != 0 || !out) return xstrdup("");
    return out;
}

static No *sa_no_add_simples(No *pai, const char *nome)
{
    No *n = no_criar(nome ? nome : "");
    if (!n) return NULL;
    if (no_add_filho(pai, n) != 0) { no_liberar(n); free(n); return NULL; }
    return n;
}

static char *sa_ra_de_relpath(const char *raiz, const char *rel)
{
    if (!rel || !*rel) return NULL;
    while (!strncmp(rel, "./", 2)) rel += 2;
    if (!strncmp(rel, "secretos/", 9) || !strncmp(rel, ".git/", 5)) return NULL;
    if (!strncmp(rel, "sisc/", 5)) return xstrdup(rel);
    const char *p = raiz ? strstr(raiz, "/sisc/") : NULL;
    const char *base = p ? p + 1 : "sisc";
    size_t nb = strlen(base), nr = strlen(rel);
    char *out = malloc(nb + 1 + nr + 1);
    if (!out) return NULL;
    memcpy(out, base, nb);
    out[nb] = '/';
    memcpy(out + nb + 1, rel, nr + 1);
    return out;
}

static void sa_parse_linha_git(const char *linha, char *status, size_t st_sz, char *path, size_t path_sz)
{
    if (status && st_sz) status[0] = '\0';
    if (path && path_sz) path[0] = '\0';
    if (!linha) return;
    const char *tab = strchr(linha, '\t');
    if (!tab) {
        snprintf(status, st_sz, "?");
        snprintf(path, path_sz, "%s", linha);
        return;
    }
    size_t ns = (size_t)(tab - linha);
    if (ns >= st_sz) ns = st_sz - 1;
    memcpy(status, linha, ns); status[ns] = '\0';
    const char *p = tab + 1;
    const char *tab2 = strchr(p, '\t');
    if (tab2) p = tab2 + 1; /* renome/cópia: mostra o caminho final */
    snprintf(path, path_sz, "%s", p);
}

static int sa_adicionar_lista_arquivos(No *grupo, const char *raiz, const char *linhas, const char *vazio, int ignorar_reversao)
{
    int total = 0;
    char *dup = xstrdup(linhas ? linhas : "");
    if (!dup) return 0;
    char *save = NULL;
    for (char *ln = strtok_r(dup, "\n", &save); ln; ln = strtok_r(NULL, "\n", &save)) {
        while (*ln && isspace((unsigned char)*ln)) ln++;
        if (!*ln) continue;
        char st[64], path[PATH_MAX * 2];
        sa_parse_linha_git(ln, st, sizeof(st), path, sizeof(path));
        if (!path[0]) continue;
        if (ignorar_reversao && strcmp(path, "arvores/reversao-gc.json") == 0) continue;
        char nome[PATH_MAX * 2 + 128];
        snprintf(nome, sizeof(nome), "%s %s", st[0] ? st : "?", path);
        No *item = sa_no_add_simples(grupo, nome);
        if (item) {
            char *ra = sa_ra_de_relpath(raiz, path);
            if (ra) { item->ra = ra; }
            total++;
        }
    }
    free(dup);
    if (total == 0 && vazio) sa_no_add_simples(grupo, vazio);
    return total;
}

static int sa_reversao_gc_carregar(const char *raiz, Arvore *arv, char *path, size_t path_sz)
{
    char dir[PATH_MAX * 2];
    snprintf(dir, sizeof(dir), "%s/arvores", raiz ? raiz : ".");
    if (garantir_dir(dir) != 0) return -1;
    snprintf(path, path_sz, "%s/reversao-gc.json", dir);
    if (access(path, F_OK) == 0) {
        if (carregar_arvore(path, arv, NULL) == 0) return 0;
        fprintf(stderr, "sa: arquivo de reversão já existe, mas não é uma árvore SA válida: %s. Operação cancelada para não sobrescrever.\n", path);
        return -1;
    }
    arvore_inicializar_vazia(arv, "reversao-gc");
    return 0;
}

static char *sa_no_valor_prefixo_rec(No *n, const char *prefixo)
{
    if (!n || !prefixo) return NULL;
    size_t np = strlen(prefixo);
    if (n->nome && strncmp(n->nome, prefixo, np) == 0) return xstrdup(n->nome + np);
    for (int i = 0; i < n->total; i++) {
        char *v = sa_no_valor_prefixo_rec(n->filhos[i], prefixo);
        if (v) return v;
    }
    return NULL;
}

static No *sa_reversao_gc_encontrar_checkpoint(Arvore *arv, const char *id, const char *arvore_nome, const char *gc)
{
    if (!arv) return NULL;
    for (int i = arv->raiz.total - 1; i >= 0; i--) {
        No *cp = arv->raiz.filhos[i];
        if (id && *id) {
            char *vid = sa_no_valor_prefixo_rec(cp, "id checkpoint: ");
            int ok = (vid && strcmp(vid, id) == 0);
            free(vid);
            if (ok) return cp;
            continue;
        }
        char *va = sa_no_valor_prefixo_rec(cp, "arvore alvo: ");
        char *vg = sa_no_valor_prefixo_rec(cp, "gc alvo: ");
        int ok = va && vg && strcmp(va, arvore_nome ? arvore_nome : "") == 0 && strcmp(vg, gc ? gc : "") == 0;
        free(va); free(vg);
        if (ok) return cp;
    }
    return NULL;
}

static int sa_reversao_gc_registrar_checkpoint(const char *raiz, const char *arvore, const char *gc,
                                               const char *hash, const char *hash_anterior,
                                               const char *commit_msg, const char *id)
{
    Arvore arv;
    char path[PATH_MAX * 2];
    if (sa_reversao_gc_carregar(raiz, &arv, path, sizeof(path)) != 0) return -1;
    char titulo[1024];
    snprintf(titulo, sizeof(titulo), "checkpoint %s — arvore=%s gc=%s", id ? id : "", arvore ? arvore : "", gc ? gc : "");
    No *cp = sa_no_add_simples(&arv.raiz, titulo);
    if (!cp) { arvore_liberar(&arv); return -1; }

    char *iso = agora_iso();
    No *ident = sa_no_add_simples(cp, "identificacao operacional da reversao");
    char linha[2048];
    snprintf(linha, sizeof(linha), "id checkpoint: %s", id ? id : ""); sa_no_add_simples(ident, linha);
    snprintf(linha, sizeof(linha), "arvore alvo: %s", arvore ? arvore : ""); sa_no_add_simples(ident, linha);
    snprintf(linha, sizeof(linha), "gc alvo: %s", gc ? gc : ""); sa_no_add_simples(ident, linha);
    snprintf(linha, sizeof(linha), "commit para reversao: %s", hash ? hash : ""); sa_no_add_simples(ident, linha);
    snprintf(linha, sizeof(linha), "commit anterior ao checkpoint: %s", hash_anterior && *hash_anterior ? hash_anterior : "sem HEAD anterior"); sa_no_add_simples(ident, linha);
    snprintf(linha, sizeof(linha), "mensagem do commit checkpoint: %s", commit_msg ? commit_msg : ""); sa_no_add_simples(ident, linha);
    snprintf(linha, sizeof(linha), "criado em: %s", iso ? iso : ""); sa_no_add_simples(ident, linha);
    snprintf(linha, sizeof(linha), "comando de reversao: pp --sa=%s --reverter=%s", arvore ? arvore : "", gc ? gc : ""); sa_no_add_simples(ident, linha);

    No *commitados = sa_no_add_simples(cp, "arquivos commitados no checkpoint antes do prompt");
    char *arquivos_commit = sa_git_arquivos_commit(raiz, hash, hash_anterior);
    sa_adicionar_lista_arquivos(commitados, raiz, arquivos_commit, "nenhum arquivo novo foi commitado neste checkpoint; HEAD ja estava limpo", 0);

    No *risco = sa_no_add_simples(cp, "arquivos alterados apos o checkpoint que podem ser perdidos na reversao");
    char *arquivos_risco = sa_git_arquivos_desde_commit(raiz, hash);
    sa_adicionar_lista_arquivos(risco, raiz, arquivos_risco, "nenhuma divergencia detectada imediatamente apos o checkpoint", 1);

    No *regras = sa_no_add_simples(cp, "orientacao ao operador antes de reverter");
    sa_no_add_simples(regras, "revise este checkpoint e os snapshots de risco antes de executar --reverter");
    sa_no_add_simples(regras, "a reversao volta os arquivos rastreados pelo Git ao commit para reversao informado acima");
    sa_no_add_simples(regras, "se um arquivo listado em 'alterados apos o checkpoint' recebeu mudanças posteriores, estas mudanças podem ser revertidas/perdidas");

    int r = salvar_arvore(path, &arv);
    printf("sa: material de reversao registrado na arvore %s\n", path);
    free(iso); free(arquivos_commit); free(arquivos_risco); arvore_liberar(&arv);
    return r;
}

static int sa_reversao_gc_registrar_snapshot(const char *raiz, const char *id, const char *hash, const char *rotulo)
{
    if (!id || !*id || !hash || !*hash) return 0;
    Arvore arv;
    char path[PATH_MAX * 2];
    if (sa_reversao_gc_carregar(raiz, &arv, path, sizeof(path)) != 0) return -1;
    No *cp = sa_reversao_gc_encontrar_checkpoint(&arv, id, NULL, NULL);
    if (!cp) { arvore_liberar(&arv); return -1; }
    char *iso = agora_iso();
    char titulo[512];
    snprintf(titulo, sizeof(titulo), "%s em %s", rotulo ? rotulo : "snapshot", iso ? iso : "");
    No *snap = sa_no_add_simples(cp, titulo);
    char *arquivos = sa_git_arquivos_desde_commit(raiz, hash);
    sa_adicionar_lista_arquivos(snap, raiz, arquivos, "nenhum arquivo rastreado divergente em relacao ao checkpoint", 1);
    int r = salvar_arvore(path, &arv);
    printf("sa: snapshot de risco atualizado em %s\n", path);
    free(iso); free(arquivos); arvore_liberar(&arv);
    return r;
}

static char *sa_ler_checkpoint_latest_arvore(const char *raiz, const char *arvore_nome, const char *gc, char *path_out, size_t path_tam)
{
    Arvore arv;
    char path[PATH_MAX * 2];
    if (sa_reversao_gc_carregar(raiz, &arv, path, sizeof(path)) != 0) return NULL;
    No *cp = sa_reversao_gc_encontrar_checkpoint(&arv, NULL, arvore_nome, gc);
    if (!cp) { arvore_liberar(&arv); return NULL; }
    char *hash = sa_no_valor_prefixo_rec(cp, "commit para reversao: ");
    char *id = sa_no_valor_prefixo_rec(cp, "id checkpoint: ");
    if (!hash || !*hash) { free(hash); free(id); arvore_liberar(&arv); return NULL; }
    if (path_out && path_tam) snprintf(path_out, path_tam, "%s", path);
    Str s; str_init(&s);
    str_add(&s, "{\n  \"tipo\": \"checkpoint-sa-arvore\",\n  \"id\": "); json_escape(&s, id ? id : "");
    str_add(&s, ",\n  \"commitAntes\": "); json_escape(&s, hash);
    str_add(&s, "\n}\n");
    free(hash); free(id); arvore_liberar(&arv);
    return s.buf;
}

static int sa_eh_arvore_reversao_gc(const char *path, Arvore *arv)
{
    if (arv && arv->nome && strcmp(arv->nome, "reversao-gc") == 0) return 1;
    const char *base = strrchr(path ? path : "", '/');
    base = base ? base + 1 : (path ? path : "");
    return strcmp(base, "reversao-gc.json") == 0;
}

static int sa_tarefas_checkpoint_limpar_um(const char *raiz, const char *arvore_nome, const char *gc, const char *id)
{
    if (!raiz || !arvore_nome || !*arvore_nome || !gc || !*gc || !id || !*id) return 0;
    char *na = normalizar_nome(arvore_nome);
    char *ng = normalizar_nome(gc);
    if (!na || !ng) { free(na); free(ng); return 0; }
    char dir[PATH_MAX * 2], path_id[PATH_MAX * 3], latest[PATH_MAX * 3], pai[PATH_MAX * 2];
    snprintf(dir, sizeof(dir), "%s/tarefas/sa-checkpoints/%s/%s", raiz, na, ng);
    snprintf(path_id, sizeof(path_id), "%s/%s.json", dir, id);
    snprintf(latest, sizeof(latest), "%s/latest.json", dir);
    int removidos = 0;
    if (unlink(path_id) == 0) removidos++;
    char *lj = ler_arquivo(latest);
    if (lj) {
        char *lid = sa_json_get_string_simples(lj, "id");
        if (lid && strcmp(lid, id) == 0 && unlink(latest) == 0) removidos++;
        free(lid); free(lj);
    }
    rmdir(dir);
    snprintf(pai, sizeof(pai), "%s/tarefas/sa-checkpoints/%s", raiz, na);
    rmdir(pai);
    free(na); free(ng);
    return removidos;
}

static int sa_tarefas_checkpoint_limpar_rec(const char *raiz, No *n)
{
    if (!n) return 0;
    int removidos = 0;
    char *id = sa_no_valor_prefixo_rec(n, "id checkpoint: ");
    if (id && *id) {
        char *arvore_nome = sa_no_valor_prefixo_rec(n, "arvore alvo: ");
        char *gc = sa_no_valor_prefixo_rec(n, "gc alvo: ");
        removidos += sa_tarefas_checkpoint_limpar_um(raiz, arvore_nome, gc, id);
        free(arvore_nome); free(gc);
    }
    free(id);
    for (int i = 0; i < n->total; i++) removidos += sa_tarefas_checkpoint_limpar_rec(raiz, n->filhos[i]);
    return removidos;
}

static int sa_salvar_checkpoint(const char *raiz, const char *arvore, const char *gc, const char *hash, const char *hash_anterior, const char *commit_msg, char **id_out)
{
    char id[128];
    snprintf(id, sizeof(id), "%ld-%.12s", (long)time(NULL), hash ? hash : "semhash");
    if (id_out) *id_out = xstrdup(id);
    return sa_reversao_gc_registrar_checkpoint(raiz, arvore, gc, hash, hash_anterior, commit_msg, id);
}

static int sa_checkpoint_github_antes_prompt(const char *raiz, const char *arvore, const char *gc, char **hash_out, char **id_out)
{
    if (hash_out) *hash_out = NULL;
    if (id_out) *id_out = NULL;
    char pp[PATH_MAX * 2];
    if (caminho_pp(pp, sizeof(pp)) != 0) { fprintf(stderr, "sa: não foi possível localizar pp para checkpoint.\n"); return 1; }
    char *hash_anterior = sa_git_head(raiz);
    char arg_push[256], arg_commit[640], commit_msg[512];
    snprintf(arg_push, sizeof(arg_push), "--push-github=%s", SA_CHECKPOINT_GITHUB_CONTA);
    snprintf(commit_msg, sizeof(commit_msg), "checkpoint-sa: arvore=%s gc=%s antes", arvore ? arvore : "", gc ? gc : "");
    snprintf(arg_commit, sizeof(arg_commit), "--commit=%s", commit_msg);
    printf("\n=== CHECKPOINT GITHUB ANTES DO --sa --prompt ===\n");
    printf("sa: executando pp --push-github=%s --commit=\"%s\"\n", SA_CHECKPOINT_GITHUB_CONTA, commit_msg);
    char *args[] = { pp, arg_push, arg_commit, NULL };
    int rc = sa_run_exec(args, raiz, 0);
    if (rc != 0) { free(hash_anterior); fprintf(stderr, "sa: checkpoint GitHub falhou (código %d); abortando.\n", rc); return rc; }
    char *hash = sa_git_head(raiz);
    if (!hash || !*hash) { free(hash); free(hash_anterior); fprintf(stderr, "sa: não foi possível obter hash do checkpoint.\n"); return 1; }
    printf("sa: checkpoint GitHub criado: %s\n", hash);
    char *id = NULL;
    rc = sa_salvar_checkpoint(raiz, arvore, gc, hash, hash_anterior, commit_msg, &id);
    if (rc == 0) {
        if (hash_out) *hash_out = xstrdup(hash);
        if (id_out) *id_out = id;
        else free(id);
    } else free(id);
    free(hash); free(hash_anterior);
    return rc == 0 ? 0 : 1;
}

static char *sa_ler_checkpoint_latest(const char *raiz, const char *arvore, const char *gc, char *path_out, size_t path_tam)
{
    char *ck = sa_ler_checkpoint_latest_arvore(raiz, arvore, gc, path_out, path_tam);
    if (ck) return ck;

    /* Compatibilidade: checkpoints antigos ainda podem estar em tarefas/sa-checkpoints/. */
    char *na = normalizar_nome(arvore ? arvore : "");
    char *ng = normalizar_nome(gc ? gc : "");
    if (!na || !ng) { free(na); free(ng); return NULL; }
    char path[PATH_MAX * 2];
    snprintf(path, sizeof(path), "%s/tarefas/sa-checkpoints/%s/%s/latest.json", raiz, na, ng);
    if (path_out && path_tam) snprintf(path_out, path_tam, "%s", path);
    free(na); free(ng);
    return ler_arquivo(path);
}

static int sa_reverter_checkpoint_gc(const char *raiz, const char *arvore, const char *gc)
{
    char ckpath[PATH_MAX * 2];
    char *ck = sa_ler_checkpoint_latest(raiz, arvore, gc, ckpath, sizeof(ckpath));
    if (!ck) { fprintf(stderr, "sa: checkpoint não encontrado para árvore '%s' e GC '%s'.\n", arvore, gc); return 1; }
    char *hash = sa_json_get_string_simples(ck, "commitAntes");
    char *id = sa_json_get_string_simples(ck, "id");
    free(ck);
    if (!hash || !*hash) { fprintf(stderr, "sa: checkpoint sem commitAntes válido: %s\n", ckpath); free(hash); free(id); return 1; }
    char *head = sa_git_head(raiz);
    if (!head || !*head) { fprintf(stderr, "sa: não foi possível obter HEAD atual.\n"); free(hash); free(id); free(head); return 1; }
    printf("\n=== REVERTENDO --sa --prompt PELO CHECKPOINT GITHUB ===\n");
    printf("sa: checkpoint: %s\nsa: commit alvo: %s\nsa: HEAD atual:  %s\n", id ? id : "", hash, head);
    char *reset_hard[] = { "git", "reset", "--hard", hash, NULL };
    int rc = sa_run_exec(reset_hard, raiz, 0);
    if (rc == 0) {
        char *reset_soft[] = { "git", "reset", "--soft", head, NULL };
        rc = sa_run_exec(reset_soft, raiz, 0);
    }
    if (rc != 0) { fprintf(stderr, "sa: falha ao preparar reversão git (código %d).\n", rc); free(hash); free(id); free(head); return rc; }
    char pp[PATH_MAX * 2], arg_push[256], arg_commit[640], msg[512];
    if (caminho_pp(pp, sizeof(pp)) != 0) { free(hash); free(id); free(head); return 1; }
    snprintf(arg_push, sizeof(arg_push), "--push-github=%s", SA_CHECKPOINT_GITHUB_CONTA);
    snprintf(msg, sizeof(msg), "reverter-sa: arvore=%s gc=%s checkpoint=%.12s", arvore ? arvore : "", gc ? gc : "", hash);
    snprintf(arg_commit, sizeof(arg_commit), "--commit=%s", msg);
    printf("sa: publicando commit de reversão: %s\n", msg);
    char *push_args[] = { pp, arg_push, arg_commit, NULL };
    rc = sa_run_exec(push_args, raiz, 0);
    if (rc == 0) printf("sa: reversão concluída e publicada.\n");
    else fprintf(stderr, "sa: push da reversão falhou (código %d).\n", rc);
    free(hash); free(id); free(head);
    return rc;
}

static int caminho_parallm(char *out, size_t tam)
{
    if (!out || tam == 0) return -1;
    char exe[PATH_MAX];
    ssize_t n = readlink("/proc/self/exe", exe, sizeof(exe) - 1);
    if (n <= 0) return -1;
    exe[n] = '\0';
    char *slash = strrchr(exe, '/');
    if (!slash) return -1;
    *slash = '\0';
    size_t len = strlen(exe);
    if (len + strlen("/parallm") + 1 > tam) return -1;
    memcpy(out, exe, len);
    memcpy(out + len, "/parallm", strlen("/parallm") + 1);
    return 0;
}

static int executar_parallm_prompt(const char *prompt, int relatorio, int sem_heb, int executar_at)
{
    char parallm_path[PATH_MAX];
    if (caminho_parallm(parallm_path, sizeof(parallm_path)) != 0) {
        fprintf(stderr, "sa: não foi possível localizar o comando parallm.\n");
        return 1;
    }

    char *args[10];
    int pos = 0;
    args[pos++] = parallm_path;
    args[pos++] = (char *)prompt;
    if (relatorio) args[pos++] = "--rel";
    if (sem_heb) args[pos++] = "--sem-heb";
    if (executar_at) args[pos++] = "--executar";
    args[pos] = NULL;

    pid_t pid = fork();
    if (pid < 0) { perror("sa: fork"); return 1; }
    if (pid == 0) {
        execv(parallm_path, args);
        perror("sa: execv parallm");
        _exit(127);
    }
    int status = 0;
    if (waitpid(pid, &status, 0) < 0) { perror("sa: waitpid"); return 1; }
    if (WIFEXITED(status)) return WEXITSTATUS(status);
    return 1;
}

static int sa_prompt_executar(Arvore *arv, const char *gc_nome, int relatorio)
{
    if (!gc_nome || !*gc_nome) return 0;
    char raiz_checkpoint[PATH_MAX];
    if (raiz_projeto(raiz_checkpoint, sizeof(raiz_checkpoint)) != 0) {
        fprintf(stderr, "sa: não foi possível detectar raiz para checkpoint GitHub.\n");
        return 1;
    }
    char *checkpoint_hash = NULL;
    char *checkpoint_id = NULL;
    int chk = sa_checkpoint_github_antes_prompt(raiz_checkpoint, arv && arv->nome ? arv->nome : "", gc_nome, &checkpoint_hash, &checkpoint_id);
    if (chk != 0) return chk;
    int total = 0;
    char *dossie = prompt_montar_dossie_gc(arv, gc_nome, &total);
    if (!dossie) {
        fprintf(stderr, "sa: gc '%s' não encontrado na árvore '%s'.\n", gc_nome, arv && arv->nome ? arv->nome : "");
        free(checkpoint_hash);
        free(checkpoint_id);
        return 1;
    }

    char *prompt_final = prompt_montar_final_fallback(arv && arv->nome ? arv->nome : "", gc_nome, dossie);
    if (!prompt_final) {
        fprintf(stderr, "sa: erro de memória ao montar prompt final.\n");
        free(dossie);
        free(checkpoint_hash);
        free(checkpoint_id);
        return 1;
    }

    int r1 = 0, r2 = 0;
    printf("\n=== PROMPT ESPECIALIZADO DO GC '%s' (%d item(ns); montado deterministicamente pelo sa; agente-at automático) ===\n",
           gc_nome, total);
    if (relatorio) {
        printf("\n=== EXECUÇÃO SEM HEB/HRB PARA COMPARAÇÃO ===\n");
        fflush(stdout);
        r1 = executar_parallm_prompt(prompt_final, 1, 1, 0);
        printf("\n=== EXECUÇÃO PADRÃO COM HEB/HRB VIA --parallm + agente-at automático ===\n");
        fflush(stdout);
        r2 = executar_parallm_prompt(prompt_final, 1, 0, 1);
    } else {
        printf("\n=== EXECUÇÃO PADRÃO COM HEB/HRB VIA --parallm + agente-at automático ===\n");
        fflush(stdout);
        r1 = executar_parallm_prompt(prompt_final, 0, 0, 1);
    }

    if (checkpoint_hash && checkpoint_id) {
        sa_reversao_gc_registrar_snapshot(raiz_checkpoint, checkpoint_id, checkpoint_hash,
                                          "snapshot apos execucao do prompt");
    }

    free(prompt_final);
    free(dossie);
    free(checkpoint_hash);
    free(checkpoint_id);
    return r1 ? r1 : r2;
}

static int carregar_arvore(const char *path, Arvore *a, int *nome_corrigido)
{
    char *json = ler_arquivo(path);
    if (!json) return -1;
    int r = parse_arvore(json, a);
    free(json);
    if (nome_corrigido) *nome_corrigido = 0;
    if (r == 0) {
        int alterou = sincronizar_nome_arvore_com_arquivo(a, path);
        alterou = aplicar_cor_tronco(a) || alterou;
        if (nome_corrigido) *nome_corrigido = alterou;
    }
    return r;
}

static int salvar_arvore(const char *path, Arvore *a)
{
    sincronizar_nome_arvore_com_arquivo(a, path);
    aplicar_cor_tronco(a);
    free(a->atualizadoEm);
    a->atualizadoEm = agora_iso();
    /* garante que o diretório pai existe (necessário para árvores com caminho) */
    char dirp[PATH_MAX * 2];
    snprintf(dirp, sizeof(dirp), "%s", path);
    char *slash = strrchr(dirp, '/');
    if (slash) { *slash = '\0'; sa_mkdir_p(dirp); }
    char *json = arvore_json(a);
    if (!json) return -1;
    int r = escrever_arquivo(path, json);
    free(json);
    return r;
}

static void arvore_inicializar_vazia(Arvore *a, const char *nome_arvore)
{
    memset(a, 0, sizeof(*a));
    a->nome = xstrdup(nome_arvore);
    a->criadoEm = agora_iso();
    a->atualizadoEm = agora_iso();
    no_init(&a->raiz, nome_arvore);
}

typedef struct {
    char *nome;
    int dir;
} EntradaDir;

static int comparar_entrada_dir(const void *a, const void *b)
{
    const EntradaDir *ea = (const EntradaDir *)a;
    const EntradaDir *eb = (const EntradaDir *)b;
    if (ea->dir != eb->dir) return eb->dir - ea->dir; /* diretórios primeiro */
    return strcasecmp(ea->nome ? ea->nome : "", eb->nome ? eb->nome : "");
}

static int path_mesmo_arquivo(const char *a, const char *b)
{
    if (!a || !b) return 0;
    char ra[PATH_MAX * 2], rb[PATH_MAX * 2];
    if (realpath(a, ra) && realpath(b, rb)) return strcmp(ra, rb) == 0;
    return strcmp(a, b) == 0;
}

static int nome_oculto(const char *nome)
{
    return nome && nome[0] == '.';
}

static int montar_no_dir_rec(const char *abs_dir, const char *nome_exibir, const char *path_arvore_saida, No **out, long *total_itens)
{
    No *dir_no = no_criar(nome_exibir);
    if (!dir_no) return -1;
    DIR *d = opendir(abs_dir);
    if (!d) {
        no_liberar(dir_no);
        free(dir_no);
        return -1;
    }

    EntradaDir *entradas = NULL;
    size_t n = 0, cap = 0;
    struct dirent *e;
    while ((e = readdir(d)) != NULL) {
        if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue;
        if (nome_oculto(e->d_name)) continue; /* comportamento próximo ao tree padrão, sem -a */

        char full[PATH_MAX * 3];
        snprintf(full, sizeof(full), "%s/%s", abs_dir, e->d_name);
        if (path_mesmo_arquivo(full, path_arvore_saida)) continue;

        struct stat st;
        if (lstat(full, &st) != 0) continue;
        if (n == cap) {
            size_t nc = cap ? cap * 2 : 32;
            EntradaDir *nv = realloc(entradas, nc * sizeof(EntradaDir));
            if (!nv) { closedir(d); free(entradas); no_liberar(dir_no); free(dir_no); return -1; }
            entradas = nv;
            cap = nc;
        }
        entradas[n].nome = xstrdup(e->d_name);
        entradas[n].dir = S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode);
        if (!entradas[n].nome) { closedir(d); free(entradas); no_liberar(dir_no); free(dir_no); return -1; }
        n++;
    }
    closedir(d);
    qsort(entradas, n, sizeof(EntradaDir), comparar_entrada_dir);

    for (size_t i = 0; i < n; i++) {
        char full[PATH_MAX * 3];
        snprintf(full, sizeof(full), "%s/%s", abs_dir, entradas[i].nome);
        No *filho = NULL;
        if (entradas[i].dir) {
            char nome_dir[PATH_MAX];
            snprintf(nome_dir, sizeof(nome_dir), "%s/", entradas[i].nome);
            if (montar_no_dir_rec(full, nome_dir, path_arvore_saida, &filho, total_itens) != 0) {
                filho = no_criar(nome_dir);
            }
        } else {
            filho = no_criar(entradas[i].nome);
        }
        if (!filho || no_add_filho(dir_no, filho) != 0) {
            if (filho) { no_liberar(filho); free(filho); }
            for (size_t j = 0; j < n; j++) free(entradas[j].nome);
            free(entradas);
            no_liberar(dir_no);
            free(dir_no);
            return -1;
        }
        if (total_itens) (*total_itens)++;
    }
    for (size_t i = 0; i < n; i++) free(entradas[i].nome);
    free(entradas);
    *out = dir_no;
    return 0;
}

static const char *base_nome_dir(const char *path)
{
    if (!path || !*path) return ".";
    const char *fim = path + strlen(path);
    while (fim > path && fim[-1] == '/') fim--;
    const char *p = fim;
    while (p > path && p[-1] != '/') p--;
    return *p ? p : path;
}

static int criar_arvore_do_diretorio_atual(const char *path_arvore, const char *nome_arvore, int json_out)
{
    char cwd[PATH_MAX];
    if (!getcwd(cwd, sizeof(cwd))) {
        perror("sa --dir: getcwd");
        return 1;
    }

    Arvore arv;
    arvore_inicializar_vazia(&arv, nome_arvore && *nome_arvore ? nome_arvore : "estrutura-diretorio");

    char raiz_nome[PATH_MAX];
    snprintf(raiz_nome, sizeof(raiz_nome), "%s/", base_nome_dir(cwd));
    No *raiz_dir = NULL;
    long total_itens = 0;
    if (montar_no_dir_rec(cwd, raiz_nome, path_arvore, &raiz_dir, &total_itens) != 0 || !raiz_dir) {
        fprintf(stderr, "sa --dir: não foi possível varrer o diretório atual: %s\n", cwd);
        arvore_liberar(&arv);
        return 1;
    }
    if (no_add_filho(&arv.raiz, raiz_dir) != 0) {
        no_liberar(raiz_dir);
        free(raiz_dir);
        arvore_liberar(&arv);
        return 1;
    }

    if (salvar_arvore(path_arvore, &arv) != 0) {
        fprintf(stderr, "sa --dir: não foi possível salvar árvore em %s\n", path_arvore);
        arvore_liberar(&arv);
        return 1;
    }

    printf("Árvore '%s' criada a partir de %s\n", nome_arvore ? nome_arvore : "", cwd);
    printf("Arquivo: %s\n", path_arvore);
    printf("Itens registrados: %ld\n", total_itens + 1);
    if (json_out) {
        char *json = arvore_json(&arv);
        if (json) { puts(json); free(json); }
    }
    arvore_liberar(&arv);
    return 0;
}

static int confirmar_criar_arquivo_arvore(const char *nome_arvore, const char *path)
{
    char resp[32];
    fprintf(stderr, "Árvore '%s' não encontrada.\n", nome_arvore ? nome_arvore : "");
    fprintf(stderr, "Arquivo esperado: %s\n", path ? path : "");
    fprintf(stderr, "Deseja criar um arquivo novo para esta árvore? [s/N] ");
    fflush(stderr);

    if (!fgets(resp, sizeof(resp), stdin)) {
        fprintf(stderr, "\nCriação cancelada.\n");
        return 0;
    }

    char *p = resp;
    while (*p && isspace((unsigned char)*p)) p++;
    if (*p == 's' || *p == 'S' || *p == 'y' || *p == 'Y') return 1;

    fprintf(stderr, "Criação cancelada.\n");
    return 0;
}

static int listar_arvores(const char *dir)
{
    DIR *d = opendir(dir);
    if (!d) return -1;

    int cap = 32, total = 0;
    char **nomes = malloc((size_t)cap * sizeof(char *));
    if (!nomes) { closedir(d); return -1; }

    struct dirent *e;
    while ((e = readdir(d)) != NULL) {
        const char *n = e->d_name;
        size_t len = strlen(n);
        if (len <= 5 || strcmp(n + len - 5, ".json") != 0) continue;

        if (total >= cap) {
            cap *= 2;
            char **nv = realloc(nomes, (size_t)cap * sizeof(char *));
            if (!nv) { closedir(d); free(nomes); return -1; }
            nomes = nv;
        }
        nomes[total] = strdup(n);
        if (!nomes[total]) { closedir(d); free(nomes); return -1; }
        total++;
    }
    closedir(d);

    if (total == 0) {
        free(nomes);
        printf("Nenhuma arvore encontrada em %s\n", dir);
        return 0;
    }

    /* ordena alfabeticamente */
    for (int i = 0; i < total; i++)
        for (int j = i + 1; j < total; j++)
            if (strcasecmp(nomes[i], nomes[j]) > 0) {
                char *tmp = nomes[i]; nomes[i] = nomes[j]; nomes[j] = tmp;
            }

    printf("\nArvores em %s:\n\n", dir);
    for (int i = 0; i < total; i++) {
        /* remove extensao .json para exibicao */
        nomes[i][strlen(nomes[i]) - 5] = '\0';
        printf("  %s\n", nomes[i]);
        free(nomes[i]);
    }
    printf("\n");
    free(nomes);
    return 0;
}

static void ajuda(void)
{
    printf("Uso:\n");
    printf("  pp --sa                                  Lista as árvores existentes\n");
    printf("  pp --sa=<árvore>                         Exibe a árvore; se não existir, pergunta antes de criar arquivo vazio\n");
    printf("  pp --sa=<árvore> --ag=<galho>            Pergunta antes de criar a árvore, se não existir, e adiciona galho\n");
    printf("  pp --sa=<árvore> [opções]                Manipula a árvore\n");
    printf("  pp --sa=<árvore> --prompt=<gc> [--rel]   Faz checkpoint GitHub, registra reversão em arvores/reversao-gc.json,\n");
    printf("                                             monta prompt do GC, envia ao --parallm e aciona agente-at\n");
    printf("  pp --sa=<árvore> --reverter=<gc>          Reverte para o checkpoint salvo antes do GC\n");
    printf("  pp --sa --mapa [opcoes]                  Alias legado para pp --atualizar-manual-funcional\n");
    printf("  pp --sa --ajuda:<flag>                    Exibe só o grupo de ajuda que contém a flag\n");
    printf("\n");
    printf("Descrição:\n");
    printf("  Gerencia árvores hierárquicas de galhos persistidas em JSON.\n");
    printf("  Por padrão, cada árvore fica no diretório atual como <nome-normalizado>.json.\n");
    printf("  Também é possível informar caminho relativo ou absoluto: pp --sa=dir/minha-arvore.\n");
    printf("  Os itens são endereçados por IDs posicionais, por exemplo: 1, 2, 2.1, 2.1.3.\n");
    printf("  --mapa encaminha para pp --atualizar-manual-funcional; a saída canônica é sisc/arvores/operador-manual-funcional.json.\n");
    printf("  O nome informado por --sa=<nome> é ignorado pelo alias --mapa; use pp --sa=arvores/operador-manual-funcional para abrir.\n");
    printf("\n");
    printf("Grupos de ajuda: uso, estrutura, exibicao, relacoes, valores, gc, arquivos, legenda, procura, exemplos, mapa.\n");
    printf("Exemplos: pp --sa --ajuda:estrutura | pp --sa --ajuda:gc | pp --sa --ajuda:mapa\n");
    printf("\n");
    printf("Regras importantes:\n");
    printf("  - --cor foi removido: o esquema de cores da árvore é fixo.\n");
    printf("  - Galhos só são criados por --ag; pp --sa=<árvore> sem --ag nunca cria galho.\n");
    printf("  - Itens representam opções/assuntos deliberados dentro do galho pai; simples quebras de linha não viram novos itens.\n");
    printf("  - Quebras de linha recebidas em --ag/--eg são convertidas para espaços no mesmo item.\n");
    printf("  - --em=<id> aponta o item-alvo para edição; com --ag pode indicar posição/pai.\n");
    printf("  - --ag sempre cria itens sem status explícito (status=null/branco); use --status depois com --em para alterar.\n");
    printf("  - --prompt, quando combinado com outras operações, é executado por último.\n");
    printf("  - --ajuda:<flag> e --ajuda=<flag> são filtrados pelo pp; use o nome com ou sem --.\n");
    printf("  - --rel=<id> cria relação entre galhos; --rel sem valor só é válido com --prompt.\n");
    printf("  - --conta-llm=<conta> pode ser passado ao pp para sobrescrever a conta LLM usada.\n");
    printf("  - Em arvores/reversao-gc.json, --dg de checkpoint remove também checkpoints legados análogos em tarefas/sa-checkpoints/.\n");
    printf("\n");

    printf("1) Consulta, saída e ajuda [consulta saida ajuda uso]\n");
    printf("  --json                         Exibe a árvore em JSON\n");
    printf("  --dir                          Recria a árvore com a estrutura recursiva de diretórios/arquivos\n");
    printf("                                 a partir do diretório onde o comando foi invocado\n");
    printf("                                 Ex: pp --sa=nome-arvore --dir\n");
    printf("                                 Usa apenas nomes; não lê conteúdo dos arquivos; ignora entradas ocultas\n");
    printf("  --legenda                      Exibe tabela com cores e significados do pp --sa\n");
    printf("  --ajuda, --help, -h            Exibe esta ajuda completa\n");
    printf("  --ajuda --proc=<termo>         Abre esta ajuda com procura destacada; única flag permitida junto de --ajuda\n");
    printf("  --ajuda:<flag>                 Exibe apenas o grupo da flag. Ex: pp --sa --ajuda:ag\n");
    printf("  --ajuda=<flag>                 Forma equivalente aceita pelo roteador pp\n");
    printf("\n");

    printf("2) Estrutura da árvore [estrutura arvore status]\n");
    printf("  --ag=<nome>                    Adiciona novo galho; na raiz ou na posição/pai de --em\n");
    printf("                                 Uma chamada --ag cria um único item; quebras de linha viram espaços, não subitens\n");
    printf("  --em=<id>                      Item-alvo ou posição. Ex: --em=2.1; --em=3 cria na raiz se 3 ainda não existir\n");
    printf("  --eg=<nome> --em=<id>          Renomeia o galho informado em --em\n");
    printf("  --status=<valor> --em=<id>     Define status do item e de todos os filhos: aprovado, reprovado, andamento, null\n");
    printf("                                 Use --status=null para remover status e deixar o item branco\n");
    printf("                                 --ag sempre cria itens sem status explícito (status=null/branco)\n");
    printf("                                 Se --status for informado junto de --ag, ele é ignorado na criação\n");
    printf("                                 Itens sem status sao exibidos em branco normal\n");
    printf("  --dg=<id>                      Deleta o galho e todos os sub-galhos\n");
    printf("  --decepar-galho=<id>           Alias detalhado de --dg=<id>\n");
    printf("  --podar --em=<id>             Remove todos os sub-galhos do item em --em, mantendo o item\n");
    printf("  --de=<id> --para=<id>          Move um galho para outra posição\n");
    printf("  --replace=[x][y] --em=<id>     Substitui x por y no item\n");
    printf("  --replace=[x][y] --de=<id> --ate=<id>\n");
    printf("                                 Substitui x por y em intervalo exibido\n");
    printf("  --replace=[+]texto --em=<id>   Acrescenta texto ao final do nome do item\n");
    printf("  --reorganizar                  Regrava a árvore e compacta/renumera IDs\n");
    printf("  Exemplo com todas as flags do grupo (combinações lógicas):\n");
    printf("    pp --sa=app --ag=backend --em=2.1  # criação e posicionamento\n");
    printf("    pp --sa=app --eg='novo nome' --em=2.1  # renomeação\n");
    printf("    pp --sa=app --de=1 --para=2.1  # movimentação\n");
    printf("    pp --sa=app --replace=[a][b] --em=2.1  # substituição pontual\n");
    printf("    pp --sa=app --replace=[a][b] --de=2.1 --ate=2.5  # substituição em intervalo\n");
    printf("    pp --sa=app --dg=2.1  # remoção (use sozinho)\n");
    printf("    pp --sa=app --podar --em=2  # remove todos os sub-galhos do item 2\n");
    printf("    pp --sa=app --reorganizar  # reorganização geral\n");
    printf("    pp --sa=app --status=aprovado --em=2.1  # define status no item e filhos\n");
    printf("    pp --sa=app --status=null --em=2.1  # remove status e deixa branco\n");
    printf("    pp --sa=app --ag=item --status=andamento  # cria sem status; altere depois com --status --em=<id>\n");
    printf("\n");

    printf("3) Exibição/recolhimento [exibicao recolhimento]\n");
    printf("  --recolher=<id>                Mostra o item sem seus sub-galhos\n");
    printf("  --recolher=total               Recolhe todos os itens\n");
    printf("  --expandir=<id>                Volta a exibir sub-galhos de um item\n");
    printf("  --expandir=total               Expande todos os itens\n");
    printf("  Exemplo com todas as flags do grupo (--recolher e --expandir são mutuamente exclusivos):\n");
    printf("    pp --sa=app --recolher=2.1  # recolhe um item\n");
    printf("    pp --sa=app --expandir=total  # expande tudo\n");
    printf("\n");

    printf("4) Relações entre galhos [relacoes rel galhos]\n");
    printf("  --rel=<id> --em=<id>           Faz o item em --em apontar para outro item\n");
    printf("  --drel=<id> --em=<id>          Remove do item em --em a relação com o alvo\n");
    printf("  Exemplo com todas as flags do grupo (criar e remover relação são ações distintas):\n");
    printf("    pp --sa=app --rel=1 --em=2.1  # cria relação\n");
    printf("    pp --sa=app --drel=1 --em=2.1  # remove relação\n");
    printf("\n");

    printf("5) Valores e consolidação [valores consolidacao]\n");
    printf("  --vlr=<valor> --em=<id>        Define receita/valor positivo; vazio remove\n");
    printf("  --vld=<valor> --em=<id>        Define despesa/valor negativo; vazio remove\n");
    printf("  --vlt=<valor> --clt=<origem,destino> --em=<id>\n");
    printf("                                 Define transferência; use 0 como mundo externo (ex: 1.1.1,0 ou 0,1.1.1)\n");
    printf("  --cv=<id1,id2> --em=<id>       Vincula o item em --em a dois consolidados\n");
    printf("  --config-vin=<id1,id2>         Alias detalhado de --cv=<id1,id2>\n");
    printf("  --dcv=<id> --em=<id>           Remove um vínculo específico de consolidação\n");
    printf("  Exibição: [fd/fr/ft] são totais dos filhos diretos; [td/tr/tt] são totais vinculados; [sd] é saldo.\n");
    printf("  Exemplo com todas as flags do grupo (--vlr/--vld e --cv/--dcv são mutuamente exclusivos):\n");
    printf("    pp --sa=fin --em=4.1 --vlr=100  # receita\n");
    printf("    pp --sa=fin --em=4.1 --vld=50  # despesa\n");
    printf("    pp --sa=fin --em=4.1 --vlt=100 --clt=1.1.1,1.1.2  # transferência\n");
    printf("    pp --sa=fin --em=2.1.1 --cv=2.1,1.1.1  # vínculo de consolidação\n");
    printf("    pp --sa=fin --em=2.1.1 --dcv=2.1  # remove vínculo\n");
    printf("\n");

    printf("6) Grupos de comentário (GC), mensagens e execução [gc mensagens execucao]\n");
    printf("  --gc=<nome> --em=<id>          Nomeia o GC do item\n");
    printf("  --mm=<texto> --em=<id>         Adiciona mensagem do usuário ao item\n");
    printf("  --mm=<texto> --em=<id1,id2>    Adiciona a mesma mensagem em vários itens\n");
    printf("  --itens=<ids> --em=<id>        Lista itens relacionados ao GC, separados por vírgula\n");
    printf("  --gc=<nome> --mm=<texto>       Sem --em, adiciona mensagem ao item que já tem esse GC\n");
    printf("  --lgc                         Remove todos os GCs, mensagens e itens relacionados\n");
    printf("  --lgc=<nome>                  Remove apenas o GC informado\n");
    printf("  --prompt=<gc>                 Antes de executar, roda pp --push-github=%s\n", SA_CHECKPOINT_GITHUB_CONTA);
    printf("                                 com commit checkpoint-sa e registra a reversão em arvores/reversao-gc.json\n");
    printf("                                 Depois monta prompt determinístico do GC diretamente pelo sa,\n");
    printf("                                 executa no --parallm padrão com HEB/HRB e aciona agente-at\n");
    printf("                                 automaticamente para aplicar o plano\n");
    printf("  --prompt=<gc> --rel           Compara sem/com HEB/HRB e aciona agente-at só na execução padrão\n");
    printf("  --conta-llm=<conta.json>      Flag global do pp; sobrescreve a conta LLM nessa execução\n");
    printf("  --reverter=<gc>               Reverte o sistema para o commitAntes salvo no último checkpoint do GC\n");
    printf("  Limite: máximo de 1 GC por árvore. Reutilize o mesmo --gc ou remova com --lgc antes de criar outro. Cor: âmbar brilhante.\n");
    printf("  Exemplo com todas as flags do grupo (--gc/--mm/--itens/--em criam; --prompt executa; --lgc limpa; --reverter é standalone):\n");
    printf("    pp --sa=contexto --gc=rev1 --mm='mensagem' --em=7.7 --itens=7.6.2,7.5.1  # cria GC com mensagem e itens\n");
    printf("    pp --sa=contexto --gc=rev1 --mm='outra msg'  # adiciona msg a GC existente\n");
    printf("    pp --sa=contexto --prompt=rev1 --rel --conta-llm=minha-conta.json  # executa prompt do GC\n");
    printf("    pp --sa=contexto --lgc=rev1  # limpa um GC específico\n");
    printf("    pp --sa=contexto --reverter=rev1  # reverte checkpoint (use sozinho)\n");
    printf("\n");

    printf("7) Referências a arquivos do sistema [referencias arquivos ra]\n");
    printf("  --ra=<paths> --em=<id>         Associa referências de arquivos ao item\n");
    printf("                                 Paths devem começar com sisc/ e podem ser separados por vírgula\n");
    printf("  --dra --em=<id>                Remove todas as referências de arquivo do item\n");
    printf("  --dra=<path> --em=<id>         Remove somente a referência informada\n");
    printf("  Exemplo com todas as flags do grupo (--ra e --dra são mutuamente exclusivos):\n");
    printf("    pp --sa=app --ra=sisc/arq1.txt,sisc/core/arq2.txt --em=2.1  # associa referências\n");
    printf("    pp --sa=app --dra=sisc/arq1.txt --em=2.1  # remove uma referência\n");
    printf("    pp --sa=app --dra --em=2.1  # remove todas as referências\n");
    printf("\n");

    printf("8) Legenda fixa de cores [legenda cores]\n");
    printf("  --legenda                      Exibe esta legenda como tabela colorida e sai\n");
    printf("  Nome da árvore/tronco          Branco\n");
    printf("  ID de item com filhos          Ciano\n");
    printf("  ID de item sem filhos          Amarelo\n");
    printf("  Texto de item sem status       Branco normal\n");
    printf("  GC único / mensagem do GC      Âmbar brilhante\n");
    printf("  Prefixo 'gc:' antes da mm      Branco brilhante\n");
    printf("  Referência [sisc/arquivo]      Verde-limão brilhante\n");
    printf("  [rel], [vin] e [clt]           Ciano\n");
    printf("  [valor], [vlt] e [td/tr/tt/sd] Magenta\n");
    printf("  [fd/fr/ft/sd]                  Amarelo\n");
    printf("  Item criado/editado            Rosa choque\n");
    printf("  [recolhido]                    Cinza\n");
    printf("\n");

    printf("9) Procura na árvore [procura proc buscar busca search pesquisar]\n");
    printf("  --proc=<termo>                Destaca na exibição todos os itens cujo nome\n");
    printf("                                 (ou campos: mm, gc, rel, valor, itens, ra)\n");
    printf("                                 contenham o termo informado (case-insensitive).\n");
    printf("                                 Itens com correspondência aparecem em rosa choque.\n");
    printf("  --lproc                       Limpa o filtro de procura ativo.\n");
    printf("  Exemplo com todas as flags do grupo:\n");
    printf("    pp --sa=app --proc=backend          # destaca itens com 'backend'\n");
    printf("    pp --sa=app --proc=login --json     # procura + saída JSON (sem destaque)\n");
    printf("    pp --sa=app --lproc                 # limpa filtro e exibe normalmente\n");
    printf("\n");

    printf("Exemplos rápidos por grupo:\n");
    printf("  # listar/exibir\n");
    printf("  pp --sa\n");
    printf("  pp --sa=app\n");
    printf("  pp --sa=app --json\n");
    printf("\n");
    printf("  # criar/manipular estrutura\n");
    printf("  pp --sa=estrutura-atual --dir  # cria árvore recursiva do diretório atual\n");
    printf("  pp --sa=app --ag=backend\n");
    printf("  pp --sa=app --ag=login --em=2.1\n");
    printf("  pp --sa=app --eg='novo nome' --em=2.1\n");
    printf("  pp --sa=app --de=1 --para=2.1\n");
    printf("  pp --sa=app --dg=2.1\n");
    printf("  pp --sa=app --podar --em=2\n");
    printf("\n");
    printf("  # relações e valores\n");
    printf("  pp --sa=app --rel=1 --em=2.1\n");
    printf("  pp --sa=app --drel=1 --em=2.1\n");
    printf("  pp --sa=app --vlr=42 --em=2.1\n");
    printf("  pp --sa=app --vld=42 --em=2.1\n");
    printf("  pp --sa=fin --em=4.1 --vlt=100 --clt=1.1.1,1.1.2\n");
    printf("  pp --sa=fin --em=4.1 --vlt=100 --clt=1.1.1,0   # saída para mundo externo\n");
    printf("  pp --sa=fin --em=4.1 --vlt=100 --clt=0,1.1.1   # entrada do mundo externo\n");
    printf("  pp --sa=app --cv=2.1,1.1.1 --em=2.1.1\n");
    printf("  pp --sa=app --dcv=2.1 --em=2.1.1\n");
    printf("\n");
    printf("  # GC, mensagem e prompt LLM\n");
    printf("  pp --sa=contexto --gc=rev1 --mm='mensagem' --em=7.7 --itens=7.6.2,7.5.1\n");
    printf("  pp --sa=contexto --gc=rev1 --mm='mensagem' --em=2.5,3.4,4.2 --itens=5.6,6.8\n");
    printf("  pp --sa=contexto --gc=rev1 --mm='outra msg'\n");
    printf("  pp --sa=contexto --prompt=rev1\n");
    printf("  pp --sa=contexto --prompt=rev1 --rel\n");
    printf("  pp --sa=contexto --reverter=rev1\n");
    printf("  pp --sa=contexto --prompt=rev1 --conta-llm=minha-conta.json\n");
    printf("\n");
    printf("  # referências de arquivos\n");
    printf("  pp --sa=app --ra=sisc/1arq.txt,sisc/core/2arq.txt --em=2.1\n");
    printf("  pp --sa=app --dra=sisc/1arq.txt --em=2.1\n");
    printf("  pp --sa=app --dra --em=2.1\n");
    printf("\n");
    printf("  # legenda\n");
    printf("  pp --sa --legenda\n");
    printf("\n");
    printf("  # procura na árvore\n");
    printf("  pp --sa=app --proc=backend\n");
    printf("  pp --sa=app --proc=login     # destaca itens que contenham 'login'\n");
    printf("  pp --sa=app --lproc          # limpa procura ativa\n");
    printf("\n");

    printf("10) Mapa do sistema [mapa sistema diretorio arquivos]\n");
    printf("  pp --atualizar-manual-funcional           Comando principal para revarrer e atualizar o contexto vivo do sistema\n");
    printf("  --mapa                         Alias legado de pp --sa para pp --atualizar-manual-funcional\n");
    printf("  A saída canônica é sempre sisc/arvores/operador-manual-funcional.json:\n");
    printf("    pp --sa --mapa               → sisc/arvores/operador-manual-funcional.json\n");
    printf("    pp --sa=meu-sistema --mapa    → sisc/arvores/operador-manual-funcional.json (nome legado ignorado)\n");
    printf("  O mapa descreve atores, processos, artefatos e integrações; não é inventário estrutural.\n");
    printf("  Material de comandos fica fora do mapa; consulte pp --<comando> --ajuda.\n");
    printf("  Demais opcoes (--exceto=, --atualizar, --incluir-*, --max-prof=N)\n");
    printf("  sao repassadas ao pp --atualizar-manual-funcional durante a geração. A regeneração agora é sempre\n");
    printf("  executada para manter as árvores como fonte atualizada de contexto da LLM.\n");
    printf("  Exemplo com todas as flags do grupo:\n");
    printf("    pp --atualizar-manual-funcional --exceto=.,.json --incluir-codigo\n");
    printf("    pp --atualizar-manual-funcional --sistema=siscore --atualizar --exceto=.,.json --max-prof=3\n");
    printf("    pp --sa --mapa --json        # alias legado; saida JSON do mapa\n");
}

int main(int argc, char *argv[])
{
    char *nome = NULL, *add = NULL, *edit = NULL, *rel = NULL, *del_rel = NULL, *valor = NULL, *valor_transf = NULL, *contas_transf = NULL, *config_vin = NULL, *del_config_vin = NULL, *replace = NULL, *em = NULL, *decepar = NULL, *poder = NULL, *de = NULL, *para = NULL, *ate = NULL, *recolher = NULL, *expandir = NULL;
    char *gc = NULL, *mm = NULL, *itens = NULL, *ra_ref = NULL, *dra_ref = NULL, *prompt_gc_nome = NULL, *reverter_gc = NULL, *status_flag = NULL;
    int reorganizar = 0, json_out = 0, ajuda_flag = 0, legenda_flag = 0, valor_sinal = 0, valor_flags = 0, valor_transf_flag = 0, contas_transf_flag = 0, dra_flag = 0, prompt_relatorio = 0;
    char *proc = NULL;
    int lproc = 0;
    char *lgc = NULL;
    int mapa_flag = 0;
    int mapa_atualizar_flag = 0;
    int dir_flag = 0;

    for (int i = 1; i < argc; i++) {
        if (strncmp(argv[i], "--ag=", 5) == 0) add = argv[i] + 5;
        else if (strncmp(argv[i], "--eg=", 5) == 0) edit = argv[i] + 5;
        else if (strcmp(argv[i], "--cor") == 0 || strncmp(argv[i], "--cor=", 6) == 0) {
            fprintf(stderr, "sa: --cor foi removido; o esquema de cores das árvores é fixo.\n");
            return 1;
        }
        else if (strcmp(argv[i], "--rel") == 0) prompt_relatorio = 1;
        else if (strncmp(argv[i], "--rel=", 6) == 0) rel = argv[i] + 6;
        else if (strncmp(argv[i], "--drel=", 7) == 0) del_rel = argv[i] + 7;
        else if (strncmp(argv[i], "--vlr=", 6) == 0) { valor = argv[i] + 6; valor_sinal = 1; valor_flags++; }
        else if (strncmp(argv[i], "--vld=", 6) == 0) { valor = argv[i] + 6; valor_sinal = -1; valor_flags++; }
        else if (strcmp(argv[i], "--vlt") == 0 || strcmp(argv[i], "--clt") == 0) { fprintf(stderr, "sa: use --vlt=<valor> e --clt=<origem,destino>.\n"); return 1; }
        else if (strncmp(argv[i], "--vlt=", 6) == 0) { valor_transf = argv[i] + 6; valor_transf_flag = 1; }
        else if (strncmp(argv[i], "--clt=", 6) == 0) { contas_transf = argv[i] + 6; contas_transf_flag = 1; }
        else if (strncmp(argv[i], "--cv=", 5) == 0) config_vin = argv[i] + 5;
        else if (strncmp(argv[i], "--config-vin=", 13) == 0) config_vin = argv[i] + 13;
        else if (strncmp(argv[i], "--dcv=", 6) == 0) del_config_vin = argv[i] + 6;
        else if (strncmp(argv[i], "--replace=", 10) == 0) replace = argv[i] + 10;
        else if (strncmp(argv[i], "--em=", 5) == 0) em = argv[i] + 5;
        else if (strncmp(argv[i], "--decepar-galho=", 17) == 0) decepar = argv[i] + 17;
        else if (strncmp(argv[i], "--dg=", 5) == 0) decepar = argv[i] + 5;
        else if (strcmp(argv[i], "--podar") == 0) poder = "1";
        else if (strncmp(argv[i], "--de=", 5) == 0) de = argv[i] + 5;
        else if (strncmp(argv[i], "--para=", 7) == 0) para = argv[i] + 7;
        else if (strncmp(argv[i], "--ate=", 6) == 0) ate = argv[i] + 6;
        else if (strncmp(argv[i], "--recolher=", 11) == 0) recolher = argv[i] + 11;
        else if (strncmp(argv[i], "--expandir=", 11) == 0) expandir = argv[i] + 11;
        else if (strcmp(argv[i], "--reorganizar") == 0) reorganizar = 1;
        else if (strcmp(argv[i], "--json") == 0) json_out = 1;
        else if (strcmp(argv[i], "--dir") == 0) dir_flag = 1;
        else if (strcmp(argv[i], "--legenda") == 0) legenda_flag = 1;
        else if (strcmp(argv[i], "--ajuda") == 0 || strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) ajuda_flag = 1;
        else if (strcmp(argv[i], "--lgc") == 0) lgc = "";
        else if (strncmp(argv[i], "--lgc=", 6) == 0) lgc = argv[i] + 6;
        else if (strncmp(argv[i], "--gc=", 5) == 0) gc = argv[i] + 5;
        else if (strncmp(argv[i], "--mm=", 5) == 0) mm = argv[i] + 5;
        else if (strncmp(argv[i], "--itens=", 8) == 0) itens = argv[i] + 8;
        else if (strncmp(argv[i], "--ra=", 5) == 0) ra_ref = argv[i] + 5;
        else if (strcmp(argv[i], "--dra") == 0) { dra_flag = 1; dra_ref = ""; }
        else if (strncmp(argv[i], "--dra=", 6) == 0) { dra_flag = 1; dra_ref = argv[i] + 6; }
        else if (strncmp(argv[i], "--prompt=", 9) == 0) prompt_gc_nome = argv[i] + 9;
        else if (strncmp(argv[i], "--enviar-arvore", 15) == 0) { fprintf(stderr, "sa: --enviar-arvore foi removido.\n"); return 1; }
        else if (strncmp(argv[i], "--reverter=", 11) == 0) reverter_gc = argv[i] + 11;
        else if (strncmp(argv[i], "--proc=", 7) == 0) proc = argv[i] + 7;
        else if (strcmp(argv[i], "--lproc") == 0) lproc = 1;
        else if (strncmp(argv[i], "--status=", 9) == 0) status_flag = argv[i] + 9;
        else if (strcmp(argv[i], "--mapa") == 0) mapa_flag = 1;
        else if (strncmp(argv[i], "--mapa=", 7) == 0) mapa_flag = 1;
        else if (strcmp(argv[i], "--atualizar") == 0 || strcmp(argv[i], "--recriar") == 0) mapa_atualizar_flag = 1;
        else if (argv[i][0] != '-' && !nome) nome = argv[i];
    }

    if (add && sa_normalizar_quebras_item_inplace(add)) {
        fprintf(stderr, "sa: quebras de linha em --ag foram convertidas para espaços; quebra de linha não cria item novo.\n");
    }
    if (edit && sa_normalizar_quebras_item_inplace(edit)) {
        fprintf(stderr, "sa: quebras de linha em --eg foram convertidas para espaços; quebra de linha não cria item novo.\n");
    }

    if (ajuda_flag) {
        if (nome || add || edit || rel || del_rel || valor || valor_transf || contas_transf || config_vin || del_config_vin ||
            replace || em || decepar || poder || de || para || ate || recolher || expandir || reorganizar || json_out || dir_flag || legenda_flag ||
            valor_sinal || valor_flags || valor_transf_flag || contas_transf_flag || dra_flag || prompt_relatorio || lproc || lgc ||
            gc || mm || itens || ra_ref || dra_ref || prompt_gc_nome || reverter_gc || status_flag || mapa_flag || mapa_atualizar_flag) {
            fprintf(stderr, "sa: quando --ajuda é usado, somente --proc=<termo> pode ser combinado.\n");
            return 1;
        }
        ajuda();
        return 0;
    }
    if (legenda_flag) { imprimir_legenda(); return 0; }

    /* --mapa agora é apenas um alias legado: a implementação vive em atualizar-manual-funcional. */
    if (mapa_flag) return executar_atualizar_manual_funcional_alias(argc, argv);

    /* --proc / --lproc ativam/limpam o filtro global antes do carregamento */
    if (proc && proc[0] && lproc) { fprintf(stderr, "sa: use apenas --proc ou --lproc, não ambos.\n"); return 1; }
    if (lproc) { free(g_proc_termo); g_proc_termo = NULL; }
    if (proc && proc[0]) { free(g_proc_termo); g_proc_termo = strdup(proc); }
    if (valor_flags > 1) { fprintf(stderr, "sa: use apenas uma das flags: --vlr ou --vld.\n"); return 1; }
    if (valor_transf_flag != contas_transf_flag) { fprintf(stderr, "sa: --vlt e --clt devem ser especificadas juntas (ex: --vlt=100 --clt=1.1.1,1.1.2).\n"); return 1; }
    if (valor_transf_flag) {
        long long vt = 0;
        if (!valor_para_centavos(valor_transf, &vt) || vt <= 0) { fprintf(stderr, "sa: --vlt exige valor de transferência maior que zero.\n"); return 1; }
    }
    if (config_vin && del_config_vin) { fprintf(stderr, "sa: use apenas uma das flags: --cv ou --dcv.\n"); return 1; }
    if (ra_ref != NULL && dra_flag) { fprintf(stderr, "sa: use apenas uma das flags: --ra ou --dra.\n"); return 1; }
    if (prompt_relatorio && !prompt_gc_nome) { fprintf(stderr, "sa: --rel sem valor deve ser usado com --prompt; para relação entre itens use --rel=<id>.\n"); return 1; }
    if (prompt_gc_nome && !*prompt_gc_nome) { fprintf(stderr, "sa: --prompt exige o nome de um GC.\n"); return 1; }
    if (reverter_gc && !*reverter_gc) { fprintf(stderr, "sa: --reverter exige o nome de um GC.\n"); return 1; }
    if (reverter_gc && (add || edit || rel || del_rel || valor || valor_transf_flag || contas_transf_flag || config_vin || del_config_vin || replace || em || decepar || poder || de || para || ate || recolher || expandir || reorganizar || json_out || dir_flag || gc || mm || itens || ra_ref || dra_flag || prompt_gc_nome || lgc || status_flag)) { fprintf(stderr, "sa: --reverter=<gc> deve ser usado sozinho com --sa=<árvore>.\n"); return 1; }

    if (status_flag && !status_valido(status_flag)) {
        fprintf(stderr, "sa: --status aceita apenas: aprovado, reprovado, andamento, null.\n");
        return 1;
    }
    if (status_flag && (!em || !*em) && !add) {
        fprintf(stderr, "sa: --status exige --em=<id> com o caminho do item alvo.\n");
        return 1;
    }

    char raiz[PATH_MAX];
    if (raiz_projeto(raiz, sizeof(raiz)) != 0) { fprintf(stderr, "sa: não foi possível detectar raiz.\n"); return 1; }
    char dir[PATH_MAX * 2];
    snprintf(dir, sizeof(dir), "%s", raiz);

    /* sem nome de arvore: lista existentes ou erro se houver flags de acao */
    if (!nome || !*nome) {
        if (add || edit || rel || del_rel || valor || valor_transf_flag || contas_transf_flag || config_vin || del_config_vin || replace || em || decepar || poder || de || para || ate || recolher || expandir || reorganizar || json_out || dir_flag || gc || mm || itens || ra_ref || dra_flag || prompt_gc_nome || reverter_gc || lgc) {
            fprintf(stderr, "sa: nome da arvore obrigatorio quando se usa flags de manipulacao.\n");
            return 1;
        }
        listar_arvores(dir);
        return 0;
    }
    char path[PATH_MAX * 4];
    char *norm = NULL;
    if (strchr(nome, '/')) {
        /* caminho relativo ao diretório atual, ou absoluto quando começa com '/' */
        const char *suf = termina_com_json(nome) ? "" : ".json";
        if (nome[0] == '/') snprintf(path, sizeof(path), "%s%s", nome, suf);
        else snprintf(path, sizeof(path), "%s/%s%s", raiz, nome, suf);
    } else {
        /* árvore no diretório atual */
        norm = normalizar_nome(nome);
        const char *suf = termina_com_json(norm) ? "" : ".json";
        snprintf(path, sizeof(path), "%s/%s%s", dir, norm, suf);
        if (norm && strcmp(norm, "mapa-comandos") == 0 && access(path, F_OK) != 0) {
            snprintf(path, sizeof(path), "%s/core/config/mapa-comandos.json", raiz);
        }
    }

    if (dir_flag) {
        if (add || edit || rel || del_rel || valor || valor_transf_flag || contas_transf_flag || config_vin || del_config_vin ||
            replace || em || decepar || poder || de || para || ate || recolher || expandir || reorganizar || gc || mm || itens ||
            ra_ref || dra_flag || prompt_gc_nome || reverter_gc || lgc || status_flag || proc || lproc || mapa_atualizar_flag) {
            fprintf(stderr, "sa: --dir deve ser usado apenas com --sa=<árvore> e opcionalmente --json.\n");
            free(norm);
            return 1;
        }
        char *ref_path = NULL;
        const char *nome_final = norm;
        if (!nome_final) {
            ref_path = nome_arvore_de_path(nome);
            nome_final = ref_path ? ref_path : nome;
        }
        int rd = criar_arvore_do_diretorio_atual(path, nome_final, json_out);
        free(ref_path);
        free(norm);
        return rd;
    }

    if (reverter_gc) {
        char *ref_path = NULL;
        const char *ref = norm;
        if (!ref) {
            ref_path = nome_arvore_de_path(nome);
            ref = ref_path ? ref_path : nome;
        }
        int rr = sa_reverter_checkpoint_gc(raiz, ref, reverter_gc);
        free(ref_path);
        free(norm);
        return rr;
    }

    Arvore arv;
    int nome_corrigido = 0;
    int arquivo_existe = (access(path, F_OK) == 0);
    int existe = (arquivo_existe && carregar_arvore(path, &arv, &nome_corrigido) == 0);
    if (arquivo_existe && !existe) {
        fprintf(stderr, "sa: o arquivo já existe, mas não é uma árvore SA válida (exige \"tipo\": \"arvore-sa\"): %s\n", path);
        fprintf(stderr, "sa: operação cancelada para não sobrescrever ou destruir arquivo que não pertence ao pp --sa.\n");
        free(norm);
        return 1;
    }
    if (existe && nome_corrigido) salvar_arvore(path, &arv);

    if (lgc) {
        if (!existe) { fprintf(stderr, "Árvore '%s' não encontrada.\n", nome); free(norm); return 1; }
        if (lgc[0]) {
            /* --lgc=NOME: limpa apenas o gc com aquele nome */
            no_limpar_gc_por_nome_rec(&arv.raiz, lgc, &arv.raiz);
            printf("GC '%s' limpo da árvore '%s'. Itens desse gc voltaram à cor normal.\n\n", lgc, nome);
        } else {
            /* --lgc (sem valor): limpa todos */
            no_limpar_gc_rec(&arv.raiz);
            printf("GC limpo da árvore '%s'. Todos os itens voltaram à cor normal.\n\n", nome);
        }
        if (arvore_aplicar_cores_gcs(&arv, NULL) != 0) { erro_limite_gcs(); arvore_liberar(&arv); free(norm); return 1; }
        salvar_arvore(path, &arv);
        exibir_arvore(&arv);
        int pr = sa_prompt_executar(&arv, prompt_gc_nome, prompt_relatorio);
        arvore_liberar(&arv); free(norm); return pr;
    }

    if (existe && arvore_aplicar_cores_gcs(&arv, NULL) != 0) {
        erro_limite_gcs();
        arvore_liberar(&arv); free(norm); return 1;
    }

    if (recolher && expandir) {
        fprintf(stderr, "Use apenas uma das flags: --recolher ou --expandir.\n");
        if (existe) arvore_liberar(&arv);
        free(norm); return 1;
    }

    if (recolher || expandir) {
        if (!existe) { fprintf(stderr, "Árvore '%s' não encontrada.\n", nome); free(norm); return 1; }
        const char *alvo = recolher ? recolher : expandir;
        int valor = recolher ? 1 : 0;
        int alterados = 0, total = 0;
        int r = definir_recolhido_alvo(&arv.raiz, alvo, valor, &alterados, &total);
        if (r != 0) {
            if (r == -2) fprintf(stderr, "Item não encontrado: %s.\n", alvo);
            else fprintf(stderr, "Alvo inválido para %s: use um ID (ex: 2.1) ou total.\n", recolher ? "--recolher" : "--expandir");
            arvore_liberar(&arv); free(norm); return 1;
        }
        salvar_arvore(path, &arv);
        printf("%s concluído em %s: %d item(ns) alterado(s) de %d.\n\n", recolher ? "Recolher" : "Expandir", alvo, alterados, total);
        exibir_arvore(&arv);
        int pr = sa_prompt_executar(&arv, prompt_gc_nome, prompt_relatorio);
        arvore_liberar(&arv); free(norm); return pr;
    }

    if (replace) {
        if (!existe) { fprintf(stderr, "Árvore '%s' não encontrada.\n", nome); free(norm); return 1; }
        if (!em && !de && !ate) {
            fprintf(stderr, "--replace exige pelo menos um alvo: --em=<id>, --de=<id> ou --ate=<id>.\n");
            arvore_liberar(&arv); free(norm); return 1;
        }
        if (em && (de || ate)) {
            fprintf(stderr, "--replace deve usar --em sozinho ou intervalo com --de/--ate.\n");
            arvore_liberar(&arv); free(norm); return 1;
        }
        char *de_txt = NULL, *para_txt = NULL;
        int adicionar = 0;
        if (parse_replace_arg(replace, &de_txt, &para_txt, &adicionar) != 0) {
            fprintf(stderr, "Formato inválido para --replace. Use: --replace=[texto_atual][texto_novo] ou --replace=[+]texto\n");
            arvore_liberar(&arv); free(norm); return 1;
        }
        int itens_alt = 0, ocorrencias = 0;
        int r;
        if (em) r = substituir_item(&arv.raiz, em, de_txt, para_txt, adicionar, &itens_alt, &ocorrencias);
        else r = substituir_intervalo(&arv.raiz, de, ate, de_txt, para_txt, adicionar, &itens_alt, &ocorrencias);
        if (r != 0) {
            if (r == -2) {
                if (em) fprintf(stderr, "Item não encontrado: %s.\n", em);
                else fprintf(stderr, "Intervalo inválido: item inicial ou final não encontrado (%s até %s).\n", de ? de : "início", ate ? ate : "fim");
            } else if (r == -3) fprintf(stderr, "Intervalo inválido: --de deve vir antes ou ser igual a --ate na ordem exibida da árvore.\n");
            else fprintf(stderr, "Falha ao executar --replace no alvo informado.\n");
            free(de_txt); free(para_txt); arvore_liberar(&arv); free(norm); return 1;
        }
        salvar_arvore(path, &arv);
        if (em) printf("Replace concluído em %s: %d ocorrência(s) em %d item(ns).\n\n", em, ocorrencias, itens_alt);
        else printf("Replace concluído de %s até %s: %d ocorrência(s) em %d item(ns).\n\n", de ? de : "início", ate ? ate : "fim", ocorrencias, itens_alt);
        free(de_txt); free(para_txt);
        exibir_arvore(&arv);
        int pr = sa_prompt_executar(&arv, prompt_gc_nome, prompt_relatorio);
        arvore_liberar(&arv); free(norm); return pr;
    }

    if (del_rel) {
        if (!existe) { fprintf(stderr, "Árvore '%s' não encontrada.\n", nome); free(norm); return 1; }
        if (!em || !caminho_valido(em)) { fprintf(stderr, "--drel exige --em=<id> com caminho válido do item que possui a relação.\n"); arvore_liberar(&arv); free(norm); return 1; }
        if (!caminho_valido(del_rel)) { fprintf(stderr, "Caminho inválido para --drel: %s\n", del_rel); arvore_liberar(&arv); free(norm); return 1; }
        No *src = encontrar_no(&arv.raiz, em, NULL, NULL);
        if (!src || src == &arv.raiz) { fprintf(stderr, "Galho informado em --em não encontrado: %s\n", em); arvore_liberar(&arv); free(norm); return 1; }
        No *dst = encontrar_no(&arv.raiz, del_rel, NULL, NULL);
        if (!dst || dst == &arv.raiz) { fprintf(stderr, "Galho informado em --drel não encontrado: %s\n", del_rel); arvore_liberar(&arv); free(norm); return 1; }
        if (!src->rel || !src->rel[0]) { fprintf(stderr, "O item %s não possui relação cadastrada.\n", em); arvore_liberar(&arv); free(norm); return 1; }
        char *rel_atual = caminho_de_no(&arv.raiz, dst);
        if (!rel_atual || !*rel_atual || strcmp(src->rel, rel_atual) != 0) {
            fprintf(stderr, "O item %s não está relacionado a %s; relação atual: %s.\n", em, del_rel, src->rel ? src->rel : "");
            free(rel_atual); arvore_liberar(&arv); free(norm); return 1;
        }
        free(rel_atual);
        free(src->rel);
        src->rel = NULL;
        salvar_arvore(path, &arv);
        printf("Relação removida: item %s não aponta mais para %s.\n\n", em, del_rel);
        exibir_arvore(&arv);
        int pr = sa_prompt_executar(&arv, prompt_gc_nome, prompt_relatorio);
        arvore_liberar(&arv); free(norm); return pr;
    }

    if (de && para) {
        if (!existe) { fprintf(stderr, "Árvore '%s' não encontrada.\n", nome); free(norm); return 1; }
        if (!caminho_valido(de) || !caminho_valido(para)) { fprintf(stderr, "Caminho de origem/destino inválido.\n"); arvore_liberar(&arv); free(norm); return 1; }
        No *pai = NULL; int idx = -1;
        No *orig = encontrar_no(&arv.raiz, de, &pai, &idx);
        if (!orig || !pai) { fprintf(stderr, "Galho de origem não encontrado: %s\n", de); arvore_liberar(&arv); free(norm); return 1; }
        RelMaps relmaps; relmaps_coletar(&arv.raiz, &relmaps);
        char *nome_mov = xstrdup(orig->nome);
        No *mov = no_remove_filho(pai, idx);

        char *dup = strdup(para), *lastdot = strrchr(dup, '.');
        char *pai_dest_caminho = NULL; int pos = 0;
        if (lastdot) { *lastdot = '\0'; pai_dest_caminho = dup; pos = atoi(lastdot + 1) - 1; }
        else { pai_dest_caminho = ""; pos = atoi(dup) - 1; }
        No *dest = encontrar_no(&arv.raiz, pai_dest_caminho, NULL, NULL);
        if (!dest || no_insert_filho(dest, pos, mov) != 0) {
            fprintf(stderr, "Falha ao mover galho de %s para %s.\n", de, para);
            no_liberar(mov); free(mov); relmaps_liberar(&relmaps); free(dup); free(nome_mov); arvore_liberar(&arv); free(norm); return 1;
        }
        free(dup);
        relmaps_aplicar(&arv.raiz, &relmaps);
        relmaps_liberar(&relmaps);
        char *mov_id = caminho_de_no(&arv.raiz, mov);
        salvar_arvore(path, &arv);
        printf("Galho '%s' movido de %s para \033[%sm%s\033[0m. IDs reorganizados.\n\n", nome_mov, de, SA_ITEM_DESTAQUE_ANSI, (mov_id && *mov_id) ? mov_id : para);
        free(nome_mov);
        g_item_destaque_ids = (mov_id && *mov_id) ? mov_id : para;
        exibir_arvore(&arv);
        g_item_destaque_ids = NULL;
        free(mov_id);
        int pr = sa_prompt_executar(&arv, prompt_gc_nome, prompt_relatorio);
        arvore_liberar(&arv); free(norm); return pr;
    }

    if (decepar) {
        if (!existe) { fprintf(stderr, "Árvore '%s' não encontrada.\n", nome); free(norm); return 1; }
        if (!caminho_valido(decepar)) { fprintf(stderr, "Caminho inválido: %s\n", decepar); arvore_liberar(&arv); free(norm); return 1; }
        No *pai = NULL; int idx = -1;
        No *no = encontrar_no(&arv.raiz, decepar, &pai, &idx);
        if (!no || !pai) { fprintf(stderr, "Galho não encontrado: %s\n", decepar); arvore_liberar(&arv); free(norm); return 1; }
        if (deletar_bloqueado_por_relacoes(&arv.raiz, no, decepar)) { arvore_liberar(&arv); free(norm); return 1; }
        RelMaps relmaps; relmaps_coletar(&arv.raiz, &relmaps);
        char *nome_rem = xstrdup(no->nome);
        int tarefas_removidas = sa_eh_arvore_reversao_gc(path, &arv) ? sa_tarefas_checkpoint_limpar_rec(raiz, no) : 0;
        No *rem = no_remove_filho(pai, idx);
        no_liberar(rem); free(rem);
        relmaps_aplicar(&arv.raiz, &relmaps);
        relmaps_liberar(&relmaps);
        salvar_arvore(path, &arv);
        printf("Galho '%s' (%s) deletado. IDs reorganizados automaticamente.\n", nome_rem, decepar);
        if (tarefas_removidas > 0) printf("Limpeza análoga em tarefas/sa-checkpoints: %d arquivo(s) removido(s).\n", tarefas_removidas);
        printf("\n");
        free(nome_rem);
        exibir_arvore(&arv);
        int pr = sa_prompt_executar(&arv, prompt_gc_nome, prompt_relatorio);
        arvore_liberar(&arv); free(norm); return pr;
    }

    if (poder) {
        if (!existe) { fprintf(stderr, "Árvore '%s' não encontrada.\n", nome); free(norm); return 1; }
        if (!em) { fprintf(stderr, "sa: --podar exige --em=<id> para indicar o galho pai.\n"); arvore_liberar(&arv); free(norm); return 1; }
        if (!caminho_valido(em)) { fprintf(stderr, "Caminho inválido: %s\n", em); arvore_liberar(&arv); free(norm); return 1; }
        No *pai_no = NULL; int idx_no = -1;
        No *no = encontrar_no(&arv.raiz, em, &pai_no, &idx_no);
        if (!no) { fprintf(stderr, "Galho não encontrado: %s\n", em); arvore_liberar(&arv); free(norm); return 1; }
        int removidos = no->total;
        for (int i = no->total - 1; i >= 0; i--) {
            No *filho = no->filhos[i];
            no_remove_filho(no, i);
            no_liberar(filho);
            free(filho);
        }
        salvar_arvore(path, &arv);
        printf("Poda concluída: %d galho(s) removido(s) de '%s' (%s).\n", removidos, no->nome, em);
        printf("\n");
        exibir_arvore(&arv);
        int pr = sa_prompt_executar(&arv, prompt_gc_nome, prompt_relatorio);
        arvore_liberar(&arv); free(norm); return pr;
    }

    if (add) {
        if (del_config_vin) { fprintf(stderr, "--dcv deve ser usado em item existente com --em, não junto com --ag.\n"); if (existe) arvore_liberar(&arv); free(norm); return 1; }
        if (ra_ref != NULL) { fprintf(stderr, "--ra deve ser usado em item existente com --em, não junto com --ag.\n"); if (existe) arvore_liberar(&arv); free(norm); return 1; }
        if (dra_flag) { fprintf(stderr, "--dra deve ser usado em item existente com --em, não junto com --ag.\n"); if (existe) arvore_liberar(&arv); free(norm); return 1; }
        if (!existe) {
            if (!arquivo_existe && !confirmar_criar_arquivo_arvore(nome, path)) {
                free(norm);
                return 1;
            }
            arvore_inicializar_vazia(&arv, norm ? norm : nome);
            existe = 1;
            arquivo_existe = 1;
        }
        if (em && !caminho_valido(em)) { fprintf(stderr, "Caminho do pai inválido: %s\n", em); arvore_liberar(&arv); free(norm); return 1; }
        No *rel_dst = NULL;
        if (rel && !rel_alvo_valido(&arv, rel, NULL, &rel_dst)) { fprintf(stderr, "Relação inválida: --rel deve apontar para outro galho existente da árvore atual.\n"); arvore_liberar(&arv); free(norm); return 1; }
        char cv_ids[2][256] = {{0}};
        No *config_dsts[2] = { NULL, NULL };
        if (config_vin) {
            if (!parse_cv_duplo(config_vin, cv_ids) || !cv_duplo_alvo_valido(&arv, cv_ids, NULL, config_dsts)) {
                fprintf(stderr, "Vínculo inválido: --cv exige exatamente dois IDs existentes separados por vírgula (ex: --cv=3.1.1.1,1.1.1).\n");
                arvore_liberar(&arv); free(norm); return 1;
            }
        }
        char clt_ids[2][256] = {{0}};
        No *clt_dsts[2] = { NULL, NULL };
        if (valor_transf_flag) {
            if (!parse_clt_duplo(contas_transf, clt_ids) || !clt_duplo_alvo_valido(&arv, clt_ids, NULL, clt_dsts)) {
                fprintf(stderr, "Transferência inválida: --clt exige exatamente duas contas/IDs existentes separados por vírgula (origem,destino); use 0 para mundo externo.\n");
                arvore_liberar(&arv); free(norm); return 1;
            }
        }
        RelMaps relmaps; relmaps_coletar(&arv.raiz, &relmaps);

        /* Interpreta --em como posição exata: "1.2" = pai "1", posição 2 (índice 1) */
        char *dup_em = NULL;
        char *pai_caminho = "";
        int em_sem_ponto = 0;
        int pos = -1;  /* -1 = append ao final */
        if (em && *em) {
            dup_em = strdup(em);
            char *lastdot = strrchr(dup_em, '.');
            if (lastdot) {
                *lastdot = '\0';
                pai_caminho = dup_em;
                pos = atoi(lastdot + 1) - 1;
                if (pos < 0) pos = 0;
            } else {
                /* sem ponto: --em=4 significa "adicionar como filho do nó 4" quando ele existe.
                 * Se o nó ainda não existe e o número é uma posição válida da raiz
                 * (ex.: raiz tem 2 filhos e --em=3), cria nessa posição da raiz. */
                em_sem_ponto = 1;
                pai_caminho = dup_em;
                pos = -1;
            }
        }

        No *pai = encontrar_no(&arv.raiz, pai_caminho, NULL, NULL);
        if (!pai && em_sem_ponto) {
            int pos_raiz = atoi(dup_em) - 1;
            if (pos_raiz >= 0 && pos_raiz <= arv.raiz.total) {
                pai = &arv.raiz;
                pai_caminho = "";
                pos = pos_raiz;
            }
        }
        if (!pai) { fprintf(stderr, "Falha ao adicionar galho '%s': nó pai não encontrado.\n", add); relmaps_liberar(&relmaps); free(dup_em); arvore_liberar(&arv); free(norm); return 1; }

        No *novo = no_criar(add);
        /* --ag sempre cria o item sem status explicito (status NULL/branco).
         * --status combinado com --ag e ignorado na criacao; para alterar, use
         * --status=<valor> --em=<id> depois que o galho existir. */
        free(novo->status);
        novo->status = NULL;
        free(novo->cor);
        novo->cor = NULL;
        if (gc && gc[0]) {
            free(novo->gc);
            novo->gc = xstrdup(gc);
        }
        if (mm && mm[0]) {
            if (no_prepend_mm(novo, mm) != 0) {
                fprintf(stderr, "sa: erro de memória.\n");
                relmaps_liberar(&relmaps); free(dup_em); no_liberar(novo); free(novo); arvore_liberar(&arv); free(norm); return 1;
            }
            /* item novo via --ag permanece com status NULL mesmo quando recebe mm/gc */
        }
        if (itens && itens[0]) {
            free(novo->itens);
            novo->itens = xstrdup(itens);
        }
        if (valor) {
            char *vnorm = valor_com_sinal(valor, valor_sinal);
            free(novo->valor);
            novo->valor = (vnorm && vnorm[0]) ? vnorm : NULL;
            if (vnorm && !vnorm[0]) free(vnorm);
        }
        if (valor_transf_flag) {
            char *vnorm = valor_com_sinal(valor_transf, 0);
            free(novo->vlt);
            novo->vlt = (vnorm && vnorm[0]) ? vnorm : NULL;
            if (vnorm && !vnorm[0]) free(vnorm);
        }
        if (pos >= 0 && pos <= pai->total) {
            if (no_insert_filho(pai, pos, novo) != 0) { fprintf(stderr, "Falha ao inserir galho '%s' na posição %d.\n", add, pos + 1); relmaps_liberar(&relmaps); free(dup_em); no_liberar(novo); free(novo); arvore_liberar(&arv); free(norm); return 1; }
        } else {
            if (no_add_filho(pai, novo) != 0) { fprintf(stderr, "Falha ao adicionar galho '%s'.\n", add); relmaps_liberar(&relmaps); free(dup_em); no_liberar(novo); free(novo); arvore_liberar(&arv); free(norm); return 1; }
            pos = pai->total - 1;
        }

        char novo_id[256];
        if (pai_caminho && *pai_caminho)
            snprintf(novo_id, sizeof(novo_id), "%s.%d", pai_caminho, pos + 1);
        else
            snprintf(novo_id, sizeof(novo_id), "%d", pos + 1);

        relmaps_aplicar(&arv.raiz, &relmaps);
        relmaps_liberar(&relmaps);
        if (rel_dst) {
            char *rel_atual = caminho_de_no(&arv.raiz, rel_dst);
            if (rel_atual && *rel_atual) { free(novo->rel); novo->rel = rel_atual; }
            else free(rel_atual);
        }
        if (config_vin) {
            for (int i = 0; i < 2; i++) {
                if (config_dsts[i] == novo) { fprintf(stderr, "Vínculo inválido: --cv não pode apontar para o próprio item.\n"); free(dup_em); arvore_liberar(&arv); free(norm); return 1; }
                char *vin_atual = caminho_de_no(&arv.raiz, config_dsts[i]);
                if (vin_atual && *vin_atual) {
                    char *lista = config_vin_adicionar(novo->config_vin, vin_atual);
                    free(novo->config_vin);
                    novo->config_vin = lista;
                }
                free(vin_atual);
            }
        }
        if (valor_transf_flag) {
            for (int i = 0; i < 2; i++) {
                if (clt_dsts[i] == novo) { fprintf(stderr, "Transferência inválida: --clt não pode apontar para o próprio item.\n"); free(dup_em); arvore_liberar(&arv); free(norm); return 1; }
                if (clt_adicionar_resolvido(&arv.raiz, &novo->clt, clt_ids[i], clt_dsts[i]) != 0) {
                    fprintf(stderr, "Falha ao gravar --clt.\n");
                    free(dup_em); arvore_liberar(&arv); free(norm); return 1;
                }
            }
        }

        free(dup_em);
        if (arvore_aplicar_cores_gcs(&arv, NULL) != 0) {
            erro_limite_gcs();
            arvore_liberar(&arv); free(norm); return 1;
        }
        salvar_arvore(path, &arv);
        printf("Galho '\033[%sm%s\033[0m' adicionado na posição %s.\n\n", SA_ITEM_DESTAQUE_ANSI, add, novo_id);
        g_item_destaque_ids = novo_id;
        exibir_arvore(&arv);
        g_item_destaque_ids = NULL;
        int pr = sa_prompt_executar(&arv, prompt_gc_nome, prompt_relatorio);
        arvore_liberar(&arv); free(norm); return pr;
    }

    if (gc && gc[0] && !em && (mm || itens) && ra_ref == NULL && !dra_flag) {
        /* gc sem em: procura o nó que já tem este gc */
        if (!existe) { fprintf(stderr, "Árvore '%s' não encontrada.\n", nome); free(norm); return 1; }
        No *no_gc = no_encontrar_por_gc_rec(&arv.raiz, gc);
        if (!no_gc) { fprintf(stderr, "gc '%s' não encontrado na árvore '%s'.\n", gc, nome); arvore_liberar(&arv); free(norm); return 1; }
        /* Prepend mensagem */
        if (no_prepend_mm(no_gc, mm) != 0) { fprintf(stderr, "sa: erro de memória.\n"); arvore_liberar(&arv); free(norm); return 1; }
        /* item que recebe mm de GC automaticamente fica em andamento (se --status nao foi passado) */
        if (!status_flag) {
            free(no_gc->status);
            no_gc->status = xstrdup("andamento");
        }
        if (itens && itens[0]) {
            limpar_cor_ids_sem_gc(&arv.raiz, no_gc->itens);
            free(no_gc->itens);
            no_gc->itens = xstrdup(itens);
        }
        if (arvore_aplicar_cores_gcs(&arv, NULL) != 0) { erro_limite_gcs(); arvore_liberar(&arv); free(norm); return 1; }
        salvar_arvore(path, &arv);
        char *gc_item_id = caminho_de_no(&arv.raiz, no_gc);
        if (gc_item_id && *gc_item_id)
            printf("Atualizado gc '%s' no item \033[%sm%s\033[0m.\n\n", gc, SA_ITEM_DESTAQUE_ANSI, gc_item_id);
        else
            printf("Atualizado gc '%s' no item.\n\n", gc);
        g_item_destaque_ids = gc_item_id;
        exibir_arvore(&arv);
        g_item_destaque_ids = NULL;
        free(gc_item_id);
        int pr = sa_prompt_executar(&arv, prompt_gc_nome, prompt_relatorio);
        arvore_liberar(&arv); free(norm); return pr;
    }

    if (em && strchr(em, ',') && mm && mm[0]) {
        if (edit || rel || valor || valor_transf_flag || contas_transf_flag || config_vin || del_config_vin || ra_ref != NULL || dra_flag || status_flag) {
            fprintf(stderr, "sa: --em com múltiplos IDs é permitido apenas com --mm e, opcionalmente, --gc/--itens.\n");
            if (existe) arvore_liberar(&arv);
            free(norm); return 1;
        }
        if (!existe) { fprintf(stderr, "Árvore '%s' não encontrada. Crie o primeiro galho com: pp --sa=%s --ag=<nome-do-galho>\n", nome, nome); free(norm); return 1; }
        char erro_lista[512];
        int total_mm = 0;
        if (aplicar_mm_em_lista(&arv.raiz, em, gc, mm, itens, &total_mm, erro_lista, sizeof(erro_lista)) != 0) {
            fprintf(stderr, "sa: %s.\n", erro_lista);
            arvore_liberar(&arv); free(norm); return 1;
        }
        if (arvore_aplicar_cores_gcs(&arv, NULL) != 0) { erro_limite_gcs(); arvore_liberar(&arv); free(norm); return 1; }
        salvar_arvore(path, &arv);
        printf("Mensagem adicionada a %d item(ns) em --em '%s'", total_mm, em);
        if (gc && gc[0]) printf(" com gc '%s'", gc);
        if (itens && itens[0]) printf(" e itens '%s'", itens);
        printf(".\n\n");
        g_item_destaque_ids = em;
        exibir_arvore(&arv);
        g_item_destaque_ids = NULL;
        int pr = sa_prompt_executar(&arv, prompt_gc_nome, prompt_relatorio);
        arvore_liberar(&arv); free(norm); return pr;
    }

    if (edit || rel || valor || valor_transf_flag || contas_transf_flag || config_vin || del_config_vin || gc || mm || itens || ra_ref != NULL || dra_flag || status_flag) {
        if (!existe) { fprintf(stderr, "Árvore '%s' não encontrada. Crie o primeiro galho com: pp --sa=%s --ag=<nome-do-galho>\n", nome, nome); free(norm); return 1; }
        if (!em || !caminho_valido(em)) { fprintf(stderr, "--em obrigatório e deve ser um caminho válido para esta operação.\n"); arvore_liberar(&arv); free(norm); return 1; }
        No *no = encontrar_no(&arv.raiz, em, NULL, NULL);
        if (!no || no == &arv.raiz) { fprintf(stderr, "Galho não encontrado na posição %s.\n", em); arvore_liberar(&arv); free(norm); return 1; }
        if (ra_ref != NULL) {
            char erro_ra[512];
            if (!ra_lista_valida(ra_ref, erro_ra, sizeof(erro_ra))) { fprintf(stderr, "sa: %s.\n", erro_ra); arvore_liberar(&arv); free(norm); return 1; }
        }
        if (dra_flag && dra_ref && dra_ref[0]) {
            char erro_ra[512];
            if (strchr(dra_ref, ',')) { fprintf(stderr, "sa: --dra aceita apenas um path por vez; use --dra sem valor para remover todos.\n"); arvore_liberar(&arv); free(norm); return 1; }
            if (!ra_lista_valida(dra_ref, erro_ra, sizeof(erro_ra))) { fprintf(stderr, "sa: %s.\n", erro_ra); arvore_liberar(&arv); free(norm); return 1; }
        }
        char clt_ids[2][256] = {{0}};
        No *clt_dsts[2] = { NULL, NULL };
        if (valor_transf_flag) {
            if (!parse_clt_duplo(contas_transf, clt_ids) || !clt_duplo_alvo_valido(&arv, clt_ids, no, clt_dsts)) {
                fprintf(stderr, "Transferência inválida: --clt exige exatamente duas contas/IDs existentes separados por vírgula (origem,destino), diferentes do item em --em; use 0 para mundo externo.\n");
                arvore_liberar(&arv); free(norm); return 1;
            }
        }
        char *nome_antigo = xstrdup(no->nome);
        if (edit) {
            free(no->nome);
            no->nome = xstrdup(edit);
        }
        if (gc && gc[0]) {
            free(no->gc);
            no->gc = xstrdup(gc);
        }
        int status_itens_alterados = 0;
        if (status_flag) {
            status_itens_alterados = no_definir_status_rec(no, status_flag);
        }
        if (no_prepend_mm(no, mm) != 0) { fprintf(stderr, "sa: erro de memória.\n"); free(nome_antigo); arvore_liberar(&arv); free(norm); return 1; }
        /* item que recebe mm de GC automaticamente fica em andamento (se --status nao foi passado) */
        if (mm && mm[0] && gc && gc[0] && !status_flag) {
            free(no->status);
            no->status = xstrdup("andamento");
        }
        if (itens && itens[0]) {
            limpar_cor_ids_sem_gc(&arv.raiz, no->itens);
            free(no->itens);
            no->itens = xstrdup(itens);
        }
        if (ra_ref != NULL) {
            free(no->ra);
            no->ra = ra_ref[0] ? xstrdup(ra_ref) : NULL;
        }
        if (dra_flag) {
            if (!dra_ref || !dra_ref[0]) {
                free(no->ra);
                no->ra = NULL;
            } else {
                char *lista = config_vin_remover(no->ra, dra_ref);
                free(no->ra);
                no->ra = (lista && lista[0]) ? lista : NULL;
                if (lista && !lista[0]) free(lista);
            }
        }
        if (rel) {
            No *rel_dst = NULL;
            if (!rel_alvo_valido(&arv, rel, no, &rel_dst)) { fprintf(stderr, "Relação inválida: --rel deve apontar para outro galho existente da árvore atual.\n"); free(nome_antigo); arvore_liberar(&arv); free(norm); return 1; }
            char *rel_atual = caminho_de_no(&arv.raiz, rel_dst);
            if (rel_atual && *rel_atual) { free(no->rel); no->rel = rel_atual; }
            else free(rel_atual);
        }
        if (valor) {
            char *vnorm = valor_com_sinal(valor, valor_sinal);
            free(no->valor);
            no->valor = (vnorm && vnorm[0]) ? vnorm : NULL;
            if (vnorm && !vnorm[0]) free(vnorm);
        }
        if (valor_transf_flag) {
            char *vnorm = valor_com_sinal(valor_transf, 0);
            free(no->vlt);
            no->vlt = (vnorm && vnorm[0]) ? vnorm : NULL;
            if (vnorm && !vnorm[0]) free(vnorm);
            free(no->clt);
            no->clt = NULL;
            for (int i = 0; i < 2; i++) {
                if (clt_adicionar_resolvido(&arv.raiz, &no->clt, clt_ids[i], clt_dsts[i]) != 0) {
                    fprintf(stderr, "Falha ao gravar --clt.\n");
                    free(nome_antigo); arvore_liberar(&arv); free(norm); return 1;
                }
            }
        }
        if (config_vin) {
            char cv_ids[2][256] = {{0}};
            No *config_dsts[2] = { NULL, NULL };
            if (!parse_cv_duplo(config_vin, cv_ids) || !cv_duplo_alvo_valido(&arv, cv_ids, no, config_dsts)) {
                fprintf(stderr, "Vínculo inválido: --cv exige exatamente dois IDs existentes separados por vírgula (ex: --cv=3.1.1.1,1.1.1).\n");
                free(nome_antigo); arvore_liberar(&arv); free(norm); return 1;
            }
            for (int i = 0; i < 2; i++) {
                char *vin_atual = caminho_de_no(&arv.raiz, config_dsts[i]);
                if (vin_atual && *vin_atual) {
                    char *lista = config_vin_adicionar(no->config_vin, vin_atual);
                    free(no->config_vin);
                    no->config_vin = lista;
                }
                free(vin_atual);
            }
        }
        if (del_config_vin) {
            if (!del_config_vin[0] || !caminho_valido(del_config_vin)) { fprintf(stderr, "--dcv exige um ID válido.\n"); free(nome_antigo); arvore_liberar(&arv); free(norm); return 1; }
            No *config_dst = NULL;
            if (!rel_alvo_valido(&arv, del_config_vin, no, &config_dst)) { fprintf(stderr, "Vínculo inválido: --dcv deve apontar para outro item existente da árvore atual.\n"); free(nome_antigo); arvore_liberar(&arv); free(norm); return 1; }
            char *vin_atual = caminho_de_no(&arv.raiz, config_dst);
            if (vin_atual && *vin_atual) {
                char *lista = config_vin_remover(no->config_vin, vin_atual);
                free(no->config_vin);
                no->config_vin = (lista && lista[0]) ? lista : NULL;
                if (lista && !lista[0]) free(lista);
            }
            free(vin_atual);
        }
        if (arvore_aplicar_cores_gcs(&arv, NULL) != 0) { erro_limite_gcs(); free(nome_antigo); arvore_liberar(&arv); free(norm); return 1; }

        salvar_arvore(path, &arv);
        if (edit) printf("Galho '%s' (%s) renomeado para '\033[%sm%s\033[0m'", nome_antigo, em, SA_ITEM_DESTAQUE_ANSI, edit);
        else printf("Galho '\033[%sm%s\033[0m' (%s) atualizado", SA_ITEM_DESTAQUE_ANSI, no->nome, em);
        if (gc && gc[0]) printf(" com gc '%s'", gc);
        if (status_flag) printf(" com status '%s' em %d item(ns)", status_flag, status_itens_alterados);
        if (mm && mm[0]) printf(" e mm '%s'", mm);
        if (itens && itens[0]) printf(" e itens '%s'", itens);
        if (ra_ref != NULL) printf(" e ra '%s'", no->ra && no->ra[0] ? no->ra : "");
        if (dra_flag) printf(" e dra '%s'", dra_ref && dra_ref[0] ? dra_ref : "todos");
        if (rel && no->rel && no->rel[0]) printf(" e relação '%s'", no->rel);
        if (valor) printf(" e valor '%s'", no->valor && no->valor[0] ? no->valor : "");
        if (valor_transf_flag) printf(" e transferência vlt '%s' clt '%s'", no->vlt && no->vlt[0] ? no->vlt : "", no->clt && no->clt[0] ? no->clt : "");
        if (config_vin) printf(" e vínculo de valor '%s'", no->config_vin && no->config_vin[0] ? no->config_vin : "");
        if (del_config_vin) printf(" e vínculo removido; vínculos atuais '%s'", no->config_vin && no->config_vin[0] ? no->config_vin : "");
        printf(".\n\n");
        free(nome_antigo);
        g_item_destaque_ids = em;
        exibir_arvore(&arv);
        g_item_destaque_ids = NULL;
        int pr = sa_prompt_executar(&arv, prompt_gc_nome, prompt_relatorio);
        arvore_liberar(&arv); free(norm); return pr;
    }

    if (reorganizar) {
        if (!existe) { fprintf(stderr, "Árvore '%s' não encontrada.\n", nome); free(norm); return 1; }
        salvar_arvore(path, &arv);
        printf("Árvore '%s' reorganizada. IDs renumerados automaticamente.\n\n", nome);
        exibir_arvore(&arv);
        int pr = sa_prompt_executar(&arv, prompt_gc_nome, prompt_relatorio);
        arvore_liberar(&arv); free(norm); return pr;
    }

    if (!existe) {
        if (!arquivo_existe && !prompt_gc_nome && confirmar_criar_arquivo_arvore(nome, path)) {
            arvore_inicializar_vazia(&arv, norm ? norm : nome);
            if (salvar_arvore(path, &arv) != 0) {
                fprintf(stderr, "sa: não foi possível criar o arquivo da árvore em %s.\n", path);
                arvore_liberar(&arv);
                free(norm);
                return 1;
            }
            existe = 1;
            arquivo_existe = 1;
            printf("Árvore '%s' criada em %s.\n\n", nome, path);
        } else {
            fprintf(stderr, "Árvore '%s' não encontrada. Galhos só são criados por --ag; use: pp --sa=%s --ag=<nome-do-galho>\n", nome, nome);
            free(norm);
            return 1;
        }
    }

    if (json_out) {
        char *out = arvore_json(&arv);
        if (out) { printf("%s", out); free(out); }
        int pr = sa_prompt_executar(&arv, prompt_gc_nome, prompt_relatorio);
        arvore_liberar(&arv); free(norm); return pr;
    }

    exibir_arvore(&arv);
    int pr = sa_prompt_executar(&arv, prompt_gc_nome, prompt_relatorio);
    arvore_liberar(&arv); free(norm); return pr;
}
