/*
 * atualizar-heb - Reconstrói arquivos heb.txt por diretório (incremental)
 * Uso: pp --atualizar-heb [--raiz=<caminho>] [--modo=conteudo|hibrido] [--max-bytes=N] [--verbose]
 *
 * Cria/atualiza um heb.txt em cada diretório com a transformação HEB
 * (Hash de Extração Bruta) dos arquivos daquele diretório.
 * Modo padrão: nome (usa apenas caminho/nome do arquivo).
 * Só reconstrói se o heb.txt estiver ausente ou mais antigo que algum
 * arquivo do diretório.
 *
 * Formato do heb.txt: caminho-relativo~textoheb~caminho-relativo~textoheb...
 *
 * Transformação HEB: normaliza ASCII (minúsculas, sem acentos), remove
 * vogais (a,e,i,o,u), remove verbos prováveis do português.
 */

#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <limits.h>
#include <libgen.h>
#include "../restricao.h"

/* ================================================================
 * Configuração
 * ================================================================ */

#define HEB_MAX_BYTES_PADRAO  512000
#define HEB_NOME_ARQUIVO      "heb.txt"
#define HEB_AMOSTRA_BINARIO   8192

typedef enum {
    HEB_MODO_NOME = 0,
    HEB_MODO_CONTEUDO = 1,
    HEB_MODO_HIBRIDO = 2
} HebModo;

static const char *heb_modo_nome(HebModo modo)
{
    switch (modo) {
        case HEB_MODO_CONTEUDO: return "conteudo";
        case HEB_MODO_HIBRIDO:  return "hibrido";
        default:                return "nome";
    }
}

/* Política única de contexto: sempre carregar apenas sisc/.ignorecontexto. */
static char **ctx_ignore = NULL;
static int ctx_ignore_count = 0;

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 int ctx_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';
    char *slash = strrchr(exe, '/');
    if (!slash) return -1;
    *slash = '\0'; /* .../core/comandos */
    slash = strrchr(exe, '/');
    if (!slash) return -1;
    *slash = '\0'; /* .../core */
    slash = strrchr(exe, '/');
    if (!slash) return -1;
    *slash = '\0'; /* .../sisc */
    snprintf(out, tam, "%s", exe);
    return 0;
}

static void ctx_carregar_ignore_unico(void)
{
    char raiz_sisc[PATH_MAX];
    if (ctx_raiz_sisc_por_executavel(raiz_sisc, sizeof(raiz_sisc)) != 0) return;
    char path[PATH_MAX * 2];
    snprintf(path, sizeof(path), "%s/.ignorecontexto", raiz_sisc);
    char *txt = ler_arquivo_todo(path);
    if (!txt) return;
    int cap = 32;
    ctx_ignore = calloc((size_t)cap, sizeof(char *));
    if (!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 (ctx_ignore_count >= cap) {
                cap *= 2;
                char **nx = realloc(ctx_ignore, (size_t)cap * sizeof(char *));
                if (!nx) break;
                ctx_ignore = nx;
            }
            ctx_ignore[ctx_ignore_count++] = strdup(linha);
        }
        linha = strtok_r(NULL, "\n", &save);
    }
    free(txt);
}

static void ctx_liberar_ignore(void)
{
    for (int i = 0; i < ctx_ignore_count; i++) free(ctx_ignore[i]);
    free(ctx_ignore);
    ctx_ignore = NULL;
    ctx_ignore_count = 0;
}

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

static int ctx_deve_ignorar_path(const char *path, int eh_dir)
{
    if (!path) return 0;
    size_t len = strlen(path);
    for (int i = 0; i < ctx_ignore_count; i++) {
        const char *pat = ctx_ignore[i];
        if (!pat || !*pat) continue;
        if (pat[0] == '!') {
            if (!eh_dir && strcmp(pat, "!sem-extensao") == 0) {
                const char *b = base_name_ctx(path);
                if (b && *b && !strchr(b, '.')) return 1;
            }
            continue;
        }
        size_t lp = strlen(pat);
        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;
}

/* ================================================================
 * Lista de verbos (ordenada para busca binária)
 * ================================================================ */

static const char *verbos[] = {
    "achar","achado","achando","acha","ache",
    "aplicado","aplicando","aplica","aplicar","aplique",
    "atualizado","atualizando","atualiza","atualizar","atualize",
    "chamado","chamando","chama","chamar","chame",
    "comparado","comparando","compara","comparar","compare",
    "cria","criado","criando","criam","criar","crie",
    "defina","define","definido","definindo","definir",
    "deve","devem","devendo","dever","devia","deveria","devido","devo",
    "envia","enviado","enviando","envie","enviar",
    "era","eram",
    "escrito","escreva","escreve","escrevendo","escrever",
    "esta","estado","estamos","estando","estao","estar","estava","estavam","esteja","estejam","estive","esteve","estiveram","estou",
    "executa","executado","executando","executar","execute",
    "faca","facam","faco","faz","fazem","fazendo","fazia","feito","fazer","fizeram",
    "foi","foram","fui",
    "gera","gerado","gerando","gerar","gere",
    "grava","gravado","gravando","gravar","grave",
    "ha","havendo","haver","havera","haveria","havia","haviam","havido",
    "houve",
    "ia","iam","ido","indo","ira","irao","irei","ir",
    "le","leia","lendo","ler","lido",
    "lista","listado","listando","listar","liste",
    "monte","monta","montado","montando","montar",
    "pode","podem","podendo","poder","podia","podido","possa","posso","puderam",
    "precisa","precisado","precisam","precisando","precisar","precisava","precise","precisem","preciso",
    "procura","procurado","procurando","procurar","procure",
    "quer","querem","querendo","querer","queria","querido","quero","quiseram","queira",
    "remova","remove","removendo","remover","removido",
    "retira","retirado","retirando","retirar","retire",
    "salva","salvando","salvar","salve","salvo",
    "sao","seja","sejam","sendo","ser","seria","seriam","sido","somos","sou",
    "tem","temos","tendo","tenha","tenham","tenho","teve","tinha","tinham","tido","tiveram","ter",
    "testa","testado","testando","testar","teste",
    "usa","usado","usando","usar","use",
    "vai","vamos","vao","vem","venha","venho","veio","ver","viemos","vieram","vim","vinha","vindo","vir","vimos","vindo",
    "vou",
};
static const int num_verbos = sizeof(verbos) / sizeof(verbos[0]);

/* ================================================================
 * Mapa de acentos para ASCII
 * ================================================================ */

static int mapa_acento[256];
static int mapa_inicializado = 0;

static void heb_inicializar_mapa(void)
{
    if (mapa_inicializado) return;

    /* Preenche identidade */
    for (int i = 0; i < 256; i++) {
        /* Trata bytes signed como unsigned para índice */
        mapa_acento[i] = i;
    }

    /* Minúsculas acentuadas -> vogal simples */
    struct { const char *de; char para; } tabela[] = {
        /* á à ã â ä å ā */
        {"áàãâäåā", 'a'},
        /* é è ê ë ē */
        {"éèêëē", 'e'},
        /* í ì î ï ī */
        {"íìîïī", 'i'},
        /* ó ò õ ô ö ō */
        {"óòõôöō", 'o'},
        /* ú ù û ü ū */
        {"úùûüū", 'u'},
        /* ç */
        {"ç", 'c'},
        /* ñ */
        {"ñ", 'n'},
        /* Á À Ã Â Ä Å Ā */
        {"ÁÀÃÂÄÅĀ", 'a'},
        /* É È Ê Ë Ē */
        {"ÉÈÊËĒ", 'e'},
        /* Í Ì Î Ï Ī */
        {"ÍÌÎÏĪ", 'i'},
        /* Ó Ò Õ Ô Ö Ō */
        {"ÓÒÕÔÖŌ", 'o'},
        /* Ú Ù Û Ü Ū */
        {"ÚÙÛÜŪ", 'u'},
        /* Ç */
        {"Ç", 'c'},
        /* Ñ */
        {"Ñ", 'n'},
    };

    int n = sizeof(tabela) / sizeof(tabela[0]);
    for (int i = 0; i < n; i++) {
        char para = tabela[i].para;
        const char *de = tabela[i].de;
        for (const char *p = de; *p; p++) {
            mapa_acento[(unsigned char)*p] = (unsigned char)para;
        }
    }

    mapa_inicializado = 1;
}

/* ================================================================
 * Comparação para bsearch (verbos)
 * ================================================================ */

static int cmp_verbo(const void *a, const void *b)
{
    return strcmp((const char *)a, *(const char *const *)b);
}

/* ================================================================
 * heb_eh_vogal
 * ================================================================ */

static int heb_eh_vogal(int c)
{
    return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u'
        || c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U';
}

/* ================================================================
 * heb_normalizar_ascii: minúscula + remove acentos in-place
 * ================================================================ */

static void heb_normalizar_ascii(char *texto)
{
    heb_inicializar_mapa();
    for (char *p = texto; *p; p++) {
        unsigned char c = (unsigned char)*p;
        *p = (char)mapa_acento[c];
        /* lowercase ASCII */
        if (*p >= 'A' && *p <= 'Z') {
            *p = (char)(*p + 32);
        }
    }
}

/* ================================================================
 * heb_transformar_texto
 *
 * Processa o texto e retorna uma string alocada com o resultado HEB.
 * - Substitui ~ por espaço
 * - Normaliza acentos e minúsculas
 * - Para cada palavra alfabética: remove verbos, remove vogais do resto
 * - Colapsa espaços
 * O chamador deve liberar com free().
 * ================================================================ */

static char *heb_transformar_texto(const char *texto_original)
{
    if (!texto_original) return NULL;

    size_t len = strlen(texto_original);
    char *buf = malloc(len + 1);
    if (!buf) return NULL;

    /* Copia substituindo ~ por espaço */
    for (size_t i = 0; i < len; i++) {
        buf[i] = (texto_original[i] == '~') ? ' ' : texto_original[i];
    }
    buf[len] = '\0';

    /* Normaliza acentos e lowercase */
    heb_normalizar_ascii(buf);

    /* Buffer de saída */
    char *saida = malloc(len + 1);
    if (!saida) { free(buf); return NULL; }
    size_t pos_saida = 0;

    char *p = buf;
    while (*p) {
        /* Pula não-letras, preservando como espaço */
        if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') ||
              ((unsigned char)*p >= 0xC0))) {
            /* Caractere não-alfabético */
            if (pos_saida > 0 && saida[pos_saida - 1] != ' ') {
                saida[pos_saida++] = ' ';
            }
            p++;
            continue;
        }

        /* Encontra o fim da palavra (sequência de letras) */
        const char *inicio = p;
        while (*p && (((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') ||
                       ((unsigned char)*p >= 0xC0)))) {
            p++;
        }
        size_t tam_palavra = (size_t)(p - inicio);

        /* Cópia normalizada da palavra */
        char *palavra = malloc(tam_palavra + 1);
        if (!palavra) continue;
        memcpy(palavra, inicio, tam_palavra);
        palavra[tam_palavra] = '\0';
        heb_normalizar_ascii(palavra);

        /* Remove não-letras para verificação de verbo */
        char *palavra_limpa = malloc(tam_palavra + 1);
        if (!palavra_limpa) { free(palavra); continue; }
        size_t pl = 0;
        for (size_t i = 0; i < tam_palavra; i++) {
            if (palavra[i] >= 'a' && palavra[i] <= 'z') {
                palavra_limpa[pl++] = palavra[i];
            }
        }
        palavra_limpa[pl] = '\0';

        /* Verifica se é verbo */
        int eh_verbo = 0;
        if (pl >= 2 && bsearch(palavra_limpa, verbos, num_verbos, sizeof(char *), cmp_verbo)) {
            eh_verbo = 1;
        }

        /* Também verifica sufixos verbais: ar, er, ir, ando, endo, indo, ado, ido, etc. */
        if (!eh_verbo && pl >= 4) {
            /* Verifica terminações comuns de verbo */
            static const char *sufixos[] = {
                "ando","endo","indo","ado","ada","ados","adas","ido","ida","idos","idas",
                "ava","avam","avas","avamos","asse","assem","asses",
                "esse","essem","esses","isse","issem","isses",
                "amos","emos","imos","aram","eram","iram",
                "arei","erei","irei","arao","erao","irao",
                "aria","eria","iria","arias","erias","irias",
                NULL
            };
            for (const char **s = sufixos; *s; s++) {
                size_t slen = strlen(*s);
                if (pl > slen + 2 && strcmp(palavra_limpa + pl - slen, *s) == 0) {
                    eh_verbo = 1;
                    break;
                }
            }
        }

        /* Verifica terminação ar/er/ir com radical >= 3 */
        if (!eh_verbo && pl >= 5) {
            const char *fim = palavra_limpa + pl - 2;
            if ((strcmp(fim, "ar") == 0 || strcmp(fim, "er") == 0 || strcmp(fim, "ir") == 0)) {
                eh_verbo = 1;
            }
        }

        free(palavra_limpa);

        if (eh_verbo) {
            free(palavra);
            if (pos_saida > 0 && saida[pos_saida - 1] != ' ') {
                saida[pos_saida++] = ' ';
            }
            continue;
        }

        /* Remove vogais da palavra normalizada */
        for (size_t i = 0; i < tam_palavra; i++) {
            if (!heb_eh_vogal((unsigned char)palavra[i])) {
                saida[pos_saida++] = palavra[i];
            }
        }
        free(palavra);

        /* Espaço entre palavras */
        if (pos_saida > 0 && saida[pos_saida - 1] != ' ') {
            saida[pos_saida++] = ' ';
        }
    }

    free(buf);
    saida[pos_saida] = '\0';

    /* Colapsa espaços múltiplos e trim */
    size_t w = 0;
    int espaco = 1;
    for (size_t r = 0; r < pos_saida; r++) {
        if (saida[r] == ' ') {
            if (!espaco) {
                saida[w++] = ' ';
                espaco = 1;
            }
        } else {
            saida[w++] = saida[r];
            espaco = 0;
        }
    }
    /* Remove espaço final */
    if (w > 0 && saida[w - 1] == ' ') w--;
    saida[w] = '\0';

    return saida;
}

/* ================================================================
 * heb_eh_texto: verifica se arquivo é texto (sem null bytes no início)
 * ================================================================ */

static int heb_eh_texto(const char *caminho, int max_bytes)
{
    FILE *fp = fopen(caminho, "rb");
    if (!fp) return 0;

    int tam_amostra = (max_bytes > 0 && max_bytes < HEB_AMOSTRA_BINARIO)
                      ? max_bytes : HEB_AMOSTRA_BINARIO;
    char *buf = malloc(tam_amostra);
    if (!buf) { fclose(fp); return 0; }

    size_t n = fread(buf, 1, tam_amostra, fp);
    fclose(fp);

    if (n == 0) { free(buf); return 1; } /* arquivo vazio é texto */

    for (size_t i = 0; i < n; i++) {
        if (buf[i] == '\0') { free(buf); return 0; }
    }

    free(buf);
    return 1;
}

/* ================================================================
 * heb_diretorio_ignorado: verifica se o diretório deve ser ignorado
 * ================================================================ */

static int heb_diretorio_ignorado(const char *caminho)
{
    return ctx_deve_ignorar_path(caminho, 1);
}

static int heb_arquivo_modo_atual(const char *caminho_heb, HebModo modo)
{
    FILE *fp = fopen(caminho_heb, "rb");
    if (!fp) return 0;

    char buf[256];
    size_t n = fread(buf, 1, sizeof(buf) - 1, fp);
    fclose(fp);
    buf[n] = '\0';

    char esperado[64];
    snprintf(esperado, sizeof(esperado), "__heb_meta__~modo=%s~", heb_modo_nome(modo));
    return strncmp(buf, esperado, strlen(esperado)) == 0;
}

/* ================================================================
 * heb_processar_diretorio
 *
 * Para um diretório, verifica se heb.txt precisa ser reconstruído.
 * Se sim, lê todos os arquivos de texto, transforma com HEB e grava.
 * Retorna 0 se nada foi feito, 1 se reconstruiu, -1 em erro.
 * ================================================================ */

static int heb_processar_diretorio(const char *dir_absoluto,
                                    const char *raiz,
                                    int max_bytes, int verbose,
                                    HebModo modo)
{
    char caminho_heb[PATH_MAX];
    snprintf(caminho_heb, sizeof(caminho_heb), "%s/%s", dir_absoluto, HEB_NOME_ARQUIVO);

    struct stat st_heb;
    int heb_existe = (stat(caminho_heb, &st_heb) == 0);
    time_t mtime_heb = heb_existe ? st_heb.st_mtime : 0;
    int modo_atual = heb_existe && heb_arquivo_modo_atual(caminho_heb, modo);

    /* Lista arquivos regulares no diretório */
    DIR *dirp = opendir(dir_absoluto);
    if (!dirp) return -1;

    struct dirent *ent;
    int precisa_reconstruir = !heb_existe || !modo_atual;
    int num_arquivos = 0;

    /* Coleta nomes de arquivos primeiro para verificar mtime */
    char **nomes = NULL;
    int cap_nomes = 0;
    int tot_nomes = 0;

    while ((ent = readdir(dirp)) != NULL) {
        if (ent->d_name[0] == '.') continue; /* pula ocultos, . e .. */
        if (strcmp(ent->d_name, HEB_NOME_ARQUIVO) == 0) continue; /* pula o próprio heb.txt */

        char caminho_arquivo[PATH_MAX];
        snprintf(caminho_arquivo, sizeof(caminho_arquivo), "%s/%s", dir_absoluto, ent->d_name);

        struct stat st;
        if (stat(caminho_arquivo, &st) != 0) continue;
        if (!S_ISREG(st.st_mode)) continue; /* apenas arquivos regulares */
        if (ctx_deve_ignorar_path(caminho_arquivo, 0)) continue;
        if (st.st_size > max_bytes) continue; /* respeita limite */
        if (!heb_eh_texto(caminho_arquivo, max_bytes)) continue; /* apenas texto */

        /* Verifica se é mais novo que o heb.txt */
        if (heb_existe && st.st_mtime > mtime_heb) {
            precisa_reconstruir = 1;
        }

        /* Guarda nome */
        if (tot_nomes >= cap_nomes) {
            cap_nomes = cap_nomes ? cap_nomes * 2 : 64;
            nomes = realloc(nomes, cap_nomes * sizeof(char *));
            if (!nomes) { closedir(dirp); return -1; }
        }
        nomes[tot_nomes] = strdup(ent->d_name);
        if (!nomes[tot_nomes]) { closedir(dirp); return -1; }
        tot_nomes++;
    }
    closedir(dirp);

    if (tot_nomes == 0) {
        for (int i = 0; i < tot_nomes; i++) free(nomes[i]);
        free(nomes);
        return 0; /* diretório sem arquivos de texto */
    }

    if (!precisa_reconstruir) {
        if (verbose) printf("  [ok] %s/heb.txt (atualizado, modo=%s)\n", dir_absoluto, heb_modo_nome(modo));
        for (int i = 0; i < tot_nomes; i++) free(nomes[i]);
        free(nomes);
        return 0;
    }

    /* Reconstrói heb.txt */
    /* Primeiro, gera o conteúdo em memória */
    size_t cap_saida = 65536;
    char *saida = malloc(cap_saida);
    if (!saida) {
        for (int i = 0; i < tot_nomes; i++) free(nomes[i]);
        free(nomes);
        return -1;
    }
    size_t len_saida = 0;
    saida[0] = '\0';

    len_saida += sprintf(saida + len_saida, "__heb_meta__~modo=%s~", heb_modo_nome(modo));

    /* Calcula o prefixo relativo à raiz */
    size_t len_raiz = strlen(raiz);
    const char *dir_relativo = dir_absoluto;
    if (strncmp(dir_absoluto, raiz, len_raiz) == 0) {
        dir_relativo = dir_absoluto + len_raiz;
        if (*dir_relativo == '/') dir_relativo++;
    }
    if (*dir_relativo == '\0') dir_relativo = "";

    for (int i = 0; i < tot_nomes; i++) {
        char caminho_arquivo[PATH_MAX];
        snprintf(caminho_arquivo, sizeof(caminho_arquivo), "%s/%s", dir_absoluto, nomes[i]);

        /* Constrói o caminho relativo: dir_relativo/nome */
        char caminho_rel[PATH_MAX * 2];
        if (dir_relativo[0] != '\0') {
            snprintf(caminho_rel, sizeof(caminho_rel), "%s/%s", dir_relativo, nomes[i]);
        } else {
            snprintf(caminho_rel, sizeof(caminho_rel), "%s", nomes[i]);
        }

        char *texto_heb = NULL;

        if (modo == HEB_MODO_NOME) {
            texto_heb = heb_transformar_texto(caminho_rel);
        } else {
            /* Lê o arquivo somente nos modos conteudo/hibrido */
            FILE *fp = fopen(caminho_arquivo, "rb");
            if (!fp) continue;

            fseek(fp, 0, SEEK_END);
            long tam = ftell(fp);
            if (tam < 0 || tam > max_bytes) { fclose(fp); continue; }
            fseek(fp, 0, SEEK_SET);

            char *conteudo = malloc(tam + 1);
            if (!conteudo) { fclose(fp); continue; }
            size_t lido = fread(conteudo, 1, tam, fp);
            fclose(fp);
            conteudo[lido] = '\0';

            char *conteudo_heb = heb_transformar_texto(conteudo);
            free(conteudo);
            if (!conteudo_heb) continue;

            if (modo == HEB_MODO_CONTEUDO) {
                texto_heb = conteudo_heb;
            } else {
                char *nome_heb = heb_transformar_texto(caminho_rel);
                if (!nome_heb) { free(conteudo_heb); continue; }
                size_t tam_mix = strlen(nome_heb) + strlen(conteudo_heb) + 2;
                texto_heb = malloc(tam_mix);
                if (!texto_heb) { free(nome_heb); free(conteudo_heb); continue; }
                snprintf(texto_heb, tam_mix, "%s %s", nome_heb, conteudo_heb);
                free(nome_heb);
                free(conteudo_heb);
            }
        }

        if (!texto_heb) continue;

        /* Adiciona ao buffer de saída: caminho~textoheb~ */
        size_t bloco_len = strlen(caminho_rel) + 1 + strlen(texto_heb) + 1;
        while (len_saida + bloco_len + 1 > cap_saida) {
            cap_saida *= 2;
            char *novo = realloc(saida, cap_saida);
            if (!novo) { free(texto_heb); free(saida); return -1; }
            saida = novo;
        }

        len_saida += sprintf(saida + len_saida, "%s~%s~", caminho_rel, texto_heb);
        free(texto_heb);
        num_arquivos++;
    }

    /* Grava o heb.txt */
    FILE *fp_saida = fopen(caminho_heb, "wb");
    if (!fp_saida) {
        fprintf(stderr, "  [erro] não foi possível gravar %s: %s\n",
                caminho_heb, strerror(errno));
        free(saida);
        for (int i = 0; i < tot_nomes; i++) free(nomes[i]);
        free(nomes);
        return -1;
    }

    size_t escrito = fwrite(saida, 1, len_saida, fp_saida);
    fclose(fp_saida);
    free(saida);

    if (escrito != len_saida) {
        fprintf(stderr, "  [erro] gravação incompleta em %s\n", caminho_heb);
        for (int i = 0; i < tot_nomes; i++) free(nomes[i]);
        free(nomes);
        return -1;
    }

    if (verbose) {
        printf("  [heb] %s (%d arquivos, %zu bytes, modo=%s)\n", caminho_heb, num_arquivos, len_saida, heb_modo_nome(modo));
    }

    for (int i = 0; i < tot_nomes; i++) free(nomes[i]);
    free(nomes);
    return 1;
}

/* ================================================================
 * heb_percorrer_diretorios: função recursiva
 * ================================================================ */

static int heb_percorrer_diretorios(const char *dir_absoluto,
                                     const char *raiz,
                                     int max_bytes, int verbose,
                                     HebModo modo,
                                     int *total_dirs, int *total_atualizados)
{
    int resultado = 0;

    /* Processa o diretório atual */
    int r = heb_processar_diretorio(dir_absoluto, raiz, max_bytes, verbose, modo);
    (*total_dirs)++;
    if (r == 1) (*total_atualizados)++;

    /* Recursão nos subdiretórios */
    DIR *dirp = opendir(dir_absoluto);
    if (!dirp) return -1;

    struct dirent *ent;
    while ((ent = readdir(dirp)) != NULL) {
        if (ent->d_name[0] == '.') continue;
        if (ent->d_type != DT_DIR) {
            /* d_type pode não estar disponível em todos os filesystems */
            if (ent->d_type == DT_UNKNOWN) {
                char sub_path[PATH_MAX];
                snprintf(sub_path, sizeof(sub_path), "%s/%s", dir_absoluto, ent->d_name);
                struct stat st;
                if (stat(sub_path, &st) != 0) continue;
                if (!S_ISDIR(st.st_mode)) continue;
            } else {
                continue;
            }
        }

        char sub_path[PATH_MAX];
        snprintf(sub_path, sizeof(sub_path), "%s/%s", dir_absoluto, ent->d_name);
        if (heb_diretorio_ignorado(sub_path)) continue;

        r = heb_percorrer_diretorios(sub_path, raiz, max_bytes, verbose, modo,
                                      total_dirs, total_atualizados);
        if (r == -1) {
            /* Continua mesmo com erro em subdiretório */
        }
    }
    closedir(dirp);
    return resultado;
}

/* ================================================================
 * main
 * ================================================================ */

int main(int argc, char *argv[])
{     if (restricao_verificar("atualizar-heb") != 0) return 1; 
    /* Inicializa mapa de acentos e política única de contexto */
    heb_inicializar_mapa();
    ctx_carregar_ignore_unico();

    const char *caminho_alvo = NULL;
    int max_bytes = HEB_MAX_BYTES_PADRAO;
    int verbose = 0;
    HebModo modo = HEB_MODO_NOME; /* default econômico */

    /* Parse de argumentos */
    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "--verbose") == 0) {
            verbose = 1;
        } else if (strncmp(argv[i], "--max-bytes=", 12) == 0) {
            max_bytes = atoi(argv[i] + 12);
            if (max_bytes < 1024) max_bytes = 1024;
        } else if (strncmp(argv[i], "--modo=", 7) == 0) {
            const char *m = argv[i] + 7;
            if (strcmp(m, "conteudo") == 0) {
                modo = HEB_MODO_CONTEUDO;
            } else if (strcmp(m, "hibrido") == 0) {
                modo = HEB_MODO_HIBRIDO;
            } else if (strcmp(m, "nome") == 0) {
                modo = HEB_MODO_NOME;
            } else {
                fprintf(stderr, "atualizar-heb: modo inválido: %s (use nome, conteudo ou hibrido)\n", m);
                return 1;
            }
        } else if (strcmp(argv[i], "--ajuda") == 0 || strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
            fputs(
                "Uso:\n"
                "  pp --atualizar-heb [opcoes]\n"
                "  pp --atualizar-heb --ajuda:<grupo>\n"
                "\n"
                "Descrição:\n"
                "  Cria ou atualiza arquivos heb.txt em cada diretório, com transformação HEB\n"
                "  dos nomes/caminhos e, opcionalmente, do conteúdo dos arquivos.\n"
                "  A política de exclusão de contexto vem exclusivamente de sisc/.ignorecontexto.\n"
                "\n"
                "Grupos de ajuda: uso, entrada, modos, incremental, exemplos.\n"
                "\n"
                "1) Uso e ajuda [uso ajuda]\n"
                "  --ajuda, --help, -h            Exibe esta ajuda\n"
                "  --ajuda:<grupo>                Filtra um grupo. Ex: pp --atualizar-heb --ajuda:modos\n"
                "\n"
                "2) Entrada [entrada]\n"
                "  --raiz=<caminho>               Diretório raiz a percorrer; padrão diretório atual\n"
                "  --verbose                      Exibe cada diretório processado\n"
                "  Exemplo com todas as flags do grupo:\n"
                "    pp --atualizar-heb --raiz=/caminho --verbose\n"
                "\n"
                "3) Modos HEB [modos]\n"
                "  --modo=nome                    Usa apenas caminho/nome; padrão e mais econômico\n"
                "  --modo=conteudo                Usa conteúdo dos arquivos\n"
                "  --modo=hibrido                 Usa nome/caminho + conteúdo\n"
                "  --max-bytes=N                  Máximo por arquivo em conteudo/hibrido; padrão 512000\n"
                "  Exemplo com todas as flags do grupo (--modo=nome não requer --max-bytes):\n"
                "    pp --atualizar-heb --modo=nome\n"
                "    pp --atualizar-heb --modo=hibrido --max-bytes=200000\n"
                "\n"
                "4) Incremental e formato [incremental formato]\n"
                "  Só refaz heb.txt quando ausente ou mais antigo que algum arquivo do diretório.\n"
                "  Formato: caminho-relativo~textoheb~caminho-relativo~textoheb...\n"
                "  Transformação: minúsculas, sem acentos, sem vogais e sem verbos prováveis.\n"
                "\n"
                "5) Exemplos [exemplos]\n"
                "  pp --atualizar-heb\n"
                "  pp --atualizar-heb --raiz=. --modo=nome\n"
                "  pp --atualizar-heb --modo=hibrido --max-bytes=200000 --verbose\n",
                stdout);
            return 0;
        } else if (strncmp(argv[i], "--raiz=", 7) == 0) {
            caminho_alvo = argv[i] + 7;
        } else {
            fprintf(stderr, "atualizar-heb: argumento desconhecido: %s\n", argv[i]);
            fprintf(stderr, "Use --ajuda para ver as opções.\n");
            return 1;
        }
    }

    /* Determina o caminho raiz */
    char raiz[PATH_MAX];
    if (caminho_alvo) {
        /* Resolve caminho absoluto */
        if (realpath(caminho_alvo, raiz) == NULL) {
            fprintf(stderr, "atualizar-heb: caminho inválido: %s (%s)\n",
                    caminho_alvo, strerror(errno));
            return 1;
        }
    } else {
        /* Sem caminho explícito, a raiz é o diretório de execução. */
        if (getcwd(raiz, sizeof(raiz)) == NULL) {
            fprintf(stderr, "atualizar-heb: não foi possível obter diretório atual\n");
            return 1;
        }
    }

    /* Verifica se é diretório */
    struct stat st_raiz;
    if (stat(raiz, &st_raiz) != 0 || !S_ISDIR(st_raiz.st_mode)) {
        fprintf(stderr, "atualizar-heb: '%s' não é um diretório\n", raiz);
        return 1;
    }

    if (verbose) {
        printf("Raiz: %s\n", raiz);
        printf("Modo: %s\n", heb_modo_nome(modo));
        printf("Max bytes/arquivo: %d\n", max_bytes);
        printf("\n");
    }

    int total_dirs = 0;
    int total_atualizados = 0;

    int r = heb_percorrer_diretorios(raiz, raiz, max_bytes, verbose, modo,
                                      &total_dirs, &total_atualizados);

    printf("\nDiretórios percorridos: %d\n", total_dirs);
    printf("heb.txt atualizados: %d\n", total_atualizados);
    printf("heb.txt já em dia: %d\n", total_dirs - total_atualizados);

    ctx_liberar_ignore();
    return (r == -1) ? 1 : 0;
}
