/*
 * sa-mapa - constrói/edita uma árvore SA funcional de sisc/ + core/ + o sistema atual
 *
 * Uso interno:
 *   invocado por pp --atualizar-manual-funcional [opções do mapa] [flags do --sa]
 *
 * A árvore canônica criada é sisc/arvores/operador-manual-funcional.json. Quando executado
 * dentro de um sistema, o mapa começa em sisc/ e exclui sistemas irmãos,
 * mantendo core/ e o sistema atual. O material de comandos e ajudas de comandos é excluído.
 * As demais flags são encaminhadas ao --sa.
 */

#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>

#define MAPA_MAX_AMOSTRA_ARQUIVO (128 * 1024)
#define MAPA_MAX_TRECHO 900
#define MAPA_MAX_SIMBOLOS 2200
#define MAPA_MAX_LINHA 512

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

typedef struct No {
    char *nome;
    char *ra;
    struct No **filhos;
    int total;
    int cap;
} No;

typedef struct {
    int incluir_ocultos;
    int incluir_ignorados;
    int incluir_secretos;
    int incluir_codigo;
    int max_prof; /* -1 = ilimitado */
    const char *nome_arvore;
    char raiz_sisc[PATH_MAX];
    char raiz_exec[PATH_MAX];
    char sistema_nome[PATH_MAX];
    char saida_dir[PATH_MAX * 2];
    char self_map_path[PATH_MAX * 4];
    char system_tree_path[PATH_MAX * 4]; /* legado: mantido apenas para ignorar autoscan do próprio mapa */
    char **ctx_ignore;       /* padrões carregados exclusivamente de sisc/.ignorecontexto */
    int ctx_ignore_count;
    char exceto_buf[1024];  /* --exceto=.,.json,... */
    char sistema_alvo[256]; /* --sistema=<nome>: só core + este dir */
    char sistema_dir[PATH_MAX]; /* diretório do sistema atual, onde a árvore é salva */
    char ra_base[PATH_MAX];     /* prefixo RA: sisc ou sisc/<sistema> */
} Cfg;

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_reserve(Str *s, size_t extra)
{
    if (!s || !s->buf) return -1;
    if (s->len + extra + 1 > s->cap) {
        while (s->len + extra + 1 > s->cap) s->cap *= 2;
        char *novo = realloc(s->buf, s->cap);
        if (!novo) return -1;
        s->buf = novo;
    }
    return 0;
}

static int str_add(Str *s, const char *txt)
{
    if (!txt) txt = "";
    size_t n = strlen(txt);
    if (str_reserve(s, n) != 0) return -1;
    memcpy(s->buf + s->len, txt, n + 1);
    s->len += n;
    return 0;
}

static int str_addn(Str *s, const char *txt, size_t n)
{
    if (!txt || n == 0) return 0;
    if (str_reserve(s, n) != 0) return -1;
    memcpy(s->buf + s->len, txt, n);
    s->len += n;
    s->buf[s->len] = '\0';
    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 strdup(s ? s : "");
}

static No *no_criar(const char *nome, const char *ra)
{
    No *n = calloc(1, sizeof(No));
    if (!n) return NULL;
    n->nome = xstrdup(nome);
    n->ra = (ra && *ra) ? xstrdup(ra) : NULL;
    if (!n->nome || (ra && *ra && !n->ra)) { free(n->nome); free(n->ra); free(n); return NULL; }
    return n;
}

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

static void no_free(No *n)
{
    if (!n) return;
    for (int i = 0; i < n->total; i++) no_free(n->filhos[i]);
    free(n->filhos);
    free(n->nome);
    free(n->ra);
    free(n);
}

static int eh_diretorio(const char *caminho)
{
    struct stat st;
    return stat(caminho, &st) == 0 && S_ISDIR(st.st_mode);
}

static int eh_arquivo_regular(const char *caminho)
{
    struct stat st;
    return stat(caminho, &st) == 0 && S_ISREG(st.st_mode);
}

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, 0755) != 0 && errno != EEXIST) return -1;
    return (lstat(dir, &st) == 0 && !S_ISLNK(st.st_mode) && S_ISDIR(st.st_mode)) ? 0 : -1;
}

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 int mkdir_p(const char *path)
{
    if (!path || !*path) return -1;
    char tmp[PATH_MAX * 2];
    snprintf(tmp, sizeof(tmp), "%s", path);
    for (char *p = tmp + 1; *p; p++) {
        if (*p == '/') {
            *p = '\0';
            if (garantir_dir(tmp) != 0) return -1;
            *p = '/';
        }
    }
    return garantir_dir(tmp);
}

static int garantir_dir_pai(const char *path)
{
    char dir[PATH_MAX * 2];
    snprintf(dir, sizeof(dir), "%s", path ? path : "");
    char *slash = strrchr(dir, '/');
    if (!slash) return 0;
    if (slash == dir) slash[1] = '\0'; else *slash = '\0';
    return mkdir_p(dir);
}

static const char *base_name(const char *p)
{
    const char *s = p ? strrchr(p, '/') : NULL;
    return s ? s + 1 : (p ? p : "");
}

static int raiz_sisc_por_executavel(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';

    /* /.../sisc/core/comandos/sa-mapa -> /.../sisc */
    char *slash = strrchr(exe, '/');
    if (!slash) return -1;
    *slash = '\0'; /* comandos */
    slash = strrchr(exe, '/');
    if (!slash) return -1;
    *slash = '\0'; /* core */
    slash = strrchr(exe, '/');
    if (!slash) return -1;
    *slash = '\0'; /* sisc */

    if (strlen(exe) + 1 > tam) return -1;
    snprintf(out, tam, "%s", exe);
    return 0;
}

static int pai_dir(const char *path, char *out, size_t tam)
{
    if (!path || !*path || strlen(path) + 1 > tam) return -1;
    snprintf(out, tam, "%s", path);
    char *slash = strrchr(out, '/');
    if (!slash) return -1;
    if (slash == out) slash[1] = '\0';
    else *slash = '\0';
    return 0;
}

static int mesmo_caminho(const char *a, const char *b)
{
    char ra[PATH_MAX], rb[PATH_MAX];
    if (!realpath(a, ra)) snprintf(ra, sizeof(ra), "%s", a ? a : "");
    if (!realpath(b, rb)) snprintf(rb, sizeof(rb), "%s", b ? b : "");
    return strcmp(ra, rb) == 0;
}

static int path_tem_prefixo_dir(const char *path, const char *prefixo)
{
    if (!path || !prefixo || !*prefixo) return 0;
    size_t n = strlen(prefixo);
    return strncmp(path, prefixo, n) == 0 && (path[n] == '\0' || path[n] == '/');
}

static int eh_material_de_comando(const Cfg *cfg, const char *path)
{
    if (!cfg || !path || !*path) return 0;
    char pref[PATH_MAX * 2];
    snprintf(pref, sizeof(pref), "%s/core/comandos", cfg->raiz_sisc);
    if (path_tem_prefixo_dir(path, pref)) return 1;
    snprintf(pref, sizeof(pref), "%s/ajuda", cfg->raiz_sisc);
    if (path_tem_prefixo_dir(path, pref)) return 1;
    snprintf(pref, sizeof(pref), "%s/core/config/mapa-comandos.json", cfg->raiz_sisc);
    if (strcmp(path, pref) == 0) return 1;
    snprintf(pref, sizeof(pref), "%s/pp", cfg->raiz_sisc);
    if (strcmp(path, pref) == 0) return 1;
    snprintf(pref, sizeof(pref), "%s/pp.c", cfg->raiz_sisc);
    return strcmp(path, pref) == 0;
}

static int eh_material_secreto(const Cfg *cfg, const char *path)
{
    if (!cfg || !path || !*path) return 0;
    char pref[PATH_MAX * 2];
    snprintf(pref, sizeof(pref), "%s/secretos", cfg->raiz_sisc);
    if (path_tem_prefixo_dir(path, pref)) return 1;
    snprintf(pref, sizeof(pref), "%s/core/biblioteca-secretos", cfg->raiz_sisc);
    if (path_tem_prefixo_dir(path, pref)) return 1;
    snprintf(pref, sizeof(pref), "%s/core/biblioteca-contas-llm", cfg->raiz_sisc);
    if (path_tem_prefixo_dir(path, pref)) return 1;
    if (strstr(path, "/secretos/") || strstr(path, "/contas-llm/")) return 1;
    size_t n = strlen(path);
    return (n >= 9 && strcmp(path + n - 9, "/secretos") == 0) ||
           (n >= 11 && strcmp(path + n - 11, "/contas-llm") == 0);
}

static char *normalizar_nome(const char *nome)
{
    size_t len = strlen(nome ? 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];
        if (isalnum(c) || c == '-' || c == '_') out[i] = (char)c;
        else out[i] = '-';
    }
    out[len] = '\0';
    if (out[0] == '\0') { free(out); return xstrdup("operador-manual-funcional"); }
    return out;
}

static void texto_compacto(Str *out, const char *txt, size_t max);

static char *ler_arquivo_todo(const char *path)
{
    FILE *fp = fopen(path, "rb");
    if (!fp) return NULL;
    if (fseek(fp, 0, SEEK_END) != 0) { fclose(fp); return NULL; }
    long sz = ftell(fp);
    if (sz < 0) { fclose(fp); return NULL; }
    rewind(fp);
    char *buf = malloc((size_t)sz + 1);
    if (!buf) { fclose(fp); return NULL; }
    size_t n = fread(buf, 1, (size_t)sz, fp);
    fclose(fp);
    buf[n] = '\0';
    return buf;
}

static char *ler_amostra(const char *path, size_t max, size_t *lido_out)
{
    FILE *fp = fopen(path, "rb");
    if (!fp) return NULL;
    char *buf = malloc(max + 1);
    if (!buf) { fclose(fp); return NULL; }
    size_t n = fread(buf, 1, max, fp);
    fclose(fp);
    buf[n] = '\0';
    if (lido_out) *lido_out = n;
    return buf;
}

static char *json_extrair_string_simples(const char *json, const char *chave)
{
    if (!json || !chave || !*chave) return NULL;
    char padrao[256];
    snprintf(padrao, sizeof(padrao), "\"%s\"", chave);
    const char *p = strstr(json, padrao);
    if (!p) return NULL;
    p = strchr(p + strlen(padrao), ':');
    if (!p) return NULL;
    p++;
    while (*p && isspace((unsigned char)*p)) p++;
    if (*p != '"') return NULL;
    p++;
    size_t cap = 256, len = 0;
    char *out = malloc(cap);
    if (!out) return NULL;
    while (*p && *p != '"') {
        char c = *p++;
        if (c == '\\' && *p) {
            c = *p++;
            if (c == 'n') c = ' ';
            else if (c == 't') c = ' ';
            else if (c == 'r') c = ' ';
        }
        if (len + 1 >= cap) {
            cap *= 2;
            char *nb = realloc(out, cap);
            if (!nb) { free(out); return NULL; }
            out = nb;
        }
        out[len++] = c;
    }
    out[len] = '\0';
    return out;
}

static void adicionar_campo_json_se_existir(No *n, const char *amostra, const char *campo, const char *rotulo, const char *ra)
{
    char *v = json_extrair_string_simples(amostra, campo);
    if (!v || !*v) { free(v); return; }
    Str s; str_init(&s);
    str_addf(&s, "%s: ", rotulo);
    texto_compacto(&s, v, 700);
    no_add(n, no_criar(s.buf ? s.buf : rotulo, ra));
    free(s.buf);
    free(v);
}

static int mapa_existente_raiz_sisc(const char *path)
{
    size_t n = 0;
    char *json = ler_amostra(path, 8192, &n);
    (void)n;
    if (!json) return 0;
    int ok = (strstr(json, "OPERADOR MANUAL FUNCIONAL DO SISTEMA") != NULL ||
              strstr(json, "operador manual funcional do sistema") != NULL ||
              strstr(json, "MAPA FUNCIONAL DO SISTEMA") != NULL ||
              strstr(json, "mapa funcional do sistema") != NULL);
    free(json);
    return ok;
}

static int parece_texto(const char *buf, size_t n)
{
    if (!buf) return 0;
    int ruins = 0;
    for (size_t i = 0; i < n; i++) {
        unsigned char c = (unsigned char)buf[i];
        if (c == 0) return 0;
        if (c < 7 || (c > 13 && c < 32)) ruins++;
    }
    return n == 0 || ruins < (int)(n / 20 + 1);
}

static void texto_compacto(Str *out, const char *txt, size_t max)
{
    size_t usados = 0;
    int esp = 0;
    for (const unsigned char *p = (const unsigned char *)(txt ? txt : ""); *p && usados < max; p++) {
        unsigned char c = *p;
        if (c == '\r' || c == '\n' || c == '\t' || isspace(c)) {
            if (!esp && usados < max) { str_add(out, " "); usados++; esp = 1; }
        } else {
            char t[2] = { (char)c, '\0' };
            str_add(out, t);
            usados++;
            esp = 0;
        }
    }
    if (txt && strlen(txt) > max) str_add(out, "...");
}

static char *primeiras_linhas_compactas(const char *buf, int max_linhas, size_t max_chars)
{
    Str s; str_init(&s);
    if (!s.buf) return NULL;
    int linhas = 0;
    const char *p = buf ? buf : "";
    while (*p && linhas < max_linhas && s.len < max_chars) {
        while (*p == '\n' || *p == '\r') p++;
        if (!*p) break;
        const char *fim = p;
        while (*fim && *fim != '\n' && *fim != '\r') fim++;
        size_t n = (size_t)(fim - p);
        while (n > 0 && isspace((unsigned char)*p)) { p++; n--; }
        while (n > 0 && isspace((unsigned char)p[n - 1])) n--;
        if (n > 0) {
            if (s.len > 0) str_add(&s, " | ");
            size_t restante = max_chars > s.len ? max_chars - s.len : 0;
            if (n > restante) n = restante;
            str_addn(&s, p, n);
            linhas++;
        }
        p = fim;
    }
    if (buf && strlen(buf) > max_chars) str_add(&s, "...");
    return s.buf;
}

static const char *extensao(const char *nome)
{
    const char *b = base_name(nome);
    const char *p = strrchr(b, '.');
    return p && p != b ? p + 1 : "";
}

static const char *tipo_por_ext(const char *ext)
{
    if (!ext || !*ext) return "sem extensão/possível executável ou configuração";
    if (!strcasecmp(ext, "c")) return "código C";
    if (!strcasecmp(ext, "h")) return "cabeçalho C/C++";
    if (!strcasecmp(ext, "php")) return "código PHP";
    if (!strcasecmp(ext, "js")) return "código JavaScript";
    if (!strcasecmp(ext, "ts")) return "código TypeScript";
    if (!strcasecmp(ext, "py")) return "código Python";
    if (!strcasecmp(ext, "sh")) return "script shell";
    if (!strcasecmp(ext, "json")) return "JSON/configuração";
    if (!strcasecmp(ext, "md")) return "documentação Markdown";
    if (!strcasecmp(ext, "txt")) return "texto";
    if (!strcasecmp(ext, "sql")) return "SQL";
    if (!strcasecmp(ext, "html") || !strcasecmp(ext, "htm")) return "HTML";
    if (!strcasecmp(ext, "css")) return "CSS";
    if (!strcasecmp(ext, "xml")) return "XML";
    if (!strcasecmp(ext, "yml") || !strcasecmp(ext, "yaml")) return "YAML";
    if (!strcasecmp(ext, "png") || !strcasecmp(ext, "jpg") || !strcasecmp(ext, "jpeg") || !strcasecmp(ext, "gif") || !strcasecmp(ext, "webp")) return "imagem";
    if (!strcasecmp(ext, "pdf")) return "PDF";
    return "arquivo";
}

static const char *papel_dir(const char *nome)
{
    if (!nome) return "diretório";
    if (!strcasecmp(nome, "core")) return "núcleo/infraestrutura do sistema";
    if (!strcasecmp(nome, "comandos")) return "executáveis e comandos do operador";
    if (!strcasecmp(nome, "config")) return "configurações";
    if (!strcasecmp(nome, "arvores")) return "árvores persistidas do SA";
    if (!strcasecmp(nome, "agentes")) return "definições de agentes";
    if (!strcasecmp(nome, "conectores")) return "conectores/integrações";
    if (!strcasecmp(nome, "specs")) return "especificações";
    if (!strcasecmp(nome, "formatos")) return "formatos/modelos";
    if (!strcasecmp(nome, "secretos")) return "segredos/credenciais";
    if (!strcasecmp(nome, "docs") || !strcasecmp(nome, "doc")) return "documentação";
    if (!strcasecmp(nome, "tests") || !strcasecmp(nome, "testes")) return "testes";
    return "diretório do sistema";
}

static const char *papel_arquivo(const char *nome)
{
    const char *b = base_name(nome);
    if (!strcasecmp(b, "Makefile")) return "arquivo de build/compilação";
    if (!strcasecmp(b, "README.md")) return "documentação principal";
    if (!strcasecmp(b, "heb.txt")) return "índice HEB usado para contexto compacto";
    if (!strcasecmp(b, "pp.c")) return "fonte do roteador de comandos pp";
    if (!strcasecmp(b, "pp")) return "binário roteador de comandos pp";
    const char *ext = extensao(b);
    if (!strcasecmp(ext, "c")) return "implementação de comando/módulo em C";
    if (!strcasecmp(ext, "json")) return "dados/configuração persistida";
    if (!strcasecmp(ext, "md")) return "documentação";
    if (!strcasecmp(ext, "txt")) return "texto/índice";
    return tipo_por_ext(ext);
}

static void coletar_simbolos_linha(Str *s, const char *linha)
{
    const char *p = linha;
    while (*p && isspace((unsigned char)*p)) p++;
    if (!*p) return;

    int interessante = 0;
    if (strstr(p, "#include") == p) interessante = 1;
    else if (strstr(p, "function ")) interessante = 1;
    else if (strstr(p, "class ")) interessante = 1;
    else if (strstr(p, "def ") == p || strstr(p, "async def ") == p) interessante = 1;
    else if (strstr(p, "typedef struct") == p) interessante = 1;
    else if ((strstr(p, "static ") == p || strstr(p, "int ") == p || strstr(p, "void ") == p || strstr(p, "char ") == p) && strchr(p, '(') && (strchr(p, '{') || strchr(p, ')'))) interessante = 1;
    else if (strchr(p, ':') && p[0] != '/' && p[0] != '*' && strlen(p) < 160) {
        const char *c = strchr(p, ':');
        if (c && c[1] != '/' && c[1] != ':') interessante = 1;
    }

    if (!interessante) return;
    if (s->len > MAPA_MAX_SIMBOLOS) return;
    size_t n = strlen(p);
    while (n > 0 && (p[n - 1] == '\n' || p[n - 1] == '\r' || isspace((unsigned char)p[n - 1]))) n--;
    if (n > 180) n = 180;
    str_add(s, " | ");
    str_addn(s, p, n);
}

static char *extrair_simbolos(const char *buf)
{
    Str s; str_init(&s);
    if (!s.buf) return NULL;
    const char *p = buf ? buf : "";
    char linha[MAPA_MAX_LINHA];
    int linhas = 0;
    while (*p && linhas < 2000 && s.len < MAPA_MAX_SIMBOLOS) {
        size_t n = 0;
        while (p[n] && p[n] != '\n' && p[n] != '\r' && n < sizeof(linha) - 1) { linha[n] = p[n]; n++; }
        linha[n] = '\0';
        coletar_simbolos_linha(&s, linha);
        while (p[n] && p[n] != '\n' && p[n] != '\r') n++;
        p += n;
        while (*p == '\n' || *p == '\r') p++;
        linhas++;
    }
    if (s.len == 0) str_add(&s, "nenhum símbolo de alto nível detectado na amostra");
    return s.buf;
}

static char *formatar_tamanho(off_t tam)
{
    char buf[64];
    if (tam >= 1024 * 1024) snprintf(buf, sizeof(buf), "%.2f MB", (double)tam / (1024.0 * 1024.0));
    else if (tam >= 1024) snprintf(buf, sizeof(buf), "%.2f KB", (double)tam / 1024.0);
    else snprintf(buf, sizeof(buf), "%lld B", (long long)tam);
    return xstrdup(buf);
}

static char *formatar_mtime(time_t t)
{
    struct tm tmv;
    localtime_r(&t, &tmv);
    char buf[64];
    strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tmv);
    return xstrdup(buf);
}

static int caminho_igual(const char *a, const char *b)
{
    if (!a || !b) return 0;
    char *ra = realpath(a, NULL);
    char *rb = realpath(b, NULL);
    int eq;
    if (ra && rb) eq = (strcmp(ra, rb) == 0);
    else eq = (strcmp(a, b) == 0);
    free(ra);
    free(rb);
    return eq;
}

static void ctx_carregar_ignore_unico(Cfg *cfg)
{
    if (!cfg || !cfg->raiz_sisc[0]) return;
    char path[PATH_MAX * 2];
    snprintf(path, sizeof(path), "%s/.ignorecontexto", cfg->raiz_sisc);
    char *txt = ler_arquivo_todo(path);
    if (!txt) return;
    int cap = 32;
    cfg->ctx_ignore = calloc((size_t)cap, sizeof(char *));
    if (!cfg->ctx_ignore) { free(txt); return; }
    char *save = NULL;
    char *linha = strtok_r(txt, "\n", &save);
    while (linha) {
        while (*linha == ' ' || *linha == '\t') linha++;
        char *fim = linha + strlen(linha);
        while (fim > linha && (fim[-1] == ' ' || fim[-1] == '\t' || fim[-1] == '\r')) *--fim = '\0';
        if (*linha && *linha != '#') {
            if (cfg->ctx_ignore_count >= cap) {
                cap *= 2;
                char **nx = realloc(cfg->ctx_ignore, (size_t)cap * sizeof(char *));
                if (!nx) break;
                cfg->ctx_ignore = nx;
            }
            cfg->ctx_ignore[cfg->ctx_ignore_count++] = xstrdup(linha);
        }
        linha = strtok_r(NULL, "\n", &save);
    }
    free(txt);
}

static void ctx_liberar_ignore(Cfg *cfg)
{
    if (!cfg || !cfg->ctx_ignore) return;
    for (int i = 0; i < cfg->ctx_ignore_count; i++) free(cfg->ctx_ignore[i]);
    free(cfg->ctx_ignore);
    cfg->ctx_ignore = NULL;
    cfg->ctx_ignore_count = 0;
}

static int ctx_deve_ignorar_path(const Cfg *cfg, const char *path, int eh_dir)
{
    if (!cfg || !path) return 0;
    size_t len = strlen(path);
    for (int i = 0; i < cfg->ctx_ignore_count; i++) {
        const char *pat = cfg->ctx_ignore[i];
        if (!pat || !*pat) continue;
        if (pat[0] == '!') {
            if (!eh_dir && strcmp(pat, "!sem-extensao") == 0) {
                const char *b = base_name(path);
                if (b && *b && !strchr(b, '.')) return 1;
            }
            continue;
        }
        size_t lp = strlen(pat);
        if (lp == 0) continue;
        if (lp <= len) {
            for (size_t p = 0; p + lp <= len; p++)
                if (strncmp(path + p, pat, lp) == 0) return 1;
        }
        if (eh_dir && lp >= 2 && pat[0] == '/' && pat[lp - 1] == '/') {
            size_t core = lp - 1;
            if (len >= core && strncmp(path + len - core, pat, core) == 0) return 1;
        }
    }
    return 0;
}

static int deve_ignorar_dir(const Cfg *cfg, const char *nome, const char *path, int prof)
{
    if (eh_material_de_comando(cfg, path)) return 1;
    int secreto = eh_material_secreto(cfg, path);
    if (secreto && !cfg->incluir_secretos) return 1;
    if (!(secreto && cfg->incluir_secretos) && ctx_deve_ignorar_path(cfg, path, 1)) return 1;
    if (!cfg->incluir_ocultos && nome[0] == '.') return 1;
    /* Filtro de sistemas irmãos (só na raiz) */
    if (prof == 0 && cfg->sistema_alvo[0]) {
        if (strcmp(nome, "core") != 0 && strcmp(nome, "arvores") != 0 && strcmp(nome, cfg->sistema_alvo) != 0) return 1;
    }
    if (cfg->incluir_ignorados) return 0;
    if (!strcmp(nome, ".svn") || !strcmp(nome, ".hg") || !strcmp(nome, "__pycache__") || !strcmp(nome, ".cache")) return 1;
    return 0;
}

static int deve_ignorar_file(const Cfg *cfg, const char *nome, const char *path)
{
    if (eh_material_de_comando(cfg, path)) return 1;
    int secreto = eh_material_secreto(cfg, path);
    if (secreto && !cfg->incluir_secretos) return 1;
    if (!(secreto && cfg->incluir_secretos) && ctx_deve_ignorar_path(cfg, path, 0)) return 1;
    if (!cfg->incluir_ocultos && nome[0] == '.') return 1;
    if (cfg->self_map_path[0] && caminho_igual(path, cfg->self_map_path)) return 1;
    if (cfg->system_tree_path[0] && caminho_igual(path, cfg->system_tree_path)) return 1;
    return 0;
}

static int deve_excluir_extensao(const Cfg *cfg, const char *nome)
{
    if (!cfg->exceto_buf[0]) return 0;
    const char *ext = extensao(nome);
    char *buf = strdup(cfg->exceto_buf);
    if (!buf) return 0;
    char *saveptr = NULL;
    char *token = strtok_r(buf, ",", &saveptr);
    int found = 0;
    while (token) {
        /* token pode ser "." (sem extensao) ou ".json" etc */
        const char *t = token;
        while (*t == '.') t++;
        if (*t == '\0') {
            /* entrada "." significa arquivos sem extensao */
            if (ext[0] == '\0') { found = 1; break; }
        } else {
            if (strcasecmp(t, ext) == 0) { found = 1; break; }
        }
        token = strtok_r(NULL, ",", &saveptr);
    }
    free(buf);
    return found;
}

typedef struct {
    char *nome;
    char path[PATH_MAX * 2];
    int is_dir;
    off_t tam;
} Entrada;

static int cmp_entrada(const void *a, const void *b)
{
    const Entrada *ea = (const Entrada *)a, *eb = (const Entrada *)b;
    if (ea->is_dir != eb->is_dir) return eb->is_dir - ea->is_dir; /* dirs primeiro */
    return strcasecmp(ea->nome, eb->nome);
}

static void entrada_free(Entrada *e, int n)
{
    for (int i = 0; i < n; i++) free(e[i].nome);
    free(e);
}

static int listar_entradas(const Cfg *cfg, const char *dir, Entrada **out, int *total_out, long long *bytes_out, int *dirs_out, int *files_out, int prof)
{
    DIR *d = opendir(dir);
    if (!d) return -1;
    Entrada *v = NULL;
    int total = 0, cap = 0;
    long long bytes = 0;
    int nd = 0, nf = 0;
    struct dirent *ent;
    while ((ent = readdir(d)) != NULL) {
        if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) continue;
        char path[PATH_MAX * 2];
        snprintf(path, sizeof(path), "%s/%s", dir, ent->d_name);
        struct stat st;
        if (lstat(path, &st) != 0) continue;
        int is_dir = S_ISDIR(st.st_mode);
        int is_reg = S_ISREG(st.st_mode);
        if (!is_dir && !is_reg) continue;
        if (is_dir && deve_ignorar_dir(cfg, ent->d_name, path, prof)) continue;
        if (is_reg && deve_ignorar_file(cfg, ent->d_name, path)) continue;
        if (is_reg && deve_excluir_extensao(cfg, ent->d_name)) continue;
        if (total >= cap) {
            int nova = cap ? cap * 2 : 32;
            Entrada *nv = realloc(v, (size_t)nova * sizeof(Entrada));
            if (!nv) { closedir(d); entrada_free(v, total); return -1; }
            v = nv; cap = nova;
        }
        memset(&v[total], 0, sizeof(Entrada));
        v[total].nome = xstrdup(ent->d_name);
        snprintf(v[total].path, sizeof(v[total].path), "%s", path);
        v[total].is_dir = is_dir;
        v[total].tam = st.st_size;
        total++;
        if (is_dir) nd++; else { nf++; bytes += (long long)st.st_size; }
    }
    closedir(d);
    qsort(v, (size_t)total, sizeof(Entrada), cmp_entrada);
    *out = v;
    *total_out = total;
    if (bytes_out) *bytes_out = bytes;
    if (dirs_out) *dirs_out = nd;
    if (files_out) *files_out = nf;
    return 0;
}

static void montar_ra(const Cfg *cfg, const char *rel, char *out, size_t tam)
{
    const char *base = cfg->ra_base[0] ? cfg->ra_base : "sisc";
    if (!rel || !*rel) snprintf(out, tam, "%s", base);
    else snprintf(out, tam, "%s/%s", base, rel);
}

static char *rel_join(const char *base, const char *nome)
{
    if (!base || !*base) return xstrdup(nome);
    size_t a = strlen(base), b = strlen(nome);
    char *out = malloc(a + b + 2);
    if (!out) return NULL;
    memcpy(out, base, a);
    out[a] = '/';
    memcpy(out + a + 1, nome, b + 1);
    return out;
}

static No *criar_no_arquivo(const Cfg *cfg, const char *path, const char *rel, const char *nome)
{
    struct stat st;
    if (stat(path, &st) != 0) return no_criar(nome, NULL);

    char ra[PATH_MAX * 2];
    montar_ra(cfg, rel, ra, sizeof(ra));
    char *tam = formatar_tamanho(st.st_size);
    char *mtime = formatar_mtime(st.st_mtime);
    const char *ext = extensao(nome);
    const char *tipo = tipo_por_ext(ext);
    const char *papel = papel_arquivo(nome);

    size_t lido = 0;
    char *amostra = NULL;
    int texto = 0;
    char *linhas = NULL;
    char *simbolos = NULL;
    int eh_json = !strcasecmp(ext, "json");
    int eh_textual_interessante = eh_json || !strcasecmp(ext, "md") || !strcasecmp(ext, "txt") || !strcasecmp(ext, "php") || !strcasecmp(ext, "c") || !strcasecmp(ext, "h") || !strcasecmp(ext, "sh");

    if (eh_textual_interessante || cfg->incluir_codigo) {
        amostra = ler_amostra(path, MAPA_MAX_AMOSTRA_ARQUIVO, &lido);
        texto = parece_texto(amostra, lido);
        if (texto) {
            linhas = primeiras_linhas_compactas(amostra, 6, MAPA_MAX_TRECHO);
            if (cfg->incluir_codigo) simbolos = extrair_simbolos(amostra);
        }
    }

    Str nome_no; str_init(&nome_no);
    str_addf(&nome_no, "peça funcional %s — função no sistema: %s; contrato/referência: %s", nome, papel, ra);
    No *n = no_criar(nome_no.buf ? nome_no.buf : nome, ra);

    Str meta; str_init(&meta);
    str_addf(&meta, "como consultar esta peça: use ra=%s; tipo=%s; tamanho=%s; modificado=%s", ra, tipo, tam ? tam : "", mtime ? mtime : "");
    no_add(n, no_criar(meta.buf ? meta.buf : "referência da peça", ra));
    free(meta.buf);

    if (eh_json && texto && amostra) {
        adicionar_campo_json_se_existir(n, amostra, "titulo", "título funcional", ra);
        adicionar_campo_json_se_existir(n, amostra, "descricao", "descrição funcional", ra);
        adicionar_campo_json_se_existir(n, amostra, "objetivo", "objetivo/contrato operacional", ra);
        adicionar_campo_json_se_existir(n, amostra, "tipo", "tipo declarado", ra);
        adicionar_campo_json_se_existir(n, amostra, "metodoLeitura", "modo de leitura/mensagem", ra);
        adicionar_campo_json_se_existir(n, amostra, "metodoEnvio", "modo de envio/mensagem", ra);
    } else if (texto && linhas && linhas[0] && (strstr(rel ? rel : "", "agentes/") || strstr(rel ? rel : "", "conectores/") || strstr(rel ? rel : "", "web-api/") || strstr(rel ? rel : "", "escuta/"))) {
        Str pista; str_init(&pista);
        str_add(&pista, "pista funcional extraída do próprio artefato: ");
        texto_compacto(&pista, linhas, 500);
        no_add(n, no_criar(pista.buf ? pista.buf : "pista funcional", ra));
        free(pista.buf);
    }

    if (cfg->incluir_codigo) {
        if (texto) {
            char *simtxt = simbolos && simbolos[0] ? simbolos : "nenhum símbolo detectado";
            Str ss; str_init(&ss);
            str_addf(&ss, "pontos de entrada/símbolos detectados para entender funcionamento:%s", simtxt);
            no_add(n, no_criar(ss.buf ? ss.buf : "símbolos detectados", ra));
            free(ss.buf);
        } else {
            char bin[512];
            snprintf(bin, sizeof(bin), "peça binária/não textual; funcionamento deve ser inferido pelo contrato ao redor e pela referência ra=%s", ra);
            no_add(n, no_criar(bin, ra));
        }
    }

    free(nome_no.buf);
    free(tam); free(mtime); free(amostra); free(linhas); free(simbolos);
    return n;
}

static No *criar_no_dir(const Cfg *cfg, const char *path, const char *rel, const char *nome, int prof)
{
    Entrada *ents = NULL;
    int total = 0, nd = 0, nf = 0;
    long long bytes = 0;
    listar_entradas(cfg, path, &ents, &total, &bytes, &nd, &nf, prof);

    char *tam = formatar_tamanho((off_t)bytes);
    const char *papel = rel && *rel ? papel_dir(nome) : "raiz do sistema mapeado";
    char ra[PATH_MAX * 2];
    montar_ra(cfg, rel, ra, sizeof(ra));

    Str titulo; str_init(&titulo);
    if (!rel || !*rel) {
        str_addf(&titulo, "OPERADOR MANUAL FUNCIONAL DO SISTEMA %s — contrato vivo de atores, processos, artefatos e integrações", cfg->sistema_nome);
    } else {
        str_addf(&titulo, "área funcional %s — papel no sistema: %s; referência: %s", nome, papel, ra);
    }

    No *n = no_criar(titulo.buf ? titulo.buf : nome, rel && *rel ? ra : NULL);
    Str visao; str_init(&visao);
    if (!rel || !*rel) {
        str_addf(&visao, "finalidade deste mapa: explicar como o sistema funciona por áreas, atores e processos; não é inventário estrutural. Material de comandos é excluído porque a fonte canônica é pp --<comando> --ajuda e os arquivos em ajuda/.");
    } else {
        str_addf(&visao, "contrato da área %s: %s. Use os filhos para entender responsabilidades, entradas, saídas e artefatos; estatística auxiliar: %d áreas filhas, %d peças diretas, %s em peças diretas.", nome, papel, nd, nf, tam ? tam : "");
    }
    no_add(n, no_criar(visao.buf, rel && *rel ? ra : NULL));
    free(visao.buf);

    if (!rel || !*rel) {
        no_add(n, no_criar("regra de leitura: cada item descreve o papel funcional da peça; o campo ra aponta para a evidência material quando for necessário auditar.", NULL));
        no_add(n, no_criar("escopo excluído: core/comandos/, ajuda/, pp e pp.c não entram neste mapa; consulte as ajudas dos comandos para esse material.", NULL));
    } else if (!strcmp(rel, "core")) {
        no_add(n, no_criar("comandos do operador: excluídos deste mapa funcional; consultar pp --listar-comandos e pp --<comando> --ajuda.", ra));
    }

    if (cfg->max_prof >= 0 && prof >= cfg->max_prof) {
        no_add(n, no_criar("profundidade máxima atingida; use --max-prof=N maior ou omita para mapear mais fundo", rel && *rel ? ra : NULL));
        entrada_free(ents, total);
        free(tam); free(titulo.buf);
        return n;
    }

    for (int i = 0; i < total; i++) {
        char *r = rel_join(rel, ents[i].nome);
        if (!r) continue;
        No *filho = NULL;
        if (ents[i].is_dir) filho = criar_no_dir(cfg, ents[i].path, r, ents[i].nome, prof + 1);
        else filho = criar_no_arquivo(cfg, ents[i].path, r, ents[i].nome);
        if (filho) no_add(n, filho);
        free(r);
    }

    entrada_free(ents, total);
    free(tam); free(titulo.buf);
    return n;
}

static int caminho_comando_irmao(const char *nome, char *out, size_t tam);

static int arquivo_eh_arvore_sa(const char *path)
{
    char *json = ler_arquivo_todo(path);
    if (!json) return 0;
    char *p = strstr(json, "\"tipo\"");
    int ok = 0;
    if (p) {
        p = strchr(p, ':');
        if (p) {
            p++;
            while (*p && isspace((unsigned char)*p)) p++;
            ok = strncmp(p, "\"arvore-sa\"", 11) == 0;
        }
    }
    free(json);
    return ok;
}

static int sa_exec_args(char *const args[], int confirmar)
{
    int ip[2] = {-1, -1};
    if (confirmar && pipe(ip) != 0) return 1;
    pid_t pid = fork();
    if (pid < 0) return 1;
    if (pid == 0) {
        if (confirmar) { close(ip[1]); dup2(ip[0], STDIN_FILENO); close(ip[0]); }
        int devnull = open("/dev/null", O_WRONLY);
        if (devnull >= 0) { dup2(devnull, STDOUT_FILENO); dup2(devnull, STDERR_FILENO); close(devnull); }
        execv(args[0], args);
        _exit(127);
    }
    if (confirmar) { close(ip[0]); write(ip[1], "s\n", 2); close(ip[1]); }
    int st = 0;
    if (waitpid(pid, &st, 0) < 0) return 1;
    return WIFEXITED(st) ? WEXITSTATUS(st) : 1;
}

static int materializar_no_sa_rec(const char *sa_path, const char *json_path, No *n, const char *em, const char *id, int confirmar)
{
    if (!n || !n->nome) return 0;
    char ag[MAPA_MAX_TRECHO + 16];
    snprintf(ag, sizeof(ag), "--ag=%s", n->nome);
    char emarg[256];
    char *args_add[5];
    int p = 0;
    args_add[p++] = (char *)sa_path;
    args_add[p++] = (char *)json_path;
    args_add[p++] = ag;
    if (em && *em) { snprintf(emarg, sizeof(emarg), "--em=%s", em); args_add[p++] = emarg; }
    args_add[p] = NULL;
    if (sa_exec_args(args_add, confirmar) != 0) return -1;

    if (n->ra && n->ra[0]) {
        char ra[PATH_MAX * 2 + 16];
        char emid[256];
        snprintf(ra, sizeof(ra), "--ra=%s", n->ra);
        snprintf(emid, sizeof(emid), "--em=%s", id ? id : "1");
        char *args_ra[] = { (char *)sa_path, (char *)json_path, ra, emid, NULL };
        if (sa_exec_args(args_ra, 0) != 0) return -1;
    }

    for (int i = 0; i < n->total; i++) {
        char cid[256];
        snprintf(cid, sizeof(cid), "%s%s%d", id && *id ? id : "", id && *id ? "." : "", i + 1);
        if (materializar_no_sa_rec(sa_path, json_path, n->filhos[i], cid, cid, 0) != 0) return -1;
    }
    return 0;
}

static int gerar_mapa(Cfg *cfg, const char *json_path)
{
    if (access(json_path, F_OK) == 0 && !arquivo_eh_arvore_sa(json_path)) {
        fprintf(stderr, "sa-mapa: arquivo existente não é árvore SA; recusando sobrescrever: %s\n", json_path);
        return -1;
    }
    No *raiz = criar_no_dir(cfg, cfg->raiz_exec, "", cfg->sistema_nome, 0);
    if (!raiz) return -1;
    if (garantir_dir_pai(json_path) != 0) { no_free(raiz); return -1; }
    unlink(json_path);
    char sa_path[PATH_MAX];
    if (caminho_comando_irmao("sa", sa_path, sizeof(sa_path)) != 0) { no_free(raiz); return -1; }
    int r = materializar_no_sa_rec(sa_path, json_path, raiz, NULL, "1", 1);
    no_free(raiz);
    return r;
}

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_sa(const char *nome_arvore, char **flags, int nflags)
{
    char sa_path[PATH_MAX];
    if (caminho_comando_irmao("sa", sa_path, sizeof(sa_path)) != 0) {
        fprintf(stderr, "sa-mapa: não foi possível localizar o comando sa.\n");
        return 1;
    }
    char **args = calloc((size_t)nflags + 3, sizeof(char *));
    if (!args) return 1;
    int pos = 0;
    args[pos++] = sa_path;
    args[pos++] = (char *)nome_arvore;
    for (int i = 0; i < nflags; i++) args[pos++] = flags[i];
    args[pos] = NULL;

    pid_t pid = fork();
    if (pid < 0) { perror("sa-mapa: fork"); free(args); return 1; }
    if (pid == 0) {
        setenv("SISC_SA_MAPA_DIR_LIVRE", "1", 1);
        execv(sa_path, args);
        perror("sa-mapa: execv sa");
        _exit(127);
    }
    int status = 0;
    if (waitpid(pid, &status, 0) < 0) { perror("sa-mapa: waitpid"); free(args); return 1; }
    free(args);
    if (WIFEXITED(status)) return WEXITSTATUS(status);
    return 1;
}

static void ajuda(void)
{
    fputs(
        "Uso:\n"
        "  NOTA: sa-mapa é interno. Use pp --atualizar-manual-funcional ou o alias pp --sa --mapa.\n"
        "\n"
        "  Uso público:\n"
        "    pp --atualizar-manual-funcional                 Cria/exibe sisc/arvores/operador-manual-funcional.json\n"
        "    pp --atualizar-manual-funcional --atualizar      Revarre e recria o operador manual funcional\n"
        "\n"
        "Descrição:\n"
        "  Constrói uma árvore SA funcional a partir de sisc/. Quando executado dentro\n"
        "  de um sistema, salva sempre em sisc/arvores/operador-manual-funcional.json, mantém core/ + sistema atual e exclui os sistemas irmãos.\n"
        "  O mapa explica atores, processos, artefatos e integrações; não é inventário\n"
        "  estrutural. Material de comandos é excluído porque já existe em --ajuda.\n"
        "  Este comando é automaticamente invocado por pp --atualizar-manual-funcional.\n"
        "\n"
        "Grupos de ajuda: uso, mapa, opcoes, sa, exemplos.\n"
        "\n"
        "1) Uso e ajuda [uso ajuda]\n"
        "  --ajuda, --help, -h            Exibe esta ajuda\n"
        "  --ajuda:<grupo>                Filtra um grupo. Ex: pp --sa-mapa --ajuda:opcoes\n"
        "\n"
        "2) Mapa gerado [mapa arquivos]\n"
        "  Cada peça mapeada recebe ra=sisc/<path> (ex: sisc/core/... ou\n"
        "  sisc/<sistema-atual>/...). Por padrão o mapa registra função, contrato,\n"
        "  descrição e objetivo quando existirem em JSON; não inclui core/comandos/,\n"
        "  ajuda/, pp ou pp.c. O uso padrão gera somente sisc/arvores/operador-manual-funcional.json\n"
        "  para contexto normativo/operacional do sistema.\n"
        "  A política de exclusão de contexto vem exclusivamente de sisc/.ignorecontexto.\n"
        "\n"
        "3) Opções próprias [opcoes]\n"
        "  --atualizar                    Recria o mapa mesmo se ele já existir\n"
        "  --recriar                      Alias de --atualizar\n"
        "  --incluir-ocultos              Inclui nomes iniciados por ponto\n"
        "  --incluir-ignorados            Inclui diretórios normalmente ignorados\n"
        "  --incluir-secretos             Inclui secretos/contas-llm; use com cuidado\n"
        "  --incluir-codigo               Inclui amostras/símbolos dos atores não-comando\n"        "  --max-prof=N                   Limita profundidade\n"
        "  --exceto=.ext1,.ext2,...        Exclui arquivos pela extensão. Use . para sem extensão\n"
        "  --sistema=<nome>               Filtra apenas core/ + diretório informado (raiz)\n"
        "  Exemplo com todas as flags do grupo:\n"
        "    pp --atualizar-manual-funcional --atualizar --incluir-ocultos --incluir-ignorados --incluir-secretos --incluir-codigo --max-prof=3 --exceto=.,.json,.txt --sistema=siscore\n"
        "\n"
        "4) Flags repassadas ao --sa [sa flags]\n"
        "  Flags não reconhecidas são repassadas ao --sa na árvore de mapa:\n"
        "  --gc, --mm, --prompt, --rel, --ra, --dra, --json, --recolher, --expandir, --replace.\n"
        "\n"
        "5) Exemplos [exemplos]\n"
        "  pp --atualizar-manual-funcional\n"
        "  pp --atualizar-manual-funcional --sistema=siscore\n"
        "  pp --atualizar-manual-funcional --atualizar\n"
        "  pp --atualizar-manual-funcional --json\n"
        "  pp --atualizar-manual-funcional --gc=rev1 --mm='analise este ponto' --em=1.2.3\n"
        "  pp --sa=sistema --mapa --prompt=rev1 --rel  # alias legado\n",
        stdout);
}

int main(int argc, char *argv[])
{
    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "--ajuda") == 0 || strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
            ajuda();
            return 0;
        }
    }

    /* sa-mapa só pode ser executado via pp --atualizar-manual-funcional (nunca diretamente), exceto ajuda. */
    if (!getenv("SISC_SA_MAPA_SOMENTE_GERAR")) {
        fprintf(stderr, "sa-mapa: este comando é interno; use pp --atualizar-manual-funcional.\n");
        fprintf(stderr, "Alias legado: pp --sa --mapa ou pp --sa=<nome> --mapa\n");
        return 1;
    }

    /* Preferencialmente executado dentro de um sistema: cwd define onde salvar e qual sistema manter. */
    Cfg cfg;
    memset(&cfg, 0, sizeof(cfg));
    cfg.max_prof = -1;
    cfg.nome_arvore = "operador-manual-funcional";

    int atualizar = 1; /* mapa é contexto vivo: toda invocação de pp --atualizar-manual-funcional revarre e atualiza */
    int ajuda_flag = 0;
    char **sa_flags = calloc((size_t)argc + 1, sizeof(char *));
    int n_sa_flags = 0;
    if (!sa_flags) return 1;

    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "--ajuda") == 0 || strcmp(argv[i], "-h") == 0) {
            ajuda_flag = 1;
        } else if (strcmp(argv[i], "--atualizar") == 0 || strcmp(argv[i], "--recriar") == 0) {
            atualizar = 1;
        } else if (strcmp(argv[i], "--incluir-ocultos") == 0) {
            cfg.incluir_ocultos = 1;
        } else if (strcmp(argv[i], "--incluir-ignorados") == 0) {
            cfg.incluir_ignorados = 1;
        } else if (strcmp(argv[i], "--incluir-secretos") == 0) {
            cfg.incluir_secretos = 1;
        } else if (strcmp(argv[i], "--incluir-codigo") == 0 || strcmp(argv[i], "--incluir-conteudo") == 0) {
            cfg.incluir_codigo = 1;
        } else if (strncmp(argv[i], "--max-prof=", 11) == 0) {
            cfg.max_prof = atoi(argv[i] + 11);
        } else if (strncmp(argv[i], "--exceto=", 9) == 0) {
            snprintf(cfg.exceto_buf, sizeof(cfg.exceto_buf), "%s", argv[i] + 9);
            atualizar = 1;  /* forca regeneracao para aplicar o filtro */
        } else if (strncmp(argv[i], "--sistema=", 10) == 0) {
            snprintf(cfg.sistema_alvo, sizeof(cfg.sistema_alvo), "%s", argv[i] + 10);
        } else if (argv[i][0] != '-' && cfg.nome_arvore && strcmp(cfg.nome_arvore, "operador-manual-funcional") == 0) {
            cfg.nome_arvore = argv[i];
        } else {
            sa_flags[n_sa_flags++] = argv[i];
        }
    }

    if (ajuda_flag) { ajuda(); free(sa_flags); return 0; }

    char cwd[PATH_MAX];
    if (!getcwd(cwd, sizeof(cwd))) {
        perror("sa-mapa: getcwd");
        free(sa_flags); return 1;
    }

    int modo_sa_local = getenv("SISC_SA_LOCAL_ONLY") != NULL;

    char raiz_instalada[PATH_MAX];
    int tem_raiz_instalada = !modo_sa_local && (raiz_sisc_por_executavel(raiz_instalada, sizeof(raiz_instalada)) == 0);
    if (tem_raiz_instalada) snprintf(cfg.raiz_sisc, sizeof(cfg.raiz_sisc), "%s", raiz_instalada);
    else {
        snprintf(cfg.raiz_sisc, sizeof(cfg.raiz_sisc), "%s", cwd);
        char *slash = strrchr(cfg.raiz_sisc, '/');
        if (slash && slash != cfg.raiz_sisc) *slash = '\0';
    }

    char pai_cwd[PATH_MAX];
    int cwd_tem_pai = (pai_dir(cwd, pai_cwd, sizeof(pai_cwd)) == 0);
    int cwd_eh_sistema = tem_raiz_instalada && cwd_tem_pai && mesmo_caminho(pai_cwd, cfg.raiz_sisc) && !mesmo_caminho(cwd, cfg.raiz_sisc);

    if (cwd_eh_sistema) {
        /* Executado dentro de um sistema: salva nele, mas mapeia desde sisc/. */
        snprintf(cfg.sistema_dir, sizeof(cfg.sistema_dir), "%s", cwd);
        if (!cfg.sistema_alvo[0]) snprintf(cfg.sistema_alvo, sizeof(cfg.sistema_alvo), "%s", base_name(cwd));
        snprintf(cfg.raiz_exec, sizeof(cfg.raiz_exec), "%s", cfg.raiz_sisc);
        snprintf(cfg.sistema_nome, sizeof(cfg.sistema_nome), "%s", base_name(cwd));
        snprintf(cfg.ra_base, sizeof(cfg.ra_base), "sisc");
    } else if (tem_raiz_instalada && mesmo_caminho(cwd, cfg.raiz_sisc) && cfg.sistema_alvo[0]) {
        /* Compatibilidade: executado em sisc/ com --sistema=<nome>. */
        char alvo_dir[PATH_MAX * 2];
        snprintf(alvo_dir, sizeof(alvo_dir), "%s/%s", cfg.raiz_sisc, cfg.sistema_alvo);
        if (eh_diretorio(alvo_dir)) {
            strncpy(cfg.sistema_dir, alvo_dir, sizeof(cfg.sistema_dir) - 1);
            cfg.sistema_dir[sizeof(cfg.sistema_dir) - 1] = '\0';
        } else snprintf(cfg.sistema_dir, sizeof(cfg.sistema_dir), "%s", cwd);
        snprintf(cfg.raiz_exec, sizeof(cfg.raiz_exec), "%s", cfg.raiz_sisc);
        snprintf(cfg.sistema_nome, sizeof(cfg.sistema_nome), "%s", cfg.sistema_alvo[0] ? cfg.sistema_alvo : base_name(cfg.raiz_sisc));
        snprintf(cfg.ra_base, sizeof(cfg.ra_base), "sisc");
    } else {
        /* Fallback antigo para execução fora de um sistema direto de sisc. */
        snprintf(cfg.sistema_dir, sizeof(cfg.sistema_dir), "%s", cwd);
        snprintf(cfg.raiz_exec, sizeof(cfg.raiz_exec), "%s", cwd);
        snprintf(cfg.sistema_nome, sizeof(cfg.sistema_nome), "%s", base_name(cfg.raiz_exec));
        if (!cfg.sistema_nome[0]) snprintf(cfg.sistema_nome, sizeof(cfg.sistema_nome), "sistema");
        char ra_tmp[PATH_MAX + 16];
        snprintf(ra_tmp, sizeof(ra_tmp), "sisc/%s", cfg.sistema_nome);
        strncpy(cfg.ra_base, ra_tmp, sizeof(cfg.ra_base) - 1);
        cfg.ra_base[sizeof(cfg.ra_base) - 1] = '\0';
    }

    ctx_carregar_ignore_unico(&cfg);

    snprintf(cfg.saida_dir, sizeof(cfg.saida_dir), "%s", cfg.raiz_sisc[0] ? cfg.raiz_sisc : (cfg.sistema_dir[0] ? cfg.sistema_dir : cfg.raiz_exec));

    char *norm = strchr(cfg.nome_arvore, '/') ? strdup(cfg.nome_arvore) : normalizar_nome(cfg.nome_arvore);
    if (!norm) { free(sa_flags); return 1; }
    char json_path[PATH_MAX * 4];
    const char *suf = termina_com_json(norm) ? "" : ".json";
    if (norm[0] == '/') snprintf(json_path, sizeof(json_path), "%s%s", norm, suf);
    else if (strchr(norm, '/')) snprintf(json_path, sizeof(json_path), "%s/%s%s", cfg.saida_dir, norm, suf);
    else snprintf(json_path, sizeof(json_path), "%s/arvores/%s%s", cfg.saida_dir, norm, suf);
    if (garantir_dir_pai(json_path) != 0) {
        fprintf(stderr, "sa-mapa: não foi possível criar diretório de destino para: %s\n", json_path);
        free(norm); free(sa_flags); return 1;
    }
    snprintf(cfg.self_map_path, sizeof(cfg.self_map_path), "%s", json_path);

    int existe = eh_arquivo_regular(json_path);
    if (existe && strcmp(cfg.ra_base, "sisc") == 0 && !mapa_existente_raiz_sisc(json_path)) {
        atualizar = 1; /* regenera mapas antigos que começavam no sistema em vez de sisc/ */
    }
    if (atualizar || !existe) {
        fprintf(stderr, "sa-mapa: varrendo sistema em %s ...\n", cfg.raiz_exec);
        if (gerar_mapa(&cfg, json_path) != 0) {
            fprintf(stderr, "sa-mapa: falha ao gerar mapa em %s: %s\n", json_path, strerror(errno));
            free(norm); free(sa_flags); return 1;
        }
        fprintf(stderr, "sa-mapa: mapa atualizado em %s\n", json_path);
    }

    if (getenv("SISC_SA_MAPA_SOMENTE_GERAR")) {
        free(norm);
        free(sa_flags);
        ctx_liberar_ignore(&cfg);
        return 0;
    }

    int r = executar_sa(norm, sa_flags, n_sa_flags);
    free(norm);
    free(sa_flags);
    ctx_liberar_ignore(&cfg);
    return r;
}
