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

/*
 * atualizar-ajuda-comandos
 *
 * Padroniza os arquivos JSON do diretorio ajuda/ no formato de arvore-sa.
 * A regra central é: a única numeração visível deve ser a numeração/ID
 * gerada pelo pp --sa ao renderizar a árvore. Por isso este comando remove
 * marcadores como "1)", "2)" e listas "1.", e reorganiza linhas corridas
 * em subtópicos hierárquicos (Uso, Descrição, Grupos, Exemplos, etc.).
 */

typedef struct No {
    char *nome;
    struct No **filhos;
    size_t nfilhos;
    size_t capfilhos;
} No;

typedef struct {
    char **v;
    size_t n;
    size_t cap;
} ListaStr;

typedef struct {
    const char *s;
    size_t i;
    size_t n;
    int erro;
} Parser;

static char *trim_dup(const char *s);
static int starts_ci(const char *s, const char *pref);

static char *xstrdup(const char *s)
{
    if (!s) s = "";
    size_t n = strlen(s);
    char *p = malloc(n + 1);
    if (!p) { fprintf(stderr, "atualizar-ajuda-comandos: sem memoria\n"); exit(2); }
    memcpy(p, s, n + 1);
    return p;
}

static No *no_novo(const char *nome)
{
    No *n = calloc(1, sizeof(No));
    if (!n) { fprintf(stderr, "atualizar-ajuda-comandos: sem memoria\n"); exit(2); }
    n->nome = xstrdup(nome ? nome : "");
    return n;
}

static void no_add(No *pai, No *filho)
{
    if (!pai || !filho) return;
    if (pai->nfilhos == pai->capfilhos) {
        size_t nc = pai->capfilhos ? pai->capfilhos * 2 : 8;
        No **nv = realloc(pai->filhos, nc * sizeof(No *));
        if (!nv) { fprintf(stderr, "atualizar-ajuda-comandos: sem memoria\n"); exit(2); }
        pai->filhos = nv;
        pai->capfilhos = nc;
    }
    pai->filhos[pai->nfilhos++] = filho;
}

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

static No *no_ensure_filho(No *pai, const char *nome)
{
    for (size_t i = 0; i < pai->nfilhos; i++) {
        if (strcmp(pai->filhos[i]->nome, nome) == 0) return pai->filhos[i];
    }
    No *n = no_novo(nome);
    no_add(pai, n);
    return n;
}

static void lista_add(ListaStr *l, const char *s)
{
    if (l->n == l->cap) {
        size_t nc = l->cap ? l->cap * 2 : 16;
        char **nv = realloc(l->v, nc * sizeof(char *));
        if (!nv) { fprintf(stderr, "atualizar-ajuda-comandos: sem memoria\n"); exit(2); }
        l->v = nv;
        l->cap = nc;
    }
    l->v[l->n++] = xstrdup(s ? s : "");
}

static void lista_free(ListaStr *l)
{
    for (size_t i = 0; i < l->n; i++) free(l->v[i]);
    free(l->v);
    l->v = NULL; l->n = l->cap = 0;
}

static char *ler_arquivo(const char *path, size_t *tam)
{
    FILE *f = fopen(path, "rb");
    if (!f) return NULL;
    if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; }
    long n = ftell(f);
    if (n < 0) { fclose(f); return NULL; }
    rewind(f);
    char *buf = malloc((size_t)n + 1);
    if (!buf) { fclose(f); return NULL; }
    size_t l = fread(buf, 1, (size_t)n, f);
    int erro = ferror(f);
    fclose(f);
    if (erro) { free(buf); return NULL; }
    buf[l] = '\0';
    if (tam) *tam = l;
    return buf;
}

static int arquivo_mtime(const char *path, time_t *out)
{
    struct stat st;
    if (stat(path, &st) != 0) return 0;
    if (out) *out = st.st_mtime;
    return 1;
}

static char *shell_quote(const char *s)
{
    size_t n = 2;
    for (const char *p = s; p && *p; p++) n += (*p == '\'') ? 4 : 1;
    char *out = malloc(n + 1);
    if (!out) { fprintf(stderr, "atualizar-ajuda-comandos: sem memoria\n"); exit(2); }
    size_t o = 0;
    out[o++] = '\'';
    for (const char *p = s; p && *p; p++) {
        if (*p == '\'') { memcpy(out + o, "'\\''", 4); o += 4; }
        else out[o++] = *p;
    }
    out[o++] = '\'';
    out[o] = '\0';
    return out;
}

static int executar_comando_capturando(const char *cmd, char **out)
{
    *out = NULL;
    FILE *fp = popen(cmd, "r");
    if (!fp) return 1;
    char *buf = NULL;
    size_t len = 0, cap = 0;
    char tmp[4096];
    while (fgets(tmp, sizeof(tmp), fp)) {
        size_t l = strlen(tmp);
        if (len + l + 1 > cap) {
            size_t nc = cap ? cap * 2 : 8192;
            while (len + l + 1 > nc) nc *= 2;
            char *nb = realloc(buf, nc);
            if (!nb) { pclose(fp); free(buf); fprintf(stderr, "atualizar-ajuda-comandos: sem memoria\n"); exit(2); }
            buf = nb; cap = nc;
        }
        memcpy(buf + len, tmp, l); len += l;
    }
    int rc = pclose(fp);
    if (!buf) buf = xstrdup("");
    else buf[len] = '\0';
    *out = buf;
    return rc;
}

static void compilar_se_fonte_mais_nova(const char *raiz, const char *alvo, const char *src, const char *exe)
{
    time_t ts = 0, te = 0;
    if (!arquivo_mtime(src, &ts)) return;
    if (arquivo_mtime(exe, &te) && te >= ts) return;
    char dir[PATH_MAX * 2];
    snprintf(dir, sizeof(dir), "%s/core/comandos", raiz);
    char *qdir = shell_quote(dir);
    char *qalvo = shell_quote(alvo);
    size_t tam = strlen(qdir) + strlen(qalvo) + 64;
    char *cmd = malloc(tam);
    if (!cmd) { fprintf(stderr, "atualizar-ajuda-comandos: sem memoria\n"); exit(2); }
    snprintf(cmd, tam, "make -C %s %s >/dev/null 2>&1", qdir, qalvo);
    (void)system(cmd);
    free(cmd); free(qdir); free(qalvo);
}

static char *remover_ansi(const char *s)
{
    size_t n = strlen(s);
    char *out = malloc(n + 1);
    if (!out) { fprintf(stderr, "atualizar-ajuda-comandos: sem memoria\n"); exit(2); }
    size_t o = 0;
    for (size_t i = 0; i < n;) {
        if ((unsigned char)s[i] == 0x1b && i + 1 < n && s[i + 1] == '[') {
            i += 2;
            while (i < n && !((s[i] >= 'A' && s[i] <= 'Z') || (s[i] >= 'a' && s[i] <= 'z'))) i++;
            if (i < n) i++;
        } else out[o++] = s[i++];
    }
    out[o] = '\0';
    return out;
}

static int linha_secao_numerada(const char *s, char **titulo)
{
    const char *p = s;
    while (*p && isspace((unsigned char)*p)) p++;
    if (!isdigit((unsigned char)*p)) return 0;
    while (isdigit((unsigned char)*p)) p++;
    if (*p != ')') return 0;
    p++;
    if (*p && !isspace((unsigned char)*p)) return 0;
    while (*p && isspace((unsigned char)*p)) p++;
    *titulo = trim_dup(p);
    return **titulo != '\0';
}

static int texto_parece_ajuda(const char *s)
{
    if (!s) return 0;
    if (strstr(s, "Uso:") || strstr(s, "Uso e ajuda") || strstr(s, "Grupos de ajuda") ||
        strstr(s, "Descrição:") || strstr(s, "Descricao:")) return 1;
    while (*s && isspace((unsigned char)*s)) s++;
    return starts_ci(s, "pp --");
}

static No *arvore_bruta_de_texto_ajuda(const char *nome, const char *texto)
{
    char *sem_ansi = remover_ansi(texto ? texto : "");
    No *raiz = no_novo(nome && *nome ? nome : "ajuda");
    No *secao = no_novo("Visão geral [uso descricao grupos]");
    no_add(raiz, secao);

    char *p = sem_ansi;
    while (*p) {
        char *ini = p;
        while (*p && *p != '\n') p++;
        char save = *p;
        *p = '\0';
        size_t l = strlen(ini);
        if (l > 0 && ini[l - 1] == '\r') ini[l - 1] = '\0';
        char *limpa = trim_dup(ini);
        if (*limpa) {
            char *titulo = NULL;
            if (linha_secao_numerada(limpa, &titulo)) {
                secao = no_novo(titulo);
                no_add(raiz, secao);
                free(titulo);
            } else if (!(raiz->nfilhos == 1 && secao->nfilhos == 0 && strcmp(limpa, nome ? nome : "") == 0)) {
                no_add(secao, no_novo(ini));
            }
        }
        free(limpa);
        if (!save) break;
        p++;
    }
    free(sem_ansi);
    return raiz;
}

static void pular_ws(Parser *p)
{
    while (p->i < p->n && isspace((unsigned char)p->s[p->i])) p->i++;
}

static int consumir(Parser *p, char c)
{
    pular_ws(p);
    if (p->i < p->n && p->s[p->i] == c) { p->i++; return 1; }
    p->erro = 1;
    return 0;
}

static void sb_add(char **buf, size_t *len, size_t *cap, char c)
{
    if (*len + 1 >= *cap) {
        size_t nc = *cap ? *cap * 2 : 64;
        char *nb = realloc(*buf, nc);
        if (!nb) { fprintf(stderr, "atualizar-ajuda-comandos: sem memoria\n"); exit(2); }
        *buf = nb; *cap = nc;
    }
    (*buf)[(*len)++] = c;
}

static char *parse_string(Parser *p)
{
    pular_ws(p);
    if (p->i >= p->n || p->s[p->i] != '"') { p->erro = 1; return xstrdup(""); }
    p->i++;
    char *buf = NULL;
    size_t len = 0, cap = 0;
    while (p->i < p->n) {
        unsigned char c = (unsigned char)p->s[p->i++];
        if (c == '"') break;
        if (c == '\\' && p->i < p->n) {
            c = (unsigned char)p->s[p->i++];
            if (c == 'n') c = '\n';
            else if (c == 't') c = '\t';
            else if (c == 'r') c = '\r';
            else if (c == 'b') c = '\b';
            else if (c == 'f') c = '\f';
            else if (c == 'u') {
                /* Os arquivos atuais usam UTF-8 direto. Para \uXXXX, preserva como '?'. */
                for (int k = 0; k < 4 && p->i < p->n; k++) p->i++;
                c = '?';
            }
        }
        sb_add(&buf, &len, &cap, (char)c);
    }
    sb_add(&buf, &len, &cap, '\0');
    return buf;
}

static void skip_value(Parser *p);

static No *parse_node(Parser *p);

static void parse_array_nodes(Parser *p, No *pai)
{
    if (!consumir(p, '[')) return;
    pular_ws(p);
    if (p->i < p->n && p->s[p->i] == ']') { p->i++; return; }
    while (!p->erro && p->i < p->n) {
        pular_ws(p);
        if (p->i < p->n && p->s[p->i] == '{') no_add(pai, parse_node(p));
        else skip_value(p);
        pular_ws(p);
        if (p->i < p->n && p->s[p->i] == ',') { p->i++; continue; }
        if (p->i < p->n && p->s[p->i] == ']') { p->i++; break; }
        p->erro = 1;
    }
}

static void skip_object(Parser *p)
{
    if (!consumir(p, '{')) return;
    pular_ws(p);
    if (p->i < p->n && p->s[p->i] == '}') { p->i++; return; }
    while (!p->erro && p->i < p->n) {
        char *k = parse_string(p); free(k);
        consumir(p, ':');
        skip_value(p);
        pular_ws(p);
        if (p->i < p->n && p->s[p->i] == ',') { p->i++; continue; }
        if (p->i < p->n && p->s[p->i] == '}') { p->i++; break; }
        p->erro = 1;
    }
}

static void skip_array(Parser *p)
{
    if (!consumir(p, '[')) return;
    pular_ws(p);
    if (p->i < p->n && p->s[p->i] == ']') { p->i++; return; }
    while (!p->erro && p->i < p->n) {
        skip_value(p);
        pular_ws(p);
        if (p->i < p->n && p->s[p->i] == ',') { p->i++; continue; }
        if (p->i < p->n && p->s[p->i] == ']') { p->i++; break; }
        p->erro = 1;
    }
}

static void skip_value(Parser *p)
{
    pular_ws(p);
    if (p->i >= p->n) { p->erro = 1; return; }
    char c = p->s[p->i];
    if (c == '"') { char *s = parse_string(p); free(s); return; }
    if (c == '{') { skip_object(p); return; }
    if (c == '[') { skip_array(p); return; }
    while (p->i < p->n && !strchr(",}] \t\r\n", p->s[p->i])) p->i++;
}

static No *parse_node(Parser *p)
{
    No *n = no_novo("");
    if (!consumir(p, '{')) return n;
    pular_ws(p);
    if (p->i < p->n && p->s[p->i] == '}') { p->i++; return n; }
    while (!p->erro && p->i < p->n) {
        char *key = parse_string(p);
        consumir(p, ':');
        if (strcmp(key, "nome") == 0) {
            char *v = parse_string(p);
            free(n->nome);
            n->nome = v;
        } else if (strcmp(key, "filhos") == 0) {
            parse_array_nodes(p, n);
        } else {
            skip_value(p);
        }
        free(key);
        pular_ws(p);
        if (p->i < p->n && p->s[p->i] == ',') { p->i++; continue; }
        if (p->i < p->n && p->s[p->i] == '}') { p->i++; break; }
        p->erro = 1;
    }
    return n;
}

static No *parse_json_arvore(const char *json, size_t n)
{
    Parser p = { json, 0, n, 0 };
    No *raiz = parse_node(&p);
    if (p.erro) {
        no_free(raiz);
        return NULL;
    }
    return raiz;
}

static char *extrair_string_chave(const char *json, const char *chave)
{
    char pad[128];
    snprintf(pad, sizeof(pad), "\"%s\"", chave);
    const char *p = strstr(json, pad);
    if (!p) return NULL;
    p = strchr(p + strlen(pad), ':');
    if (!p) return NULL;
    Parser pr = { p + 1, 0, strlen(p + 1), 0 };
    char *s = parse_string(&pr);
    if (pr.erro) { free(s); return NULL; }
    return s;
}

static char *trim_dup(const char *s)
{
    if (!s) return xstrdup("");
    while (*s && isspace((unsigned char)*s)) s++;
    size_t n = strlen(s);
    while (n > 0 && isspace((unsigned char)s[n - 1])) n--;
    char *out = malloc(n + 1);
    if (!out) { fprintf(stderr, "atualizar-ajuda-comandos: sem memoria\n"); exit(2); }
    memcpy(out, s, n); out[n] = '\0';
    return out;
}

static int ascii_ci_eq_n(const char *a, const char *b, size_t n)
{
    for (size_t i = 0; i < n; i++) {
        unsigned char ca = (unsigned char)a[i], cb = (unsigned char)b[i];
        if (ca >= 'A' && ca <= 'Z') ca = (unsigned char)(ca - 'A' + 'a');
        if (cb >= 'A' && cb <= 'Z') cb = (unsigned char)(cb - 'A' + 'a');
        if (ca != cb) return 0;
    }
    return 1;
}

static int starts_ci(const char *s, const char *pref)
{
    size_t n = strlen(pref);
    return strlen(s) >= n && ascii_ci_eq_n(s, pref, n);
}

static int leading_spaces(const char *s)
{
    int n = 0;
    while (s && (*s == ' ' || *s == '\t')) { n += (*s == '\t') ? 4 : 1; s++; }
    return n;
}

static char *remover_prefixo_numerico(char *s)
{
    char *p = s;
    for (;;) {
        while (*p && isspace((unsigned char)*p)) p++;
        char *q = p;
        if (!isdigit((unsigned char)*q)) break;
        while (isdigit((unsigned char)*q)) q++;
        if ((*q == ')' && isspace((unsigned char)q[1])) || (*q == '.' && isspace((unsigned char)q[1]))) {
            p = q + 1;
            while (*p && isspace((unsigned char)*p)) p++;
            continue;
        }
        break;
    }
    return p;
}

static char *remover_marcadores_numericos_inline(const char *s)
{
    size_t n = strlen(s);
    char *out = malloc(n + 1);
    if (!out) { fprintf(stderr, "atualizar-ajuda-comandos: sem memoria\n"); exit(2); }
    size_t o = 0;
    for (size_t i = 0; i < n;) {
        if ((i == 0 || s[i - 1] == ' ' || s[i - 1] == ',' || s[i - 1] == ';') && isdigit((unsigned char)s[i])) {
            size_t j = i;
            while (j < n && isdigit((unsigned char)s[j])) j++;
            if (j < n && s[j] == ')' && j + 1 < n && isspace((unsigned char)s[j + 1])) {
                i = j + 1;
                while (i < n && isspace((unsigned char)s[i])) i++;
                continue;
            }
            if (j + 1 < n && (s[j] == 'o' || s[j] == 'O') && s[j + 1] == ')' &&
                j + 2 < n && isspace((unsigned char)s[j + 2])) {
                i = j + 2;
                while (i < n && isspace((unsigned char)s[i])) i++;
                continue;
            }
        }
        out[o++] = s[i++];
    }
    out[o] = '\0';
    return out;
}

static char *limpar_item(const char *s)
{
    char *t = trim_dup(s);
    char *p = t;
    if (p[0] == '-' && isspace((unsigned char)p[1])) p += 2;
    else if ((unsigned char)p[0] == 0xe2 && (unsigned char)p[1] == 0x80 && (unsigned char)p[2] == 0xa2) p += 3;
    p = remover_prefixo_numerico(p);
    char *sem_inline = remover_marcadores_numericos_inline(p);
    char *out = trim_dup(sem_inline);
    free(sem_inline);
    free(t);
    return out;
}

static char *limpar_titulo_topo(const char *s)
{
    char *t = limpar_item(s);
    return t;
}

static int is_flag_line(const char *s);
static int is_cmd_line(const char *s);

static void no_anexar_texto(No *n, const char *extra)
{
    if (!n || !extra || !*extra) return;
    char *e = trim_dup(extra);
    if (!*e) { free(e); return; }
    size_t ln = strlen(n->nome);
    size_t le = strlen(e);
    int precisa_espaco = (ln > 0 && !isspace((unsigned char)n->nome[ln - 1]) &&
                         e[0] != ',' && e[0] != ';' && e[0] != '.' && e[0] != ')' && e[0] != ']');
    char *novo = malloc(ln + (precisa_espaco ? 1 : 0) + le + 1);
    if (!novo) { fprintf(stderr, "atualizar-ajuda-comandos: sem memoria\n"); exit(2); }
    memcpy(novo, n->nome, ln);
    size_t o = ln;
    if (precisa_espaco) novo[o++] = ' ';
    memcpy(novo + o, e, le + 1);
    free(n->nome);
    n->nome = novo;
    free(e);
}

static int termina_com_incompleto(const char *s)
{
    if (!s) return 0;
    size_t n = strlen(s);
    while (n > 0 && isspace((unsigned char)s[n - 1])) n--;
    if (n == 0) return 0;
    char c = s[n - 1];
    if (c == ',' || c == ';' || c == ':' || c == '-' || c == '/' || c == '(' || c == '[') return 1;
    const char *conectores[] = { " e", " ou", " de", " do", " da", " dos", " das", " para", " por", " com", " sem", NULL };
    for (int i = 0; conectores[i]; i++) {
        size_t lc = strlen(conectores[i]);
        if (n >= lc && ascii_ci_eq_n(s + n - lc, conectores[i], lc)) return 1;
    }
    return 0;
}

static int comeca_com_continuacao(const char *s)
{
    if (!s) return 0;
    while (*s && isspace((unsigned char)*s)) s++;
    if (!*s) return 0;
    if (*s == ',' || *s == ';' || *s == '.' || *s == ')' || *s == ']') return 1;
    if (*s >= 'a' && *s <= 'z') return 1;
    if ((unsigned char)s[0] == 0xc3 && ((unsigned char)s[1] == 0xa9 || (unsigned char)s[1] == 0xa0 ||
        (unsigned char)s[1] == 0xa1 || (unsigned char)s[1] == 0xaa || (unsigned char)s[1] == 0xad ||
        (unsigned char)s[1] == 0xb3 || (unsigned char)s[1] == 0xba || (unsigned char)s[1] == 0xa7)) return 1;
    const char *conectores[] = {
        "e ", "ou ", "de ", "do ", "da ", "dos ", "das ", "em ", "no ", "na ", "nos ", "nas ",
        "que ", "para ", "por ", "com ", "sem ", "se ", "quando ", "onde ", "via ", "ao ", "aos ",
        "ate ", "até ", NULL
    };
    for (int i = 0; conectores[i]; i++) if (starts_ci(s, conectores[i])) return 1;
    return 0;
}

static int comeca_com_conector_continuacao(const char *s)
{
    if (!s) return 0;
    while (*s && isspace((unsigned char)*s)) s++;
    if (!*s) return 0;
    if (*s == ',' || *s == ';' || *s == '.' || *s == ')' || *s == ']') return 1;
    const char *conectores[] = {
        "e ", "ou ", "de ", "do ", "da ", "dos ", "das ", "em ", "no ", "na ", "nos ", "nas ",
        "que ", "para ", "por ", "com ", "sem ", "se ", "quando ", "onde ", "via ", "ao ", "aos ",
        "ate ", "até ", NULL
    };
    for (int i = 0; conectores[i]; i++) if (starts_ci(s, conectores[i])) return 1;
    return 0;
}

static int termina_com_pontuacao_final(const char *s)
{
    if (!s) return 0;
    size_t n = strlen(s);
    while (n > 0 && isspace((unsigned char)s[n - 1])) n--;
    if (n == 0) return 0;
    char c = s[n - 1];
    return c == '.' || c == '!' || c == '?' || c == ')' || c == ']' || c == '}' || c == '`' || c == '"' || c == '\'';
}

static int parece_item_atomico(const char *s)
{
    if (!s) return 0;
    char *t = trim_dup(s);
    int tem_espaco = 0;
    for (char *p = t; *p; p++) if (isspace((unsigned char)*p)) { tem_espaco = 1; break; }
    int r = 0;
    char *eq = strchr(t, '=');
    if (eq) {
        char *kfim = eq;
        while (kfim > t && isspace((unsigned char)kfim[-1])) kfim--;
        int chave_tem_espaco = 0;
        for (char *p = t; p < kfim; p++) if (isspace((unsigned char)*p)) { chave_tem_espaco = 1; break; }
        if (!chave_tem_espaco && kfim > t && (size_t)(kfim - t) <= 48) r = 1;
    }
    if (!r && (strstr(t, "->") || strstr(t, "→"))) r = 1;
    else if (!r && !tem_espaco && (strchr(t, '/') || strstr(t, ".json") || strstr(t, ".php") || strstr(t, ".c") || strstr(t, ".txt"))) r = 1;
    free(t);
    return r;
}

static int container_lista_atomica(const No *container)
{
    if (!container || !container->nome) return 0;
    return starts_ci(container->nome, "Grupos de ajuda") || starts_ci(container->nome, "Arquivos") ||
           starts_ci(container->nome, "Exemplos") || starts_ci(container->nome, "Saída") || starts_ci(container->nome, "Saida");
}

static int deve_anexar_quebra_linha(const char *raw, const char *limpo, No *ultimo, int ultimo_indent, No *container)
{
    int indent_atual = leading_spaces(raw);
    if (!ultimo || !limpo || !*limpo) return 0;
    if (is_flag_line(limpo) || is_cmd_line(limpo) || limpo[0] == '#') return 0;

    int incompleto = termina_com_incompleto(ultimo->nome);
    int conector = comeca_com_conector_continuacao(limpo);
    int continuacao = comeca_com_continuacao(limpo);
    int atomico = container_lista_atomica(container) || parece_item_atomico(ultimo->nome) || parece_item_atomico(limpo);

    if (limpo[0] == '(' && is_flag_line(ultimo->nome)) return 1;

    /* Linhas quebradas por largura do terminal/printf não devem virar novo item.
       Mesmo com a mesma indentação, uma continuação em minúscula deve ser anexada
       quando o item anterior ainda não terminou com pontuação final. */
    if (ultimo_indent >= 0 && indent_atual <= ultimo_indent && !incompleto && !conector) {
        if (continuacao && !atomico && !termina_com_pontuacao_final(ultimo->nome)) return 1;
        return 0;
    }

    if (indent_atual < 16 && !incompleto && !conector) return 0;
    if (continuacao && !incompleto && !conector && atomico) return 0;
    return continuacao || incompleto || conector;
}

static int is_flag_line(const char *s)
{
    return starts_ci(s, "--") || starts_ci(s, "-h") || starts_ci(s, "--ajuda") || starts_ci(s, "--help");
}

static int is_cmd_line(const char *s)
{
    return starts_ci(s, "pp ") || starts_ci(s, "./pp ");
}

static void flatten_filhos_rec(No *n, ListaStr *out, int profundidade)
{
    for (size_t i = 0; i < n->nfilhos; i++) {
        if (profundidade <= 0) {
            lista_add(out, n->filhos[i]->nome);
        } else {
            size_t esp = (size_t)profundidade * 20;
            size_t tam = esp + strlen(n->filhos[i]->nome) + 1;
            char *tmp = malloc(tam);
            if (!tmp) { fprintf(stderr, "atualizar-ajuda-comandos: sem memoria\n"); exit(2); }
            memset(tmp, ' ', esp);
            strcpy(tmp + esp, n->filhos[i]->nome);
            lista_add(out, tmp);
            free(tmp);
        }
        flatten_filhos_rec(n->filhos[i], out, profundidade + 1);
    }
}

static void flatten_filhos(No *n, ListaStr *out)
{
    flatten_filhos_rec(n, out, 0);
}

static char *sem_dois_pontos_final(const char *s)
{
    char *t = trim_dup(s);
    size_t n = strlen(t);
    while (n > 0 && isspace((unsigned char)t[n - 1])) n--;
    if (n > 0 && t[n - 1] == ':') t[n - 1] = '\0';
    return t;
}

static int detectar_heading_visao(const char *s, const char **heading, const char **resto)
{
    int marcador_de_heading = (s[0] >= 'A' && s[0] <= 'Z') || strchr(s, ':') != NULL;
    if (!marcador_de_heading) return 0;
    static const char *hs[] = { "Uso", "Descrição", "Descricao", "Grupos de ajuda", "Exemplos", "Regras importantes", NULL };
    for (int i = 0; hs[i]; i++) {
        size_t n = strlen(hs[i]);
        if (starts_ci(s, hs[i]) && (s[n] == '\0' || s[n] == ':' || isspace((unsigned char)s[n]))) {
            if (strcmp(hs[i], "Descricao") == 0) *heading = "Descrição";
            else *heading = hs[i];
            const char *p = s + n;
            while (*p && isspace((unsigned char)*p)) p++;
            if (*p == ':') p++;
            while (*p && isspace((unsigned char)*p)) p++;
            *resto = p;
            return 1;
        }
    }
    return 0;
}

static void adicionar_grupos(No *h, const char *resto)
{
    char *tmp = xstrdup(resto ? resto : "");
    char *p = tmp;
    while (*p) {
        while (*p == ',' || isspace((unsigned char)*p)) p++;
        char *ini = p;
        while (*p && *p != ',' && *p != '.') p++;
        char save = *p;
        *p = '\0';
        char *g = trim_dup(ini);
        if (*g) no_add(h, no_novo(g));
        free(g);
        if (!save) break;
        p++;
    }
    free(tmp);
}

static void adicionar_exemplos_split(No *h, const char *resto)
{
    char *tmp = xstrdup(resto ? resto : "");
    char *p = tmp;
    while (*p) {
        char *sep = strstr(p, " | ");
        if (sep) *sep = '\0';
        char *e = limpar_item(p);
        if (*e) no_add(h, no_novo(e));
        free(e);
        if (!sep) break;
        p = sep + 3;
    }
    free(tmp);
}

static No *padronizar_visao(No *orig)
{
    char *titulo = limpar_titulo_topo(orig->nome);
    No *novo = no_novo(titulo);
    free(titulo);

    ListaStr linhas = {0};
    flatten_filhos(orig, &linhas);

    No *container = NULL;
    No *ultimo = NULL;
    int ultimo_indent = -1;
    for (size_t i = 0; i < linhas.n; i++) {
        const char *raw = linhas.v[i];
        char *limpo = limpar_item(raw);
        if (!*limpo) { free(limpo); continue; }
        const char *heading = NULL, *resto = NULL;
        if (detectar_heading_visao(limpo, &heading, &resto)) {
            container = no_ensure_filho(novo, heading);
            ultimo = NULL;
            ultimo_indent = -1;
            if (resto && *resto) {
                if (strcmp(heading, "Grupos de ajuda") == 0) adicionar_grupos(container, resto);
                else if (strcmp(heading, "Exemplos") == 0) adicionar_exemplos_split(container, resto);
                else {
                    char *r = limpar_item(resto);
                    if (*r) { no_add(container, no_novo(r)); ultimo = container->filhos[container->nfilhos - 1]; ultimo_indent = 0; }
                    free(r);
                }
            }
            free(limpo);
            continue;
        }
        if (!container) container = no_ensure_filho(novo, is_cmd_line(limpo) ? "Uso" : "Descrição");
        if (deve_anexar_quebra_linha(raw, limpo, ultimo, ultimo_indent, container)) {
            no_anexar_texto(ultimo, limpo);
        } else {
            no_add(container, no_novo(limpo));
            ultimo = container->filhos[container->nfilhos - 1];
            ultimo_indent = leading_spaces(raw);
        }
        free(limpo);
    }
    lista_free(&linhas);
    return novo;
}

static int detectar_heading_secao(const char *s, char **heading, char **resto)
{
    *heading = NULL; *resto = NULL;
    if (is_flag_line(s) || is_cmd_line(s)) return 0;

    const char *colon = (s[0] == '(') ? NULL : strchr(s, ':');
    if (!colon && (starts_ci(s, "Exemplo") || strcmp(s, "Exibição") == 0 || strcmp(s, "Exibicao") == 0 ||
                   strcmp(s, "Limite") == 0 || strcmp(s, "Observação") == 0 || strcmp(s, "Observacao") == 0 ||
                   strcmp(s, "Obrigatorio") == 0 || strcmp(s, "Obrigatório") == 0 || strcmp(s, "Entrada") == 0 ||
                   strcmp(s, "Saída") == 0 || strcmp(s, "Saida") == 0)) {
        *heading = xstrdup(s);
        *resto = xstrdup("");
        return 1;
    }
    if (colon) {
        size_t pref = (size_t)(colon - s);
        int especial = starts_ci(s, "Exemplo") || starts_ci(s, "Exibição") || starts_ci(s, "Exibicao") ||
                       starts_ci(s, "Limite") || starts_ci(s, "Observação") || starts_ci(s, "Observacao") ||
                       starts_ci(s, "Obrigatorio") || starts_ci(s, "Obrigatório") || starts_ci(s, "Entrada") ||
                       starts_ci(s, "Saída") || starts_ci(s, "Saida");
        if (especial || pref <= 35) {
            char *h = malloc(pref + 1);
            if (!h) { fprintf(stderr, "atualizar-ajuda-comandos: sem memoria\n"); exit(2); }
            memcpy(h, s, pref); h[pref] = '\0';
            char *ht = sem_dois_pontos_final(h);
            free(h);
            char *rt = limpar_item(colon + 1);
            *heading = ht; *resto = rt;
            return 1;
        }
    }

    size_t n = strlen(s);
    if (n > 1 && s[n - 1] == ':') {
        *heading = sem_dois_pontos_final(s);
        *resto = xstrdup("");
        return 1;
    }
    return 0;
}

static No *padronizar_secao(No *orig)
{
    char *titulo = limpar_titulo_topo(orig->nome);
    No *novo = no_novo(titulo);
    free(titulo);

    ListaStr linhas = {0};
    flatten_filhos(orig, &linhas);

    No *container = novo;
    No *ultimo = NULL;
    int ultimo_indent = -1;
    for (size_t i = 0; i < linhas.n; i++) {
        const char *raw = linhas.v[i];
        char *limpo = limpar_item(raw);
        if (!*limpo) { free(limpo); continue; }

        char *heading = NULL, *resto = NULL;
        if (detectar_heading_secao(limpo, &heading, &resto)) {
            container = no_ensure_filho(novo, heading);
            ultimo = NULL;
            ultimo_indent = -1;
            if (resto && *resto) {
                no_add(container, no_novo(resto));
                ultimo = container->filhos[container->nfilhos - 1];
                ultimo_indent = 0;
            }
            free(heading); free(resto); free(limpo);
            continue;
        }

        if (deve_anexar_quebra_linha(raw, limpo, ultimo, ultimo_indent, container)) {
            no_anexar_texto(ultimo, limpo);
        } else if (ultimo && is_cmd_line(limpo) && leading_spaces(raw) >= 16 && container == novo) {
            no_add(ultimo, no_novo(limpo));
        } else if (ultimo && !is_flag_line(limpo) && !is_cmd_line(limpo) && limpo[0] != '#' &&
            (leading_spaces(raw) >= 16 || (limpo[0] == '(' && is_flag_line(ultimo->nome)))) {
            no_add(ultimo, no_novo(limpo));
        } else {
            no_add(container, no_novo(limpo));
            ultimo = container->filhos[container->nfilhos - 1];
            ultimo_indent = leading_spaces(raw);
        }
        free(limpo);
    }
    lista_free(&linhas);
    return novo;
}

static int is_visao_geral(const char *s)
{
    char *t = limpar_titulo_topo(s);
    int ok = starts_ci(t, "Visão geral") || starts_ci(t, "Visao geral");
    free(t);
    return ok;
}

static int is_titulo_secao_raiz(const char *s)
{
    if (is_visao_geral(s)) return 1;
    char *t = limpar_titulo_topo(s);
    int ok = strchr(t, '[') && strchr(t, ']');
    free(t);
    return ok;
}

static No *no_clone(No *orig)
{
    No *n = no_novo(orig ? orig->nome : "");
    if (orig) {
        for (size_t i = 0; i < orig->nfilhos; i++) no_add(n, no_clone(orig->filhos[i]));
    }
    return n;
}

static No *padronizar_arvore(No *orig)
{
    No *novo = no_novo(orig->nome && *orig->nome ? orig->nome : "ajuda");
    for (size_t i = 0; i < orig->nfilhos; i++) {
        No *f = orig->filhos[i];
        if (is_titulo_secao_raiz(f->nome) || novo->nfilhos == 0) {
            No *grupo_atual = no_novo(f->nome);
            for (size_t j = 0; j < f->nfilhos; j++) no_add(grupo_atual, no_clone(f->filhos[j]));
            if (is_visao_geral(grupo_atual->nome)) no_add(novo, padronizar_visao(grupo_atual));
            else no_add(novo, padronizar_secao(grupo_atual));
            no_free(grupo_atual);
        }

        while (i + 1 < orig->nfilhos && !is_titulo_secao_raiz(orig->filhos[i + 1]->nome)) {
            No *ultimo_pad = novo->nfilhos ? novo->filhos[novo->nfilhos - 1] : NULL;
            if (!ultimo_pad) break;
            No *tmp = no_novo(ultimo_pad->nome);
            for (size_t j = 0; j < ultimo_pad->nfilhos; j++) no_add(tmp, no_clone(ultimo_pad->filhos[j]));
            i++;
            no_add(tmp, no_clone(orig->filhos[i]));
            no_free(ultimo_pad);
            novo->filhos[novo->nfilhos - 1] = is_visao_geral(tmp->nome) ? padronizar_visao(tmp) : padronizar_secao(tmp);
            no_free(tmp);
        }
    }
    return novo;
}

static No *tentar_regerar_de_comando(const char *raiz, const char *alvo, const char *path_ajuda, int silencioso)
{
    if (!raiz || !alvo || !*alvo) return NULL;
    char src[PATH_MAX * 2], exe[PATH_MAX * 2];
    snprintf(src, sizeof(src), "%s/core/comandos/%s.c", raiz, alvo);
    snprintf(exe, sizeof(exe), "%s/core/comandos/%s", raiz, alvo);

    time_t tajuda = 0, tsrc = 0, texe = 0;
    if (!arquivo_mtime(path_ajuda, &tajuda)) tajuda = 0;
    int tem_src = arquivo_mtime(src, &tsrc);
    int tem_exe = arquivo_mtime(exe, &texe);
    if (!tem_src && !tem_exe) return NULL;
    time_t tcmd = tem_src ? tsrc : texe;
    if (tajuda > 0 && tcmd <= tajuda) return NULL;

    if (tem_src) compilar_se_fonte_mais_nova(raiz, alvo, src, exe);
    if (!arquivo_mtime(exe, &texe)) return NULL;
    if (tem_src && texe < tsrc) {
        if (!silencioso) fprintf(stderr, "atualizar-ajuda-comandos: %s.c e mais recente, mas o executavel nao foi recompilado; mantido conteudo atual\n", alvo);
        return NULL;
    }

    char *qexe = shell_quote(exe);
    size_t tam = strlen(qexe) + 32;
    char *cmd = malloc(tam);
    if (!cmd) { fprintf(stderr, "atualizar-ajuda-comandos: sem memoria\n"); exit(2); }
    snprintf(cmd, tam, "%s --ajuda 2>&1", qexe);

    char *saida = NULL;
    (void)executar_comando_capturando(cmd, &saida);
    free(cmd); free(qexe);

    if (!texto_parece_ajuda(saida)) {
        if (!silencioso) fprintf(stderr, "atualizar-ajuda-comandos: %s parece mais novo, mas nao retornou ajuda valida; mantido conteudo atual\n", alvo);
        free(saida);
        return NULL;
    }

    No *bruta = arvore_bruta_de_texto_ajuda(alvo, saida);
    No *pad = padronizar_arvore(bruta);
    no_free(bruta);
    free(saida);
    if (!silencioso) printf("sincronizado do comando: %s\n", alvo);
    return pad;
}

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

static int sa_exec(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 dn = open("/dev/null", O_WRONLY);
        if (dn >= 0) { dup2(dn, STDOUT_FILENO); dup2(dn, STDERR_FILENO); close(dn); }
        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(const char *pp, const char *path, No *n, const char *pai, const char *id, int confirmar)
{
    char saarg[PATH_MAX * 2 + 8], ag[4096], emarg[256];
    snprintf(saarg, sizeof(saarg), "--sa=%s", path);
    snprintf(ag, sizeof(ag), "--ag=%s", n->nome ? n->nome : "");
    char *args[5]; int p = 0;
    args[p++] = (char *)pp; args[p++] = saarg; args[p++] = ag;
    if (pai && *pai) { snprintf(emarg, sizeof(emarg), "--em=%s", pai); args[p++] = emarg; }
    args[p] = NULL;
    if (sa_exec(args, confirmar) != 0) return -1;
    for (size_t i = 0; i < n->nfilhos; i++) {
        char cid[256]; snprintf(cid, sizeof(cid), "%s%s%zu", id && *id ? id : "", id && *id ? "." : "", i + 1);
        if (materializar_no_sa(pp, path, n->filhos[i], id, cid, 0) != 0) return -1;
    }
    return 0;
}

static int escrever_arvore_via_sa(const char *raiz_sisc, const char *path, No *raiz)
{
    if (access(path, F_OK) == 0 && !arquivo_eh_arvore_sa(path)) {
        fprintf(stderr, "atualizar-ajuda-comandos: arquivo existente não é árvore SA; recusando sobrescrever: %s\n", path);
        return -1;
    }
    char pp[PATH_MAX * 2];
    snprintf(pp, sizeof(pp), "%s/pp", raiz_sisc ? raiz_sisc : ".");
    unlink(path);
    for (size_t i = 0; i < raiz->nfilhos; i++) {
        char id[64]; snprintf(id, sizeof(id), "%zu", i + 1);
        if (materializar_no_sa(pp, path, raiz->filhos[i], NULL, id, i == 0) != 0) return -1;
    }
    return 0;
}

static int processar_arquivo(const char *path, int silencioso, const char *raiz, const char *alvo)
{
    size_t tam = 0;
    char *json = ler_arquivo(path, &tam);
    if (!json) {
        fprintf(stderr, "atualizar-ajuda-comandos: nao foi possivel ler %s: %s\n", path, strerror(errno));
        return 1;
    }

    char *criado = extrair_string_chave(json, "criadoEm");
    No *novo = tentar_regerar_de_comando(raiz, alvo, path, silencioso);
    No *orig = NULL;

    if (!novo) {
        orig = parse_json_arvore(json, tam);
        if (!orig) {
            fprintf(stderr, "atualizar-ajuda-comandos: JSON invalido em %s\n", path);
            free(criado);
            free(json);
            return 1;
        }
        novo = padronizar_arvore(orig);
    }

    int rc = escrever_arvore_via_sa(raiz, path, novo);
    if (rc != 0) fprintf(stderr, "atualizar-ajuda-comandos: erro ao gravar %s: %s\n", path, strerror(errno));
    else if (!silencioso) printf("atualizado: %s\n", path);
    free(criado);
    no_free(novo);
    no_free(orig);
    free(json);
    return rc != 0;
}

static int cmp_strptr(const void *a, const void *b)
{
    const char * const *sa = (const char * const *)a;
    const char * const *sb = (const char * const *)b;
    return strcmp(*sa, *sb);
}

static int nome_seguro(const char *s)
{
    if (!s || !*s) return 0;
    for (const char *p = s; *p; p++) {
        unsigned char c = (unsigned char)*p;
        if (!(isalnum(c) || c == '.' || c == '_' || c == '-')) return 0;
    }
    return 1;
}

static void normalizar_alvo(const char *in, char *out, size_t outsz)
{
    if (!out || outsz == 0) return;
    out[0] = '\0';
    if (!in) return;
    while (*in == '-') in++;
    if (strncmp(in, "atualizar-ajuda-comandos=", 25) == 0) in += 25;
    size_t n = 0;
    while (in[n] && !isspace((unsigned char)in[n]) && in[n] != '=') n++;
    if (n >= outsz) n = outsz - 1;
    memcpy(out, in, n); out[n] = '\0';
    size_t l = strlen(out);
    if (l > 5 && strcmp(out + l - 5, ".json") == 0) out[l - 5] = '\0';
}

static void ajuda(void)
{
    puts("Uso:");
    puts("  pp --atualizar-ajuda-comandos [comando]");
    puts("  pp --atualizar-ajuda-comandos=<comando>");
    puts("  pp --atualizar-ajuda-comandos --comando=<comando>");
    puts("");
    puts("Descrição:");
    puts("  Padroniza ajuda/*.json para que a estrutura seja hierarquica por assunto.");
    puts("  Sem valor, atualiza todos os comandos e agentes. Com valor, atualiza só ajuda/<comando>.json.");
    puts("  Se core/comandos/<comando>.c estiver mais recente que ajuda/<comando>.json,");
    puts("  recria a ajuda a partir de '<comando> --ajuda' antes de padronizar.");
    puts("  Quando nao houver .c correspondente, usa a data do executavel como referencia.");
    puts("  Quebras de linha de uma mesma frase sao anexadas ao item anterior; nao viram novo item.");
    puts("");
    puts("Grupos de ajuda: uso, alvo, sincronizacao, padronizacao.");
}

int main(int argc, char **argv)
{
    if (argc > 1 && (!strcmp(argv[1], "--ajuda") || !strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) {
        ajuda();
        return 0;
    }

    char exe[PATH_MAX];
    ssize_t n = readlink("/proc/self/exe", exe, sizeof(exe) - 1);
    if (n < 0) { perror("atualizar-ajuda-comandos: readlink"); return 1; }
    exe[n] = '\0';

    char raiz[PATH_MAX];
    snprintf(raiz, sizeof(raiz), "%s", exe);
    char *p = strrchr(raiz, '/'); if (p) *p = '\0';          /* core/comandos */
    p = strrchr(raiz, '/'); if (p) *p = '\0';                /* core */
    p = strrchr(raiz, '/'); if (p) *p = '\0';                /* raiz */

    char ajuda_dir[PATH_MAX * 2];
    snprintf(ajuda_dir, sizeof(ajuda_dir), "%s/ajuda", raiz);

    char alvo[PATH_MAX] = "";
    if (argc > 1) normalizar_alvo(argv[1], alvo, sizeof(alvo));
    for (int i = 2; i < argc; i++) {
        if (strncmp(argv[i], "--comando=", 10) == 0 || strncmp(argv[i], "--alvo=", 7) == 0) {
            const char *v = strchr(argv[i], '=');
            normalizar_alvo(v ? v + 1 : "", alvo, sizeof(alvo));
        }
    }

    if (alvo[0]) {
        if (!nome_seguro(alvo)) {
            fprintf(stderr, "atualizar-ajuda-comandos: nome de comando/agente invalido: %s\n", alvo);
            return 1;
        }
        char path[PATH_MAX * 3];
        snprintf(path, sizeof(path), "%s/%s.json", ajuda_dir, alvo);
        if (access(path, R_OK) != 0) {
            fprintf(stderr, "atualizar-ajuda-comandos: arquivo nao encontrado: %s\n", path);
            return 1;
        }
        return processar_arquivo(path, 0, raiz, alvo);
    }

    DIR *d = opendir(ajuda_dir);
    if (!d) { perror("atualizar-ajuda-comandos: opendir ajuda"); return 1; }
    ListaStr arqs = {0};
    struct dirent *e;
    while ((e = readdir(d)) != NULL) {
        size_t l = strlen(e->d_name);
        if (l > 5 && strcmp(e->d_name + l - 5, ".json") == 0) lista_add(&arqs, e->d_name);
    }
    closedir(d);
    qsort(arqs.v, arqs.n, sizeof(char *), cmp_strptr);

    int erros = 0;
    for (size_t i = 0; i < arqs.n; i++) {
        char path[PATH_MAX * 3];
        snprintf(path, sizeof(path), "%s/%s", ajuda_dir, arqs.v[i]);
        char alvo_arq[PATH_MAX];
        snprintf(alvo_arq, sizeof(alvo_arq), "%s", arqs.v[i]);
        size_t la = strlen(alvo_arq);
        if (la > 5 && strcmp(alvo_arq + la - 5, ".json") == 0) alvo_arq[la - 5] = '\0';
        erros += processar_arquivo(path, 0, raiz, alvo_arq);
    }
    printf("resumo: %zu arquivo(s) processado(s), %d erro(s).\n", arqs.n, erros);
    lista_free(&arqs);
    return erros ? 1 : 0;
}
