/*
 * parallm - Envia prompt para LLM com contexto compacto/inteligente do projeto
 * Uso: pp --parallm="texto" [--conta-llm=arquivo] [--rel] [--sem-contexto] [--arvore[=<nome>]] [--nivel=0..3] [--verbose] [--executar]
 * Observação: --executar/--agente-at é necessário para acionar o agente-at.
 */

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

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

#define PARALLM_TIMEOUT_SEG 120
#define PARALLM_MAX_RESPOSTA (4 * 1024 * 1024)  /* 4 MB */
#define PARALLM_CTX_MAX_ARQUIVOS 10
#define PARALLM_CTX_MAX_BYTES 64000
#define PARALLM_CTX_MAX_CANDIDATOS 200
#define PARALLM_ARV_MAX_TRECHOS 24
#define PARALLM_ARV_MAX_TRECHOS_POR_ARQUIVO 6
#define PARALLM_SA_CTX_MAX_BYTES_SISTEMA 52000
#define PARALLM_SA_CTX_MAX_BYTES_FERRAMENTA 32000
#define PARALLM_SA_CTX_MAX_ITENS 30

/* ================================================================
 * Mini JSON: extrai valor de string por chave (formato simples)
 * ================================================================ */

/* Extrai o valor string de uma chave no formato "chave": "valor"
 * Retorna cópia alocada ou NULL. Suporta escape básico (\", \\). */
static char *json_extrair_string(const char *json, const char *chave)
{
    if (!json || !chave) return NULL;

    /* Monta padrão: "chave" */
    size_t len_chave = strlen(chave);
    char *padrao = malloc(len_chave + 4);
    if (!padrao) return NULL;
    sprintf(padrao, "\"%s\"", chave);

    const char *pos = strstr(json, padrao);
    free(padrao);
    if (!pos) return NULL;

    /* Avança até depois dos : */
    pos = strchr(pos + 1, ':');
    if (!pos) return NULL;
    pos++;

    /* Pula espaços */
    while (*pos == ' ' || *pos == '\t' || *pos == '\n' || *pos == '\r') pos++;

    if (*pos != '"') return NULL;
    pos++; /* pula a primeira aspa */

    /* Lê o valor até a aspa de fechamento */
    size_t cap = 256;
    char *valor = malloc(cap);
    if (!valor) return NULL;
    size_t len = 0;

    while (*pos && *pos != '"') {
        if (*pos == '\\') {
            pos++;
            if (*pos == '"' || *pos == '\\' || *pos == '/' || *pos == 'n' || *pos == 't' || *pos == 'r') {
                char c = *pos;
                if (c == 'n') c = '\n';
                else if (c == 't') c = '\t';
                else if (c == 'r') c = '\r';
                if (len + 1 >= cap) { cap *= 2; valor = realloc(valor, cap); if (!valor) return NULL; }
                valor[len++] = c;
                pos++;
                continue;
            }
            /* escape Unicode \uXXXX — ignora simplificado */
            if (*pos == 'u') { pos += 5; continue; }
        }
        if (len + 1 >= cap) { cap *= 2; valor = realloc(valor, cap); if (!valor) return NULL; }
        valor[len++] = *pos;
        pos++;
    }
    valor[len] = '\0';
    return valor;
}

static char *json_extrair_string_ate(const char *json, const char *chave, const char *limite)
{
    if (!json || !chave) return NULL;
    size_t len_chave = strlen(chave);
    char *padrao = malloc(len_chave + 4);
    if (!padrao) return NULL;
    sprintf(padrao, "\"%s\"", chave);
    const char *pos = strstr(json, padrao);
    free(padrao);
    if (!pos || (limite && pos >= limite)) return NULL;
    pos = strchr(pos + 1, ':');
    if (!pos || (limite && pos >= limite)) return NULL;
    pos++;
    while (*pos && isspace((unsigned char)*pos)) pos++;
    if ((limite && pos >= limite) || *pos != '"') return NULL;
    pos++;
    size_t cap = 256, len = 0;
    char *valor = malloc(cap);
    if (!valor) return NULL;
    while (*pos && *pos != '"' && (!limite || pos < limite)) {
        char c = *pos++;
        if (c == '\\' && *pos && (!limite || pos < limite)) {
            c = *pos++;
            if (c == 'n') c = '\n';
            else if (c == 't') c = '\t';
            else if (c == 'r') c = '\r';
        }
        if (len + 1 >= cap) {
            cap *= 2;
            char *novo = realloc(valor, cap);
            if (!novo) { free(valor); return NULL; }
            valor = novo;
        }
        valor[len++] = c;
    }
    valor[len] = '\0';
    return valor;
}

static char *mapa_comandos_primeiro_filho_nome(const char *obj)
{
    const char *filhos = strstr(obj, "\"filhos\"");
    if (!filhos) return NULL;
    const char *abre = strchr(filhos, '[');
    if (!abre) return NULL;
    const char *fecha = strchr(abre, ']');
    const char *nome = strstr(abre, "\"nome\"");
    if (!nome || !fecha || nome > fecha) return NULL;
    return json_extrair_string_ate(nome, "nome", fecha);
}

static char *mapa_comandos_sa_conta(const char *json, const char *comando)
{
    char *tipo = json_extrair_string(json, "tipo");
    int eh_sa = tipo && strcmp(tipo, "arvore-sa") == 0;
    free(tipo);
    if (!eh_sa || !comando || !*comando) return NULL;

    char alt[PATH_MAX];
    snprintf(alt, sizeof(alt), "%s%s", strncmp(comando, "--", 2) == 0 ? "" : "--", comando);
    const char *p = json;
    while ((p = strstr(p, "\"nome\"")) != NULL) {
        const char *prox = strstr(p + 6, "\"nome\"");
        char *nome = json_extrair_string_ate(p, "nome", prox);
        int achou = nome && (strcmp(nome, comando) == 0 || strcmp(nome, alt) == 0);
        free(nome);
        if (achou) {
            char *conta = json_extrair_string_ate(p, "mm", prox);
            if (!conta) conta = json_extrair_string_ate(p, "valor", prox);
            if (!conta) conta = mapa_comandos_primeiro_filho_nome(p);
            return conta;
        }
        p += 6;
    }
    return NULL;
}

/* Extrai valor inteiro de uma chave: "chave": 123 ou "chave": null */
static long json_extrair_int(const char *json, const char *chave, long padrao)
{
    size_t len_chave = strlen(chave);
    char *padrao_str = malloc(len_chave + 4);
    if (!padrao_str) return padrao;
    sprintf(padrao_str, "\"%s\"", chave);

    const char *pos = strstr(json, padrao_str);
    free(padrao_str);
    if (!pos) return padrao;

    pos = strchr(pos + 1, ':');
    if (!pos) return padrao;
    pos++;
    while (*pos == ' ' || *pos == '\t' || *pos == '\n' || *pos == '\r') pos++;

    if (strncmp(pos, "null", 4) == 0) return padrao;
    return strtol(pos, NULL, 10);
}

/* Lê um arquivo inteiro e retorna string alocada */
static char *ler_arquivo(const char *caminho)
{
    FILE *fp = fopen(caminho, "rb");
    if (!fp) return NULL;

    fseek(fp, 0, SEEK_END);
    long tam = ftell(fp);
    if (tam < 0 || tam > (10 * 1024 * 1024)) { fclose(fp); return NULL; }
    fseek(fp, 0, SEEK_SET);

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

/* ================================================================
 * Tokenização e similaridade de palavras
 * ================================================================ */

static void normalizar_ascii(char *s);  /* definida abaixo, na seção de acentos */

/* Lista de stopwords em português (palavras comuns sem valor semântico) */
static const char *stopwords[] = {
    "me", "te", "se", "o", "a", "os", "as", "um", "uma", "uns", "umas",
    "de", "do", "da", "dos", "das", "no", "na", "nos", "nas", "em", "para",
    "por", "com", "sem", "sob", "sobre", "entre", "e", "ou", "que", "qual",
    "quais", "quem", "quando", "onde", "como", "fale", "diga", "explique",
    "mostre", "liste", "falar", "sobre", "info", "informacao", "informacoes",
    "mais", "menos", "muito", "pouco", "todo", "toda", "todos", "todas",
    "este", "esta", "estes", "estas", "esse", "essa", "esses", "essas",
    "aquele", "aquela", "aqueles", "aquelas", "isto", "isso", "aquilo",
    "meu", "minha", "meus", "minhas", "seu", "sua", "seus", "suas",
    "nosso", "nossa", "nossos", "nossas", "ele", "ela", "eles", "elas",
    "voce", "voces", "eu", "nos", "lhe", "lhes", "ao", "aos", "pelo", "pela",
    "pelos", "pelas", "ha", "ja", "ainda", "tambem", "apenas", "so",
    "nao", "sim", "ser", "ter", "estar", "fazer", "dizer", NULL
};

static int eh_stopword(const char *palavra)
{
    for (int i = 0; stopwords[i]; i++)
        if (strcmp(palavra, stopwords[i]) == 0) return 1;
    return 0;
}

/* Tokeniza texto em palavras normalizadas (>=2 chars, sem stopwords).
 * Retorna array alocado de strings; num_tokens é preenchido.
 * O chamador deve usar liberar_tokens() para liberar. */
static char **tokenizar_palavras(const char *texto, int *num_tokens)
{
    *num_tokens = 0;
    if (!texto) return NULL;

    char *norm = strdup(texto);
    if (!norm) return NULL;
    normalizar_ascii(norm);
    for (char *c = norm; *c; c++)
        if (*c >= 'A' && *c <= 'Z') *c = (char)(*c + 32);

    int cap = 32;
    char **tokens = malloc(cap * sizeof(char *));
    if (!tokens) { free(norm); return NULL; }

    char *save = NULL;
    char *tok = strtok_r(norm, " \t\r\n,.;:()[]{}<>\"'`/\\|=+-_~!@#$%^&*?", &save);
    while (tok) {
        size_t len = strlen(tok);
        if (len >= 2 && !eh_stopword(tok)) {
            if (*num_tokens >= cap) { cap *= 2; char **nx = realloc(tokens, cap * sizeof(char *)); if (!nx) break; tokens = nx; }
            tokens[*num_tokens] = strdup(tok);
            (*num_tokens)++;
        }
        tok = strtok_r(NULL, " \t\r\n,.;:()[]{}<>\"'`/\\|=+-_~!@#$%^&*?", &save);
    }
    free(norm);
    return tokens;
}

static void liberar_tokens(char **tokens, int n)
{
    if (!tokens) return;
    for (int i = 0; i < n; i++) free(tokens[i]);
    free(tokens);
}

/* Similaridade Jaccard + bônus de bigrama entre dois conjuntos de palavras.
 * Retorna 0.0 (sem sobreposição) a 1.0 (idênticos).
 * Bigramas consecutivos em A que aparecem consecutivos em B recebem peso extra. */
static double similaridade_jaccard(char **tokens_a, int na, char **tokens_b, int nb)
{
    if (na == 0 || nb == 0) return 0.0;

    int intersecao = 0;
    for (int i = 0; i < na; i++) {
        for (int j = 0; j < nb; j++) {
            if (strcmp(tokens_a[i], tokens_b[j]) == 0) {
                intersecao++;
                break;
            }
        }
    }

    int uniao = na + nb - intersecao;
    if (uniao == 0) return 0.0;
    double base = (double)intersecao / (double)uniao;

    /* Bônus por bigramas consecutivos */
    int bigram_match = 0;
    if (na >= 2 && nb >= 2) {
        for (int i = 0; i < na - 1; i++) {
            for (int j = 0; j < nb - 1; j++) {
                if (strcmp(tokens_a[i], tokens_b[j]) == 0 &&
                    strcmp(tokens_a[i+1], tokens_b[j+1]) == 0) {
                    bigram_match++;
                    break;
                }
            }
        }
    }
    double bigram_bonus = na > 1 ? (double)bigram_match / (double)(na - 1) : 0.0;
    if (bigram_bonus > 1.0) bigram_bonus = 1.0;

    return base * 0.7 + bigram_bonus * 0.3;
}

/* ================================================================
 * Chunking de arquivos grandes
 * ================================================================ */

#define CHUNK_TAM 3000

typedef struct {
    char *texto;
    size_t tamanho;
    double score;
} Chunk;

static void liberar_chunks(Chunk *chunks, int n)
{
    if (!chunks) return;
    for (int i = 0; i < n; i++) free(chunks[i].texto);
    free(chunks);
}

/* Detecta início de função C: linha com tipo_retorno nome(params) */
static int eh_inicio_funcao_c(const char *linha)
{
    while (*linha == ' ' || *linha == '\t') linha++;
    if (*linha == '\0' || *linha == '\n' || *linha == '#') return 0;
    if (*linha == '/' || *linha == '*' || *linha == '{' || *linha == '}') return 0;
    const char *abre = strchr(linha, '(');
    if (!abre) return 0;
    const char *fecha = strchr(abre, ')');
    if (!fecha) return 0;
    /* Verifica se antes do ( so tem caracteres de identificador, whitespace ou * */
    const char *id = linha;
    while (id < abre && (*id == ' ' || *id == '\t' || *id == '*' ||
           (*id >= 'a' && *id <= 'z') || (*id >= 'A' && *id <= 'Z') ||
           (*id >= '0' && *id <= '9') || *id == '_')) id++;
    if (id != abre) return 0;
    return 1;
}

/* Divide conteúdo em chunks de ~CHUNK_TAM bytes.
 * Para código C, tenta dividir em limites de função.
 * Para outros arquivos, corta em limites de linha. */
static int chunk_arquivo(const char *conteudo, Chunk **out_chunks, int *num_chunks)
{
    *num_chunks = 0;
    *out_chunks = NULL;
    if (!conteudo || !conteudo[0]) return 0;

    size_t total = strlen(conteudo);
    if (total <= CHUNK_TAM * 2) {
        Chunk *c = malloc(sizeof(Chunk));
        if (!c) return -1;
        c->texto = strdup(conteudo);
        c->tamanho = total;
        c->score = 0.0;
        *out_chunks = c;
        *num_chunks = 1;
        return 0;
    }

    int eh_codigo = (strstr(conteudo, "#include") != NULL ||
                     strstr(conteudo, "static ") != NULL ||
                     strstr(conteudo, "int main") != NULL);

    int cap = (int)(total / CHUNK_TAM) + 4;
    Chunk *chunks = malloc(cap * sizeof(Chunk));
    if (!chunks) return -1;

    const char *p = conteudo;
    const char *fim = conteudo + total;
    int nc = 0;

    while (p < fim && nc < cap) {
        const char *inicio_chunk = p;
        size_t restante = (size_t)(fim - p);

        if (restante <= CHUNK_TAM) {
            chunks[nc].texto = strdup(p);
            chunks[nc].tamanho = restante;
            chunks[nc].score = 0.0;
            p = fim;
        } else {
            const char *corte = p + CHUNK_TAM;
            if (corte >= fim) corte = fim - 1;

            if (eh_codigo) {
                /* Procura próximo início de função após o corte */
                const char *nl = corte;
                while (nl < fim - 1 && *nl != '\n') nl++;
                const char *prox_linha = (*nl == '\n') ? nl + 1 : nl;
                while (prox_linha < fim - 100) {
                    if (eh_inicio_funcao_c(prox_linha)) {
                        corte = prox_linha;
                        break;
                    }
                    const char *nnl = strchr(prox_linha, '\n');
                    if (!nnl) break;
                    prox_linha = nnl + 1;
                    if ((size_t)(prox_linha - inicio_chunk) > CHUNK_TAM * 2) break;
                }
                if (corte == p + CHUNK_TAM) {
                    while (corte < fim && *corte != '\n') corte++;
                    if (*corte == '\n') corte++;
                }
            } else {
                while (corte < fim && *corte != '\n') corte++;
                if (*corte == '\n') corte++;
            }

            if (corte <= p || (size_t)(corte - inicio_chunk) > CHUNK_TAM * 3) {
                corte = p + CHUNK_TAM;
                if (corte > fim) corte = fim;
            }

            size_t tam = (size_t)(corte - inicio_chunk);
            chunks[nc].texto = malloc(tam + 1);
            if (chunks[nc].texto) {
                memcpy(chunks[nc].texto, inicio_chunk, tam);
                chunks[nc].texto[tam] = '\0';
                chunks[nc].tamanho = tam;
                chunks[nc].score = 0.0;
            }
            p = corte;
        }
        nc++;
    }

    *out_chunks = chunks;
    *num_chunks = nc;
    return 0;
}

/* ================================================================
 * Extrai descrição do projeto do operador-manual-funcional.json
 * ================================================================ */

static char *extrair_descricao_projeto(const char *raiz)
{
    char path[PATH_MAX * 2];
    snprintf(path, sizeof(path), "%s/arvores/operador-manual-funcional.json", raiz);
    char *json = ler_arquivo(path);
    if (!json) return NULL;

    /* Navega pela estrutura real do JSON: raiz -> "filhos" -> primeiro filho -> "nome".
     * Isso é robusto contra reordenação de campos e variações entre sistemas,
     * desde que o formato arvore-sa seja mantido. */
    const char *filhos = strstr(json, "\"filhos\": [");
    if (!filhos) { free(json); return NULL; }

    /* Primeiro "nome" dentro do array filhos (descrição do sistema) */
    const char *pos = strstr(filhos, "\"nome\": \"");
    if (!pos) { free(json); return NULL; }
    pos += 9;  /* pula "nome": " */

    const char *fim = strchr(pos, '"');
    if (!fim) { free(json); return NULL; }

    size_t tam = (size_t)(fim - pos);
    if (tam > 512) tam = 512;
    char *desc = malloc(tam + 1);
    if (!desc) { free(json); return NULL; }
    memcpy(desc, pos, tam);
    desc[tam] = '\0';
    free(json);
    return desc;
}

/* ================================================================
 * Filtros de contexto via .ignorecontexto
 * ================================================================ */

static char **ctx_ignore = NULL;
static int   ctx_ignore_count = 0;

static int raiz_sisc_por_executavel_contexto(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, '/'); /* .../core/comandos/parallm */
    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(const char *raiz)
{
    char raiz_sisc[PATH_MAX];
    char path[PATH_MAX * 2];
    if (raiz_sisc_por_executavel_contexto(raiz_sisc, sizeof(raiz_sisc)) == 0)
        snprintf(path, sizeof(path), "%s/.ignorecontexto", raiz_sisc);
    else
        snprintf(path, sizeof(path), "%s/.ignorecontexto", raiz);
    char *txt = ler_arquivo(path);
    if (!txt) return;
    int cap = 32;
    ctx_ignore = malloc(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++;
        if (*linha && *linha != '#') {
            if (ctx_ignore_count >= cap) { cap *= 2; char **nx = realloc(ctx_ignore, 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 int ctx_deve_ignorar_n_modo(const char *ra, size_t len, int eh_dir)
{
    if (!ra) return 0;
    for (int i = 0; i < ctx_ignore_count; i++) {
        const char *pat = ctx_ignore[i];
        if (!pat) continue;

        /* Regras especiais com prefixo ! */
        if (pat[0] == '!') {
            if (!eh_dir && strcmp(pat, "!sem-extensao") == 0) {
                /* Arquivo sem extensão: basename não contém '.' */
                const char *barra = ra;
                for (size_t j = 0; j < len; j++)
                    if (ra[j] == '/') barra = ra + j;
                if (*barra == '/') barra++;
                if (!memchr(barra, '.', len - (size_t)(barra - ra)))
                    return 1;
            }
            continue;
        }

        size_t lp = strlen(pat);
        if (lp == 0 || lp > len) continue;
        for (size_t p = 0; p + lp <= len; p++)
            if (strncmp(ra + p, pat, lp) == 0) return 1;

        /* Diretório informado como /nome/ também deve casar com .../nome sem barra final. */
        if (eh_dir && lp >= 2 && pat[0] == '/' && pat[lp - 1] == '/') {
            size_t core = lp - 1;
            if (len >= core && strncmp(ra + len - core, pat, core) == 0) return 1;
        }
    }
    return 0;
}

static int ctx_deve_ignorar_n(const char *ra, size_t len)
{
    return ctx_deve_ignorar_n_modo(ra, len, 0);
}

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

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

static void resolver_subdir_sistema(const char *raiz, const char *nome, char *out, size_t tam)
{
    char direto[PATH_MAX * 2];

    /* Passo 1: local no sistema atual (diretório de execução) */
    snprintf(direto, sizeof(direto), "%s/%s", raiz, nome);
    if (eh_diretorio(direto)) {
        snprintf(out, tam, "%s", direto);
        return;
    }

    /* Passo 2: procura em sistemas irmãos do core/ (ex: siscore/, outros) */
    char raiz_sisc[PATH_MAX];
    if (raiz_sisc_por_executavel_contexto(raiz_sisc, sizeof(raiz_sisc)) == 0) {
        DIR *d = opendir(raiz_sisc);
        if (d) {
            struct dirent *e;
            while ((e = readdir(d))) {
                if (e->d_name[0] == '.' || e->d_name[0] == 'c') continue; /* pula core/ e ocultos */
                char sys_path[PATH_MAX * 2];
                snprintf(sys_path, sizeof(sys_path), "%s/%s/%s", raiz_sisc, e->d_name, nome);
                if (eh_diretorio(sys_path)) {
                    snprintf(out, tam, "%s", sys_path);
                    closedir(d);
                    return;
                }
            }
            closedir(d);
        }
    }

    /* Passo 3: fallback para core/biblioteca-<nome>/ */
    if (raiz_sisc_por_executavel_contexto(raiz_sisc, sizeof(raiz_sisc)) == 0) {
        char biblioteca[PATH_MAX * 2];
        snprintf(biblioteca, sizeof(biblioteca), "%s/core/biblioteca-%s", raiz_sisc, nome);
        if (eh_diretorio(biblioteca)) {
            snprintf(out, tam, "%s", biblioteca);
            return;
        }
    }

    /* Último recurso: diretório local (mesmo que não exista) */
    snprintf(out, tam, "%s", direto);
}

static int termina_com_str(const char *s, const char *sufixo)
{
    size_t ls = strlen(s), lf = strlen(sufixo);
    return ls >= lf && strcmp(s + ls - lf, sufixo) == 0;
}

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

static void normalizar_nome_conta_llm(const char *valor, char *out, size_t out_tam)
{
    if (!out || out_tam == 0) return;
    out[0] = '\0';
    if (!valor) return;

    while (*valor && isspace((unsigned char)*valor)) valor++;
    size_t len = strlen(valor);
    while (len > 0 && isspace((unsigned char)valor[len - 1])) len--;
    if (len == 0) return;

    char tmp[PATH_MAX];
    if (len >= sizeof(tmp)) len = sizeof(tmp) - 1;
    memcpy(tmp, valor, len);
    tmp[len] = '\0';

    if (termina_com_str(tmp, ".json")) {
        snprintf(out, out_tam, "%s", tmp);
    } else {
        if (out_tam <= 6) { snprintf(out, out_tam, "%s", tmp); return; }
        size_t max_base = out_tam - 6; /* espaço para .json + NUL */
        if (len > max_base) len = max_base;
        memcpy(out, tmp, len);
        memcpy(out + len, ".json", 6);
    }
}

static int resolver_conta_llm_mapeada(const char *raiz, const char *comando, char *out, size_t out_tam, int verbose)
{
    if (!raiz || !comando || !out || out_tam == 0) return 0;
    out[0] = '\0';

    char caminho_mapa[PATH_MAX * 2];

    snprintf(caminho_mapa, sizeof(caminho_mapa), "%s/config/mapa-comandos.json", raiz);
    char *json = ler_arquivo(caminho_mapa);
    if (!json) return 0;

    const char *cmd = nome_base_cmd(comando);
    char *conta = mapa_comandos_sa_conta(json, cmd);
    if (!conta) conta = json_extrair_string(json, cmd);
    if (!conta && strncmp(cmd, "--", 2) != 0) {
        char chave[PATH_MAX];
        snprintf(chave, sizeof(chave), "--%s", cmd);
        conta = json_extrair_string(json, chave);
    }

    if (conta) normalizar_nome_conta_llm(conta, out, out_tam);
    free(conta);
    free(json);

    if (out[0]) {
        if (verbose) fprintf(stderr, "  [mapa-llm] comando=%s conta=%s\n", cmd, out);
        return 1;
    }
    return 0;
}

/* ================================================================
 * Mapa de acentos
 * ================================================================ */

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

static void init_mapa_acento(void)
{
    if (mapa_ok) return;
    for (int i = 0; i < 256; i++) mapa_acento[i] = i;

    struct { const char *de; char para; } tab[] = {
        {"áàãâäåā", 'a'}, {"éèêëē", 'e'}, {"íìîïī", 'i'},
        {"óòõôöō", 'o'}, {"úùûüū", 'u'}, {"ç", 'c'}, {"ñ", 'n'},
        {"ÁÀÃÂÄÅĀ", 'a'}, {"ÉÈÊËĒ", 'e'}, {"ÍÌÎÏĪ", 'i'},
        {"ÓÒÕÔÖŌ", 'o'}, {"ÚÙÛÜŪ", 'u'}, {"Ç", 'c'}, {"Ñ", 'n'},
    };
    int n = sizeof(tab) / sizeof(tab[0]);
    for (int i = 0; i < n; i++)
        for (const char *p = tab[i].de; *p; p++)
            mapa_acento[(unsigned char)*p] = (unsigned char)tab[i].para;
    mapa_ok = 1;
}

static void normalizar_ascii(char *s)
{
    init_mapa_acento();
    for (; *s; s++) {
        *s = (char)mapa_acento[(unsigned char)*s];
        if (*s >= 'A' && *s <= 'Z') *s = (char)(*s + 32);
    }
}

/* ================================================================
 * Arvore SA: le uma arvore JSON e extrai contexto por referencias ra
 * ================================================================ */

static char *arvore_construir_contexto_unica(const char *prompt, const char *raiz, const char *nome_arvore, int verbose)
{
    char path[PATH_MAX * 2];
    if (!nome_arvore || !nome_arvore[0]) nome_arvore = "operador-manual-funcional";
    snprintf(path, sizeof(path), "%s/arvores/%s.json", raiz, nome_arvore);

    char *json = ler_arquivo(path);
    if (!json) {
        char raiz_sisc[PATH_MAX];
        if (raiz_sisc_por_executavel_contexto(raiz_sisc, sizeof(raiz_sisc)) == 0) {
            snprintf(path, sizeof(path), "%s/arvores/%s.json", raiz_sisc, nome_arvore);
            json = ler_arquivo(path);
        }
    }
    if (!json) {
        if (verbose) fprintf(stderr, "  [arvore] mapa nao encontrado em %s\n", path);
        return NULL;
    }
    size_t len = strlen(json);
    if (verbose) fprintf(stderr, "  [arvore] mapa lido: %zu bytes\n", len);

    /* Tokeniza o prompt com stopwords filtradas */
    int nt = 0;
    char **tks = tokenizar_palavras(prompt, &nt);
    if (nt == 0) { free(json); return NULL; }

    if (verbose) {
        fprintf(stderr, "  [arvore] tokens uteis:");
        for (int i = 0; i < nt; i++) fprintf(stderr, " %s", tks[i] ? tks[i] : "");
        fprintf(stderr, "\n");
    }

    /* Estrutura para candidatos com score float */
    typedef struct { char *n; char *r; double s; } C;
    int cc = 256, nc = 0;
    C *cs = malloc(cc * sizeof(C));
    if (!cs) { liberar_tokens(tks, nt); free(json); return NULL; }

    const char *p = json, *fm = json + len;
    while (p < fm) {
        const char *tg = strstr(p, "\"nome\": \"");
        if (!tg || tg >= fm) break;
        tg += 9;
        const char *ne = strchr(tg, '"');
        if (!ne) break;
        const char *rt = strstr(ne, "\"ra\": \"");
        if (!rt || rt > ne + 2048) { p = ne + 1; continue; }
        rt += 7;
        const char *re = strchr(rt, '"');
        if (!re) { p = ne + 1; continue; }

        if (ctx_deve_ignorar_n(rt, (size_t)(re - rt))) { p = ne + 1; continue; }



        /* Score: Jaccard entre tokens do prompt e nome do nó */
        char *lo = malloc((size_t)(ne - tg) + 1);
        if (!lo) continue;
        memcpy(lo, tg, (size_t)(ne - tg));
        lo[(size_t)(ne - tg)] = '\0';
        int nt_lo = 0;
        char **tokens_lo = tokenizar_palavras(lo, &nt_lo);
        free(lo);

        double score = tokens_lo ? similaridade_jaccard(tks, nt, tokens_lo, nt_lo) : 0.0;
        liberar_tokens(tokens_lo, nt_lo);

        if (score > 0.02) {
            int dup = -1;
            for (int k = 0; k < nc; k++) {
                size_t kr = strlen(cs[k].r);
                if (kr == (size_t)(re - rt) && strncmp(cs[k].r, rt, kr) == 0) { dup = k; break; }
            }
            if (dup >= 0) {
                if (score > cs[dup].s) cs[dup].s = score;
                if ((size_t)(ne - tg) > strlen(cs[dup].n)) {
                    char *nn = malloc((size_t)(ne - tg) + 1);
                    if (nn) { memcpy(nn, tg, (size_t)(ne - tg)); nn[(size_t)(ne - tg)] = '\0'; free(cs[dup].n); cs[dup].n = nn; }
                }
            } else {
                if (nc >= cc) {
                    int novo_cc = cc * 2;
                    if (novo_cc > PARALLM_CTX_MAX_CANDIDATOS * 8) novo_cc = PARALLM_CTX_MAX_CANDIDATOS * 8;
                    if (novo_cc > cc) {
                        C *ncs = realloc(cs, (size_t)novo_cc * sizeof(C));
                        if (ncs) { cs = ncs; cc = novo_cc; }
                    }
                }
                if (nc < cc) {
                    cs[nc].n = malloc((size_t)(ne - tg) + 1);
                    if (cs[nc].n) { memcpy(cs[nc].n, tg, (size_t)(ne - tg)); cs[nc].n[(size_t)(ne - tg)] = '\0'; }
                    cs[nc].r = malloc((size_t)(re - rt) + 1);
                    if (cs[nc].r) { memcpy(cs[nc].r, rt, (size_t)(re - rt)); cs[nc].r[(size_t)(re - rt)] = '\0'; }
                    cs[nc].s = score;
                    nc++;
                }
            }
        }
        p = re + 1;
    }
    free(json);
    if (verbose) fprintf(stderr, "  [arvore] candidatos encontrados: %d\n", nc);
    if (nc == 0) { liberar_tokens(tks, nt); free(cs); return NULL; }

    /* Ordena por score decrescente (bubble sort para vetor pequeno) */
    for (int i = 0; i < nc - 1; i++)
        for (int j = i + 1; j < nc; j++)
            if (cs[j].s > cs[i].s) { C t = cs[i]; cs[i] = cs[j]; cs[j] = t; }

    int lim = PARALLM_CTX_MAX_ARQUIVOS;
    if (nc < lim) lim = nc;

    size_t cap = 65536;
    char *ctx = malloc(cap);
    if (!ctx) { liberar_tokens(tks, nt); for (int i = 0; i < nc; i++) { free(cs[i].n); free(cs[i].r); } free(cs); return NULL; }
    size_t ps = 0;
    ctx[0] = '\0';

    for (int i = 0; i < lim && ps < PARALLM_CTX_MAX_BYTES; i++) {
        char cam[PATH_MAX * 2];
        if (strncmp(cs[i].r, "sisc/", 5) == 0) {
            char base[PATH_MAX * 2];
            snprintf(base, sizeof(base), "%s", raiz);
            cam[0] = '\0';
            for (int tenta = 0; tenta < 8 && base[0]; tenta++) {
                char cand[PATH_MAX * 2];
                snprintf(cand, sizeof(cand), "%s/%s", base, cs[i].r + 5);
                if (eh_arquivo(cand)) { snprintf(cam, sizeof(cam), "%s", cand); break; }
                char *slash = strrchr(base, '/');
                if (!slash || slash == base) break;
                *slash = '\0';
            }
            if (!cam[0]) snprintf(cam, sizeof(cam), "%s/%s", raiz, cs[i].r + 5);
        } else {
            snprintf(cam, sizeof(cam), "%s/%s", raiz, cs[i].r);
        }

        char *con = ler_arquivo(cam);
        if (!con) continue;

        /* Chunking do arquivo selecionado */
        int num_chunks = 0;
        Chunk *chunks = NULL;
        chunk_arquivo(con, &chunks, &num_chunks);
        free(con);

        if (chunks && num_chunks > 0) {
            /* Score cada chunk */
            for (int c = 0; c < num_chunks; c++) {
                int nt_chunk = 0;
                char **tokens_chunk = tokenizar_palavras(chunks[c].texto, &nt_chunk);
                if (tokens_chunk && nt_chunk > 0) {
                    chunks[c].score = similaridade_jaccard(tks, nt, tokens_chunk, nt_chunk);
                }
                liberar_tokens(tokens_chunk, nt_chunk);
            }

            double threshold_chunk = (num_chunks <= 2) ? 0.0 : 0.02;
            for (int c = 0; c < num_chunks; c++) {
                if (chunks[c].score < threshold_chunk && num_chunks > 2) continue;

                size_t bloco = strlen(cs[i].n) + 32 + chunks[c].tamanho + 4;
                if (ps + bloco + 1 > cap) {
                    while (ps + bloco + 1 > cap) cap *= 2;
                    if (cap > PARALLM_CTX_MAX_BYTES * 2) break;
                    char *nv = realloc(ctx, cap);
                    if (!nv) break;
                    ctx = nv;
                }
                if (num_chunks > 1)
                    ps += snprintf(ctx + ps, cap - ps,
                        "--- %s (score=%.2f, chunk %d/%d) ---\n%s\n",
                        cs[i].n, cs[i].s, c + 1, num_chunks, chunks[c].texto);
                else
                    ps += snprintf(ctx + ps, cap - ps,
                        "--- %s (score=%.2f) ---\n%s\n",
                        cs[i].n, cs[i].s, chunks[c].texto);
            }

            if (verbose) fprintf(stderr, "  [arvore] + %s (score=%.2f, %d chunks)\n", cs[i].n, cs[i].s, num_chunks);
        }
        liberar_chunks(chunks, num_chunks);
    }

    liberar_tokens(tks, nt);
    for (int i = 0; i < nc; i++) { free(cs[i].n); free(cs[i].r); }
    free(cs);
    if (ps == 0) { free(ctx); return NULL; }
    if (verbose) fprintf(stderr, "  [arvore] contexto montado: %zu bytes\n", ps);
    return ctx;
}

typedef struct {
    char *arquivo;
    char *texto;
    double score;
    int chunk_idx;
    int total_chunks;
} ArvoreTrechoCandidato;

static int cmp_arvore_trecho(const void *a, const void *b)
{
    double diff = ((const ArvoreTrechoCandidato *)b)->score - ((const ArvoreTrechoCandidato *)a)->score;
    if (diff > 0) return 1;
    if (diff < 0) return -1;
    return 0;
}

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

static void arvore_nome_sem_ext(const char *arquivo, char *out, size_t tam)
{
    if (!out || tam == 0) return;
    const char *base = arvore_basename(arquivo);
    snprintf(out, tam, "%s", base);
    size_t l = strlen(out);
    if (l > 5 && strcmp(out + l - 5, ".json") == 0) out[l - 5] = '\0';
}

static int arvore_nome_casa(const char *arquivo, const char *nome_arvore)
{
    if (!nome_arvore || !nome_arvore[0]) return 1;
    char sem_ext[PATH_MAX];
    arvore_nome_sem_ext(arquivo, sem_ext, sizeof(sem_ext));
    return strcmp(nome_arvore, sem_ext) == 0 || strcmp(nome_arvore, arvore_basename(arquivo)) == 0;
}

static int arvores_lista_contem(char **lista, int total, const char *caminho)
{
    for (int i = 0; i < total; i++)
        if (lista[i] && strcmp(lista[i], caminho) == 0) return 1;
    return 0;
}

static void arvores_listar_json_dir(const char *dir_arv, char ***lista, int *cap, int *total)
{
    DIR *d = opendir(dir_arv);
    if (!d) return;

    struct dirent *ent;
    while ((ent = readdir(d)) != NULL) {
        if (ent->d_name[0] == '.') continue;
        if (!termina_com_str(ent->d_name, ".json")) continue;

        char caminho[PATH_MAX * 2];
        snprintf(caminho, sizeof(caminho), "%s/%s", dir_arv, ent->d_name);
        struct stat st;
        if (stat(caminho, &st) != 0 || !S_ISREG(st.st_mode)) continue;
        if (arvores_lista_contem(*lista, *total, caminho)) continue;

        if (*total >= *cap) {
            *cap *= 2;
            char **nv = realloc(*lista, (size_t)(*cap) * sizeof(char *));
            if (!nv) break;
            *lista = nv;
        }
        (*lista)[*total] = strdup(caminho);
        if ((*lista)[*total]) (*total)++;
    }
    closedir(d);
}

static int arvores_listar_json(const char *raiz, char ***lista, int *total)
{
    *lista = NULL;
    *total = 0;

    int cap = 16;
    char **v = malloc((size_t)cap * sizeof(char *));
    if (!v) return -1;

    /* Normalizacao de fontes:
     * - árvores do sistema alvo: <sistema>/arvores
     * - contexto da ferramenta SISC:       sisc/arvores
     * A ordem privilegia o sistema alvo; a camada de ranking decide quando
     * tambem faz sentido anexar material da ferramenta. */
    char dir_sistema[PATH_MAX * 2];
    snprintf(dir_sistema, sizeof(dir_sistema), "%s/arvores", raiz);
    arvores_listar_json_dir(dir_sistema, &v, &cap, total);

    char raiz_sisc[PATH_MAX];
    if (raiz_sisc_por_executavel_contexto(raiz_sisc, sizeof(raiz_sisc)) == 0) {
        char dir_ferramenta[PATH_MAX * 2];
        snprintf(dir_ferramenta, sizeof(dir_ferramenta), "%s/arvores", raiz_sisc);
        arvores_listar_json_dir(dir_ferramenta, &v, &cap, total);
    }

    if (*total == 0) { free(v); return 0; }
    *lista = v;
    return 0;
}

static void liberar_arvores_lista(char **lista, int total)
{
    if (!lista) return;
    for (int i = 0; i < total; i++) free(lista[i]);
    free(lista);
}

static int caminho_tem_prefixo_dir(const char *caminho, const char *dir)
{
    if (!caminho || !dir) return 0;
    size_t ld = strlen(dir);
    return strncmp(caminho, dir, ld) == 0 &&
           (caminho[ld] == '\0' || caminho[ld] == '/');
}

static int arvore_path_eh_ferramenta_sisc(const char *path)
{
    char raiz_sisc[PATH_MAX];
    if (raiz_sisc_por_executavel_contexto(raiz_sisc, sizeof(raiz_sisc)) != 0) return 0;
    char dir_ferramenta[PATH_MAX * 2];
    snprintf(dir_ferramenta, sizeof(dir_ferramenta), "%s/arvores", raiz_sisc);
    return caminho_tem_prefixo_dir(path, dir_ferramenta);
}

static int tokens_contem(char **tokens, int nt, const char *alvo)
{
    for (int i = 0; i < nt; i++)
        if (tokens[i] && strcmp(tokens[i], alvo) == 0) return 1;
    return 0;
}

static int prompt_pede_ferramenta_sisc(char **tokens, int nt)
{
    static const char *diretos[] = {
        "sisc", "siscore", "pp", "comando", "comandos", "ferramenta",
        "operador", "manual", "gc", "sa", "parallm", "heb", "escuta",
        "runtime", NULL
    };
    for (int i = 0; diretos[i]; i++)
        if (tokens_contem(tokens, nt, diretos[i])) return 1;

    int criar = tokens_contem(tokens, nt, "criar") || tokens_contem(tokens, nt, "crie") ||
                tokens_contem(tokens, nt, "gerar") || tokens_contem(tokens, nt, "gere") ||
                tokens_contem(tokens, nt, "atualizar") || tokens_contem(tokens, nt, "atualize");
    if (criar && (tokens_contem(tokens, nt, "conector") || tokens_contem(tokens, nt, "conectores") ||
                  tokens_contem(tokens, nt, "sistema") || tokens_contem(tokens, nt, "agente")))
        return 1;

    return 0;
}

static void arvore_rotulo_contexto(const char *raiz, const char *path, char *out, size_t tam)
{
    if (!out || tam == 0) return;
    const char *base = arvore_basename(path);
    if (arvore_path_eh_ferramenta_sisc(path)) {
        snprintf(out, tam, "sisc/arvores/%s", base);
        return;
    }

    const char *nome_sistema = nome_base_cmd(raiz);
    if (!nome_sistema || !nome_sistema[0]) nome_sistema = "sistema";
    snprintf(out, tam, "%s/arvores/%s", nome_sistema, base);
}

/*
 * Contexto direto dos JSON no diretorio arvores/.
 *
 * Esta etapa trata os proprios JSON dinamicos de arvores/ como base de
 * conhecimento. Isso e importante porque estes arquivos sao atualizados por
 * outros comandos e podem conter o contexto mais recente do sistema
 * (conectores, GC, agentes, operacao etc.).
 */
static char *arvores_json_construir_contexto(const char *prompt, const char *raiz,
                                             const char *nome_arvore, int verbose)
{
    int nt_prompt = 0;
    char **tokens_prompt = tokenizar_palavras(prompt, &nt_prompt);
    if (nt_prompt == 0) { liberar_tokens(tokens_prompt, nt_prompt); return NULL; }

    int prompt_ferramenta = prompt_pede_ferramenta_sisc(tokens_prompt, nt_prompt);
    int prompt_generico_sistema = 0;
    for (int i = 0; i < nt_prompt; i++) {
        const char *t = tokens_prompt[i];
        if (!t) continue;
        if (strcmp(t, "sistema") == 0 || strcmp(t, "projeto") == 0 ||
            strcmp(t, "contexto") == 0 || strcmp(t, "geral") == 0 ||
            strcmp(t, "resumo") == 0 || strcmp(t, "resuma") == 0 ||
            strcmp(t, "proposito") == 0 || strcmp(t, "arquitetura") == 0 ||
            strcmp(t, "funcionamento") == 0 || strcmp(t, "funciona") == 0) {
            prompt_generico_sistema = 1;
            break;
        }
    }

    char **arquivos = NULL;
    int total_arq = 0;
    arvores_listar_json(raiz, &arquivos, &total_arq);
    if (total_arq == 0) { liberar_tokens(tokens_prompt, nt_prompt); liberar_arvores_lista(arquivos, total_arq); return NULL; }

    int cap_cand = 128, nc = 0;
    ArvoreTrechoCandidato *cands = malloc((size_t)cap_cand * sizeof(ArvoreTrechoCandidato));
    if (!cands) { liberar_tokens(tokens_prompt, nt_prompt); liberar_arvores_lista(arquivos, total_arq); return NULL; }

    for (int a = 0; a < total_arq; a++) {
        const char *path = arquivos[a];
        const char *base = arvore_basename(path);
        int eh_ferramenta = arvore_path_eh_ferramenta_sisc(path);
        if (!arvore_nome_casa(base, nome_arvore)) continue;

        char rotulo[PATH_MAX * 2];
        arvore_rotulo_contexto(raiz, path, rotulo, sizeof(rotulo));

        char nome_sem_ext[PATH_MAX];
        arvore_nome_sem_ext(base, nome_sem_ext, sizeof(nome_sem_ext));

        char nome_score_buf[PATH_MAX * 2];
        snprintf(nome_score_buf, sizeof(nome_score_buf), "%s %s", nome_sem_ext, base);
        int nt_nome = 0;
        char **tokens_nome = tokenizar_palavras(nome_score_buf, &nt_nome);
        double score_nome = tokens_nome ? similaridade_jaccard(tokens_prompt, nt_prompt, tokens_nome, nt_nome) : 0.0;
        liberar_tokens(tokens_nome, nt_nome);

        char *json = ler_arquivo(path);
        if (!json) continue;

        int num_chunks = 0;
        Chunk *chunks = NULL;
        chunk_arquivo(json, &chunks, &num_chunks);
        free(json);
        if (!chunks || num_chunks == 0) { liberar_chunks(chunks, num_chunks); continue; }

        for (int c = 0; c < num_chunks; c++) {
            int nt_chunk = 0;
            char **tokens_chunk = tokenizar_palavras(chunks[c].texto, &nt_chunk);
            double score_chunk = tokens_chunk ? similaridade_jaccard(tokens_prompt, nt_prompt, tokens_chunk, nt_chunk) : 0.0;
            liberar_tokens(tokens_chunk, nt_chunk);

            /* Peso do nome da arvore ajuda a selecionar manuais tematicos como conector/gc. */
            double score = score_chunk + (score_nome * 0.45);
            if (strcmp(nome_sem_ext, "operador-manual-funcional") == 0) score += 0.005;

            int relevante = (score_chunk > 0.015 || score_nome > 0.055 ||
                             (prompt_generico_sistema && c == 0 && (!eh_ferramenta || prompt_ferramenta)) ||
                             (nome_arvore && nome_arvore[0] && score > 0.0));
            if (eh_ferramenta && !prompt_ferramenta && score < 0.12) relevante = 0;
            if (!relevante) continue;

            if (nc >= cap_cand) {
                int novo_cap = cap_cand * 2;
                if (novo_cap > PARALLM_CTX_MAX_CANDIDATOS * 8) novo_cap = PARALLM_CTX_MAX_CANDIDATOS * 8;
                if (novo_cap <= cap_cand) break;
                ArvoreTrechoCandidato *nv = realloc(cands, (size_t)novo_cap * sizeof(ArvoreTrechoCandidato));
                if (!nv) break;
                cands = nv;
                cap_cand = novo_cap;
            }

            cands[nc].arquivo = strdup(rotulo);
            cands[nc].texto = strdup(chunks[c].texto);
            cands[nc].score = score;
            cands[nc].chunk_idx = c + 1;
            cands[nc].total_chunks = num_chunks;
            if (cands[nc].arquivo && cands[nc].texto) nc++;
            else { free(cands[nc].arquivo); free(cands[nc].texto); }
        }

        if (verbose) fprintf(stderr, "  [arvores-json] %s: origem=%s score_nome=%.2f, chunks=%d\n",
                             base, eh_ferramenta ? "ferramenta-sisc" : "sistema-alvo", score_nome, num_chunks);
        liberar_chunks(chunks, num_chunks);
    }

    liberar_arvores_lista(arquivos, total_arq);
    liberar_tokens(tokens_prompt, nt_prompt);

    if (nc == 0) { free(cands); return NULL; }
    qsort(cands, (size_t)nc, sizeof(ArvoreTrechoCandidato), cmp_arvore_trecho);

    size_t cap = 65536;
    char *ctx = malloc(cap);
    if (!ctx) {
        for (int i = 0; i < nc; i++) { free(cands[i].arquivo); free(cands[i].texto); }
        free(cands);
        return NULL;
    }

    size_t ps = 0;
    ctx[0] = '\0';
    ps += snprintf(ctx + ps, cap - ps,
        "=== Trechos relevantes de <sistema>/arvores e sisc/arvores (contexto dinamico atualizado) ===\n");

    typedef struct { char *arquivo; int usados; } UsoArv;
    UsoArv usados[128];
    int nu = 0;
    int inclu = 0;

    for (int i = 0; i < nc && inclu < PARALLM_ARV_MAX_TRECHOS && ps < PARALLM_CTX_MAX_BYTES; i++) {
        int idx_uso = -1;
        for (int u = 0; u < nu; u++) {
            if (strcmp(usados[u].arquivo, cands[i].arquivo) == 0) { idx_uso = u; break; }
        }
        if (idx_uso >= 0 && usados[idx_uso].usados >= PARALLM_ARV_MAX_TRECHOS_POR_ARQUIVO) continue;
        if (idx_uso < 0 && nu < (int)(sizeof(usados) / sizeof(usados[0]))) {
            usados[nu].arquivo = cands[i].arquivo;
            usados[nu].usados = 0;
            idx_uso = nu++;
        }

        size_t bloco = strlen(cands[i].arquivo) + strlen(cands[i].texto) + 96;
        if (ps + bloco + 1 > cap) {
            while (ps + bloco + 1 > cap) cap *= 2;
            if (cap > PARALLM_CTX_MAX_BYTES * 2) break;
            char *nv = realloc(ctx, cap);
            if (!nv) break;
            ctx = nv;
        }

        ps += snprintf(ctx + ps, cap - ps,
            "--- %s (score=%.2f, chunk %d/%d) ---\n%s\n",
            cands[i].arquivo, cands[i].score, cands[i].chunk_idx, cands[i].total_chunks,
            cands[i].texto);
        if (idx_uso >= 0) usados[idx_uso].usados++;
        inclu++;
    }

    if (verbose) fprintf(stderr, "  [arvores-json] trechos incluidos: %d de %d candidatos (%zu bytes)\n", inclu, nc, ps);

    for (int i = 0; i < nc; i++) { free(cands[i].arquivo); free(cands[i].texto); }
    free(cands);

    if (inclu == 0 || ps == 0) { free(ctx); return NULL; }
    return ctx;
}

static char *arvores_refs_construir_contexto_todas(const char *prompt, const char *raiz, int verbose)
{
    int nt_prompt = 0;
    char **tokens_prompt = tokenizar_palavras(prompt, &nt_prompt);
    int prompt_ferramenta = prompt_pede_ferramenta_sisc(tokens_prompt, nt_prompt);
    liberar_tokens(tokens_prompt, nt_prompt);

    char **arquivos = NULL;
    int total_arq = 0;
    arvores_listar_json(raiz, &arquivos, &total_arq);
    if (total_arq == 0) { liberar_arvores_lista(arquivos, total_arq); return NULL; }

    size_t cap = 65536;
    char *ctx = malloc(cap);
    if (!ctx) { liberar_arvores_lista(arquivos, total_arq); return NULL; }
    size_t ps = 0;
    ctx[0] = '\0';

    for (int i = 0; i < total_arq && ps < PARALLM_CTX_MAX_BYTES; i++) {
        int eh_ferramenta = arvore_path_eh_ferramenta_sisc(arquivos[i]);
        if (eh_ferramenta && !prompt_ferramenta) continue;

        char *probe = ler_arquivo(arquivos[i]);
        int tem_ra = probe && strstr(probe, "\"ra\"") != NULL;
        free(probe);
        if (!tem_ra) continue;

        char nome[PATH_MAX];
        arvore_nome_sem_ext(arquivos[i], nome, sizeof(nome));
        char *um = arvore_construir_contexto_unica(prompt, raiz, nome, verbose);
        if (!um) continue;

        size_t bloco = strlen(nome) + strlen(um) + 96;
        if (ps + bloco + 1 > cap) {
            while (ps + bloco + 1 > cap) cap *= 2;
            if (cap > PARALLM_CTX_MAX_BYTES * 2) { free(um); break; }
            char *nv = realloc(ctx, cap);
            if (!nv) { free(um); break; }
            ctx = nv;
        }
        char rotulo[PATH_MAX * 2];
        arvore_rotulo_contexto(raiz, arquivos[i], rotulo, sizeof(rotulo));
        ps += snprintf(ctx + ps, cap - ps,
            "--- referencias extraidas de %s ---\n%s\n", rotulo, um);
        free(um);
    }

    liberar_arvores_lista(arquivos, total_arq);
    if (ps == 0) { free(ctx); return NULL; }
    return ctx;
}

static char *combinar_contextos_arvore(const char *ctx_refs, const char *ctx_json)
{
    if (!ctx_refs && !ctx_json) return NULL;
    if (!ctx_refs) return strdup(ctx_json);
    if (!ctx_json) return strdup(ctx_refs);

    size_t len = strlen(ctx_refs) + strlen(ctx_json) + 128;
    char *out = malloc(len);
    if (!out) return strdup(ctx_refs);
    snprintf(out, len,
        "=== Contexto por referencias das arvores (arquivos apontados por ra) ===\n%s\n"
        "%s",
        ctx_refs, ctx_json);
    return out;
}

static char *arvore_construir_contexto_legado(const char *prompt, const char *raiz, const char *nome_arvore, int verbose)
{
    if (nome_arvore && nome_arvore[0]) {
        char *ctx_refs = arvore_construir_contexto_unica(prompt, raiz, nome_arvore, verbose);
        char *ctx_json = arvores_json_construir_contexto(prompt, raiz, nome_arvore, verbose);
        char *ctx = combinar_contextos_arvore(ctx_refs, ctx_json);
        free(ctx_refs);
        free(ctx_json);
        return ctx;
    }

    /* Modo inteligente: usa os JSON dinamicos de arvores/ e tambem segue
     * referencias por ra encontradas em qualquer arvore JSON relevante. */
    char *ctx_refs = arvores_refs_construir_contexto_todas(prompt, raiz, verbose);
    char *ctx_json = arvores_json_construir_contexto(prompt, raiz, NULL, verbose);
    char *ctx = combinar_contextos_arvore(ctx_refs, ctx_json);
    free(ctx_refs);
    free(ctx_json);
    return ctx;
}

static int parallm_prompt_pede_ferramenta_sisc(const char *prompt)
{
    int nt = 0;
    char **tks = tokenizar_palavras(prompt, &nt);
    int r = tks ? prompt_pede_ferramenta_sisc(tks, nt) : 0;
    liberar_tokens(tks, nt);
    return r;
}

static int caminho_sa_ctx(char *out, size_t tam)
{
    char raiz_sisc[PATH_MAX];
    if (raiz_sisc_por_executavel_contexto(raiz_sisc, sizeof(raiz_sisc)) != 0) return -1;
    snprintf(out, tam, "%s/core/comandos/sa-ctx", raiz_sisc);
    return access(out, X_OK) == 0 ? 0 : -1;
}

static char *sa_ctx_capturar_contexto(const char *prompt, const char *origem,
                                      const char *nome_arvore, size_t max_bytes,
                                      int max_itens, int include_ra, int verbose)
{
    char cmd_path[PATH_MAX * 2];
    if (caminho_sa_ctx(cmd_path, sizeof(cmd_path)) != 0) {
        if (verbose) fprintf(stderr, "  [sa-ctx] executavel nao encontrado; usando fallback legado\n");
        return NULL;
    }

    char *arg_prompt = malloc(strlen(prompt ? prompt : "") + 32);
    char arg_origem[64];
    char arg_max_bytes[64];
    char arg_max_itens[64];
    if (!arg_prompt) return NULL;
    sprintf(arg_prompt, "--ctx-extrair=%s", prompt ? prompt : "");
    snprintf(arg_origem, sizeof(arg_origem), "--origem=%s", origem ? origem : "auto");
    snprintf(arg_max_bytes, sizeof(arg_max_bytes), "--max-bytes=%zu", max_bytes);
    snprintf(arg_max_itens, sizeof(arg_max_itens), "--max-itens=%d", max_itens > 0 ? max_itens : PARALLM_SA_CTX_MAX_ITENS);

    int fd[2];
    if (pipe(fd) != 0) { free(arg_prompt); return NULL; }

    pid_t pid = fork();
    if (pid < 0) {
        close(fd[0]); close(fd[1]); free(arg_prompt); return NULL;
    }

    if (pid == 0) {
        close(fd[0]);
        if (dup2(fd[1], STDOUT_FILENO) < 0) _exit(126);
        close(fd[1]);

        char *args[10];
        int p = 0;
        args[p++] = cmd_path;
        if (nome_arvore && nome_arvore[0]) args[p++] = (char *)nome_arvore;
        args[p++] = arg_prompt;
        args[p++] = arg_origem;
        args[p++] = arg_max_bytes;
        args[p++] = arg_max_itens;
        if (include_ra) args[p++] = "--include-ra";
        args[p] = NULL;
        execv(cmd_path, args);
        _exit(127);
    }

    close(fd[1]);
    size_t limite = max_bytes + 8192;
    if (limite < 16384) limite = 16384;
    char *out = malloc(limite + 1);
    if (!out) { close(fd[0]); free(arg_prompt); waitpid(pid, NULL, 0); return NULL; }
    size_t len = 0;
    char buf[8192];
    ssize_t nr;
    while ((nr = read(fd[0], buf, sizeof(buf))) > 0) {
        if (len < limite) {
            size_t take = (size_t)nr;
            if (len + take > limite) take = limite - len;
            memcpy(out + len, buf, take);
            len += take;
        }
    }
    close(fd[0]);
    out[len] = '\0';

    int st = 0;
    waitpid(pid, &st, 0);
    free(arg_prompt);

    int ok = WIFEXITED(st) && WEXITSTATUS(st) == 0;
    if (verbose) fprintf(stderr, "  [sa-ctx] origem=%s filtro=%s bytes=%zu status=%d\n",
                         origem ? origem : "auto", (nome_arvore && nome_arvore[0]) ? nome_arvore : "-", len,
                         WIFEXITED(st) ? WEXITSTATUS(st) : -1);
    if (!ok || len == 0 || strstr(out, "--- ") == NULL) {
        free(out);
        return NULL;
    }
    return out;
}

static char *sa_ctx_combinar_contextos(const char *ctx_sistema, const char *ctx_ferramenta)
{
    if (!ctx_sistema && !ctx_ferramenta) return NULL;
    if (!ctx_sistema) return strdup(ctx_ferramenta);
    if (!ctx_ferramenta) return strdup(ctx_sistema);

    size_t cap = strlen(ctx_sistema) + strlen(ctx_ferramenta) + 256;
    char *out = malloc(cap);
    if (!out) return strdup(ctx_sistema);
    snprintf(out, cap,
        "=== Contexto SA do sistema alvo (<sistema>/arvores + evidencias ra/codigo) ===\n%s\n"
        "=== Contexto SA da ferramenta SISC (sisc/arvores, usado por pertinencia do prompt) ===\n%s",
        ctx_sistema, ctx_ferramenta);
    return out;
}

static char *arvore_construir_contexto(const char *prompt, const char *raiz, const char *nome_arvore, int verbose)
{
    char sa_ctx_path_tmp[PATH_MAX * 2];
    int sa_ctx_disponivel = (caminho_sa_ctx(sa_ctx_path_tmp, sizeof(sa_ctx_path_tmp)) == 0);

    if (nome_arvore && nome_arvore[0]) {
        char *ctx = sa_ctx_capturar_contexto(prompt, "auto", nome_arvore,
                                             PARALLM_SA_CTX_MAX_BYTES_SISTEMA,
                                             PARALLM_SA_CTX_MAX_ITENS, 1, verbose);
        if (ctx) return ctx;
        return sa_ctx_disponivel ? NULL : arvore_construir_contexto_legado(prompt, raiz, nome_arvore, verbose);
    }

    char *ctx_sistema = sa_ctx_capturar_contexto(prompt, "sistema", NULL,
                                                 PARALLM_SA_CTX_MAX_BYTES_SISTEMA,
                                                 PARALLM_SA_CTX_MAX_ITENS, 1, verbose);
    char *ctx_ferramenta = NULL;
    if (parallm_prompt_pede_ferramenta_sisc(prompt)) {
        ctx_ferramenta = sa_ctx_capturar_contexto(prompt, "ferramenta", NULL,
                                                  PARALLM_SA_CTX_MAX_BYTES_FERRAMENTA,
                                                  PARALLM_SA_CTX_MAX_ITENS, 1, verbose);
    }

    char *ctx = sa_ctx_combinar_contextos(ctx_sistema, ctx_ferramenta);
    free(ctx_sistema);
    free(ctx_ferramenta);
    if (ctx) return ctx;

    return sa_ctx_disponivel ? NULL : arvore_construir_contexto_legado(prompt, raiz, NULL, verbose);
}

/* Estimativa de tokens melhorada (considera tipo de conteúdo) */
static size_t estimar_tokens(const char *texto, size_t bytes)
{
    if (!texto || bytes == 0) return 0;
    /* Conta caracteres especiais (código) vs texto natural */
    size_t simbolos = 0;
    for (size_t i = 0; i < bytes && i < 4096; i++) {
        char c = texto[i];
        if (c == '{' || c == '}' || c == '(' || c == ')' || c == ';' ||
            c == '/' || c == '\\' || c == '<' || c == '>' || c == '[' ||
            c == ']' || c == '=' || c == '#' || c == '"') simbolos++;
    }
    double proporcao = bytes > 0 ? (double)simbolos / (double)(bytes < 4096 ? bytes : 4096) : 0.0;
    /* Código: ~3 chars/token. Texto: ~4 chars/token */
    double chars_por_token = proporcao > 0.08 ? 3.0 : 4.0;
    return (size_t)((double)bytes / chars_por_token);
}

/* ================================================================
 * Relatório de fontes do contexto
 * ================================================================ */

typedef struct { char **v; int total, cap; } RelListaStr;

typedef struct {
    int itens_total;
    int itens_sistema;
    int itens_ferramenta;
    int itens_outros;
    int ra_blocos;
    RelListaStr ra_referenciados;
    RelListaStr ra_anexados;
} RelSaCtx;

static void rel_lista_free(RelListaStr *l)
{
    if (!l) return;
    for (int i = 0; i < l->total; i++) free(l->v[i]);
    free(l->v);
    memset(l, 0, sizeof(*l));
}

static void rel_lista_add_unico(RelListaStr *l, const char *ini, size_t len)
{
    if (!l || !ini) return;
    while (len > 0 && isspace((unsigned char)*ini)) { ini++; len--; }
    while (len > 0 && isspace((unsigned char)ini[len - 1])) len--;
    if (len == 0) return;

    /* Remove sufixos de truncamento textual, quando existirem. */
    const char *tr = NULL;
    const char *marca = " ... [truncado";
    size_t marca_len = strlen(marca);
    if (len >= marca_len) {
        for (const char *p = ini; p + marca_len <= ini + len; p++) {
            if (strncmp(p, marca, marca_len) == 0) { tr = p; break; }
        }
    }
    if (tr) {
        len = (size_t)(tr - ini);
        while (len > 0 && isspace((unsigned char)ini[len - 1])) len--;
        if (len == 0) return;
    }

    for (int i = 0; i < l->total; i++)
        if (strlen(l->v[i]) == len && strncmp(l->v[i], ini, len) == 0) return;

    if (l->total >= l->cap) {
        int nc = l->cap ? l->cap * 2 : 16;
        char **nv = realloc(l->v, (size_t)nc * sizeof(char *));
        if (!nv) return;
        l->v = nv;
        l->cap = nc;
    }
    char *s = malloc(len + 1);
    if (!s) return;
    memcpy(s, ini, len);
    s[len] = '\0';
    l->v[l->total++] = s;
}

static int rel_linha_comeca(const char *p, const char *fim, const char *prefixo)
{
    size_t n = strlen(prefixo);
    return p && fim && (size_t)(fim - p) >= n && strncmp(p, prefixo, n) == 0;
}

static const char *rel_memstr_linha(const char *ini, const char *fim, const char *agulha)
{
    size_t n = strlen(agulha);
    if (!ini || !fim || n == 0 || (size_t)(fim - ini) < n) return NULL;
    for (const char *p = ini; p + n <= fim; p++)
        if (strncmp(p, agulha, n) == 0) return p;
    return NULL;
}

static void rel_parse_ra_lista(RelListaStr *out, const char *ini, const char *fim)
{
    const char *p = ini;
    while (p && p < fim) {
        while (p < fim && (isspace((unsigned char)*p) || *p == ',')) p++;
        const char *e = p;
        while (e < fim && *e != ',') e++;
        rel_lista_add_unico(out, p, (size_t)(e - p));
        p = e < fim ? e + 1 : fim;
    }
}

static void rel_analisar_sa_ctx(const char *ctx, RelSaCtx *st)
{
    if (!ctx || !st) return;
    memset(st, 0, sizeof(*st));

    for (const char *p = ctx; *p;) {
        const char *fim = strchr(p, '\n');
        if (!fim) fim = p + strlen(p);

        if (rel_linha_comeca(p, fim, "--- ")) {
            const char *idp = rel_memstr_linha(p, fim, " id=");
            const char *scp = rel_memstr_linha(p, fim, " score=");
            if (idp && scp) {
                st->itens_total++;
                const char *orig = p + 4;
                const char *orig_fim = orig;
                while (orig_fim < fim && !isspace((unsigned char)*orig_fim)) orig_fim++;
                size_t olen = (size_t)(orig_fim - orig);
                if (olen == 7 && strncmp(orig, "sistema", 7) == 0) st->itens_sistema++;
                else if (olen == 10 && strncmp(orig, "ferramenta", 10) == 0) st->itens_ferramenta++;
                else st->itens_outros++;
            }
        } else if (rel_linha_comeca(p, fim, "[ra:")) {
            st->ra_blocos++;
            const char *ini = p + 4;
            const char *e = rel_memstr_linha(ini, fim, " |");
            if (!e) e = rel_memstr_linha(ini, fim, "]");
            if (!e) e = fim;
            rel_lista_add_unico(&st->ra_anexados, ini, (size_t)(e - ini));
        } else if (rel_linha_comeca(p, fim, "RA: ")) {
            rel_parse_ra_lista(&st->ra_referenciados, p + 4, fim);
        }

        p = *fim == '\n' ? fim + 1 : fim;
    }
}

static void rel_sa_ctx_free(RelSaCtx *st)
{
    if (!st) return;
    rel_lista_free(&st->ra_referenciados);
    rel_lista_free(&st->ra_anexados);
}

/* ================================================================
 * Escape de string para JSON
 * ================================================================ */

static char *json_escape(const char *str)
{
    if (!str) return strdup("");
    size_t len = strlen(str);
    size_t cap = len * 2 + 4;
    char *out = malloc(cap);
    if (!out) return NULL;
    size_t j = 0;

    for (size_t i = 0; i < len; i++) {
        if (j + 6 >= cap) { cap *= 2; out = realloc(out, cap); if (!out) return NULL; }
        switch (str[i]) {
            case '"':  out[j++] = '\\'; out[j++] = '"'; break;
            case '\\': out[j++] = '\\'; out[j++] = '\\'; break;
            case '\n': out[j++] = '\\'; out[j++] = 'n'; break;
            case '\r': out[j++] = '\\'; out[j++] = 'r'; break;
            case '\t': out[j++] = '\\'; out[j++] = 't'; break;
            default:   out[j++] = str[i]; break;
        }
    }
    out[j] = '\0';
    return out;
}

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

static char *url_encode(const char *s)
{
    size_t len = strlen(s);
    char *out = malloc(len * 3 + 1);
    if (!out) return NULL;
    size_t j = 0;
    for (size_t i = 0; i < len; i++) {
        unsigned char c = (unsigned char)s[i];
        if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') out[j++] = (char)c;
        else { sprintf(out + j, "%%%02X", c); j += 3; }
    }
    out[j] = '\0';
    return out;
}

static char *oauth_refresh_codex(const char *refresh_token, const char *provedor, const char *secret_path)
{
    char *refresh_e = url_encode(refresh_token);
    if (!refresh_e) return NULL;
    const char *client = "app_EMoamEEZ73f0CkXaXp7hrann";
    size_t body_len = strlen(refresh_e) + strlen(client) + 128;
    char *body = malloc(body_len);
    if (!body) { free(refresh_e); return NULL; }
    snprintf(body, body_len, "grant_type=refresh_token&refresh_token=%s&client_id=%s", refresh_e, client);
    free(refresh_e);

    char payload_t[] = "/tmp/sisc_oauth_refresh_payload_XXXXXX";
    int fd = mkstemp(payload_t);
    if (fd < 0) { free(body); return NULL; }
    write(fd, body, strlen(body));
    close(fd);
    free(body);

    char resp_t[] = "/tmp/sisc_oauth_refresh_resp_XXXXXX";
    fd = mkstemp(resp_t);
    if (fd < 0) { unlink(payload_t); return NULL; }
    close(fd);

    char cmd[4096];
    snprintf(cmd, sizeof(cmd), "curl -s -w '\\n%%{http_code}' --max-time 60 -H 'Content-Type: application/x-www-form-urlencoded' --data @%s -o %s 'https://auth.openai.com/oauth/token'", payload_t, resp_t);
    FILE *fp = popen(cmd, "r");
    char http[16] = "";
    if (fp) {
        char line[64];
        while (fgets(line, sizeof(line), fp)) {
            size_t l = strlen(line); if (l && line[l - 1] == '\n') line[l - 1] = '\0';
            if (isdigit((unsigned char)line[0])) snprintf(http, sizeof(http), "%s", line);
        }
        pclose(fp);
    }
    char *json = ler_arquivo(resp_t);
    unlink(payload_t); unlink(resp_t);
    if (!json) return NULL;
    if (atoi(http) >= 400) { free(json); return NULL; }

    char *access = json_extrair_string(json, "access_token");
    char *refresh2 = json_extrair_string(json, "refresh_token");
    long expires_in = json_extrair_int(json, "expires_in", 0);
    free(json);
    if (!access || !refresh2 || expires_in <= 0) { free(access); free(refresh2); return NULL; }

    long expira = (long)time(NULL) + expires_in;
    char *ae = json_escape(access), *re = json_escape(refresh2), *pe = json_escape(provedor ? provedor : "codex");
    size_t out_len = strlen(ae) + strlen(re) + strlen(pe) + 220;
    char *out = malloc(out_len);
    if (out) snprintf(out, out_len, "{\n    \"type\": \"oauth\",\n    \"provedor\": \"%s\",\n    \"access\": \"%s\",\n    \"refresh\": \"%s\",\n    \"expires\": %ld\n}", pe, ae, re, expira);
    free(ae); free(re); free(pe); free(refresh2);
    if (out) escrever_arquivo_texto(secret_path, out, 0600);
    free(out);
    return access;
}

static char *resolver_oauth_account_id(const char *raiz, const char *arquivo)
{
    if (!arquivo || !arquivo[0]) return NULL;
    char dir_secretos[PATH_MAX * 2];
    resolver_subdir_sistema(raiz, "secretos", dir_secretos, sizeof(dir_secretos));
    char path[PATH_MAX * 2];
    snprintf(path, sizeof(path), "%s/%s.json", dir_secretos, arquivo);
    char *json = ler_arquivo(path);
    if (!json) return NULL;
    char *id = json_extrair_string(json, "accountId");
    free(json);
    return id;
}

static char *resolver_oauth_access(const char *raiz, const char *arquivo, const char *provedor, int verbose)
{
    if (!arquivo || !arquivo[0]) return NULL;
    char dir_secretos[PATH_MAX * 2];
    resolver_subdir_sistema(raiz, "secretos", dir_secretos, sizeof(dir_secretos));
    char path[PATH_MAX * 2];
    snprintf(path, sizeof(path), "%s/%s.json", dir_secretos, arquivo);
    char *json = ler_arquivo(path);
    if (!json) return NULL;

    char *access = json_extrair_string(json, "access");
    char *refresh = json_extrair_string(json, "refresh");
    long expires = json_extrair_int(json, "expires", 0);
    free(json);

    if (access && expires > (long)time(NULL) + 60) {
        free(refresh);
        if (verbose) fprintf(stderr, "  [auth] oauth access carregado de %s\n", arquivo);
        return access;
    }

    if (refresh && provedor && (strcmp(provedor, "codex") == 0 || strcmp(provedor, "openai-codex") == 0)) {
        if (verbose) fprintf(stderr, "  [auth] oauth expirado; atualizando token...\n");
        char *novo = oauth_refresh_codex(refresh, provedor, path);
        free(access); free(refresh);
        return novo;
    }

    free(access); free(refresh);
    return NULL;
}

static char *json_unescape_slice(const char *ini, size_t tam)
{
    char *out = malloc(tam + 1);
    if (!out) return NULL;
    size_t j = 0;
    for (size_t i = 0; i < tam; i++) {
        if (ini[i] == '\\' && i + 1 < tam) {
            i++;
            switch (ini[i]) {
                case 'n': out[j++] = '\n'; break;
                case 'r': out[j++] = '\r'; break;
                case 't': out[j++] = '\t'; break;
                case '"': out[j++] = '"'; break;
                case '\\': out[j++] = '\\'; break;
                default: out[j++] = ini[i]; break;
            }
        } else {
            out[j++] = ini[i];
        }
    }
    out[j] = '\0';
    return out;
}

static char *extrair_codex_texto_sse(const char *sse)
{
    size_t cap = 8192, len = 0;
    char *out = malloc(cap);
    if (!out) return NULL;
    out[0] = '\0';

    const char *p = sse;
    while ((p = strstr(p, "\"delta\"")) != NULL) {
        const char *colon = strchr(p, ':');
        if (!colon) break;
        p = colon + 1;
        while (*p && isspace((unsigned char)*p)) p++;
        if (*p != '"') continue;
        p++;
        const char *ini = p;
        while (*p && *p != '"') {
            if (*p == '\\' && p[1]) p++;
            p++;
        }
        if (*p != '"') break;
        char *part = json_unescape_slice(ini, (size_t)(p - ini));
        if (part) {
            size_t pl = strlen(part);
            if (len + pl + 1 >= cap) { cap = (len + pl + 1) * 2; out = realloc(out, cap); if (!out) { free(part); return NULL; } }
            memcpy(out + len, part, pl);
            len += pl;
            out[len] = '\0';
            free(part);
        }
        p++;
    }

    if (len == 0) {
        free(out);
        return NULL;
    }
    return out;
}

/* ================================================================
 * Sessao: carrega/salva historico de conversa (JSONL)
 * ================================================================ */

/* Formato do arquivo de sessao (JSONL, um objeto por linha):
 *   {"role":"user","content":"pergunta"}
 *   {"role":"assistant","content":"resposta"}
 * Retorna uma string alocada com o JSON do array messages pronto para
 * incluir no payload da API, ou NULL se o arquivo nao existe / vazio.
 * O array NAO inclui a mensagem system - apenas o historico user/assistant. */
static char *sessao_carregar_mensagens(const char *raiz, const char *nome_sessao)
{
    if (!raiz || !nome_sessao || !nome_sessao[0]) return NULL;

    char dir_sessoes[PATH_MAX * 2], path[PATH_MAX * 2];
    snprintf(dir_sessoes, sizeof(dir_sessoes), "%s/sessoes", raiz);
    snprintf(path, sizeof(path), "%s/%s", dir_sessoes, nome_sessao);

    char *conteudo = ler_arquivo(path);
    if (!conteudo) return NULL;

    /* Constroi string com os objetos JSON separados por virgula */
    size_t cap = 8192, len = 0;
    char *msg_array = malloc(cap);
    if (!msg_array) { free(conteudo); return NULL; }
    msg_array[0] = '\0';

    char *linha = conteudo;
    int primeira = 1;
    while (*linha) {
        /* Encontra o fim da linha */
        char *fim = strchr(linha, '\n');
        if (!fim) fim = linha + strlen(linha);

        /* Pula linhas vazias */
        char *p = linha;
        while (p < fim && isspace((unsigned char)*p)) p++;
        if (p < fim) {
            size_t tam = (size_t)(fim - linha);
            /* Verifica se parece JSON valido (comeca com {) */
            const char *j = linha;
            while (j < fim && isspace((unsigned char)*j)) j++;
            if (j < fim && *j == '{') {
                /* Adiciona virgula entre objetos */
                if (len + tam + 8 >= cap) { cap = (len + tam + 8) * 2; char *nv = realloc(msg_array, cap); if (!nv) { free(msg_array); free(conteudo); return NULL; } msg_array = nv; }
                if (!primeira) { msg_array[len++] = ','; msg_array[len++] = '\n'; }
                memcpy(msg_array + len, linha, tam);
                len += tam;
                msg_array[len] = '\0';
                primeira = 0;
            }
        }

        linha = (*fim) ? fim + 1 : fim;
    }

    free(conteudo);

    /* Se nao encontrou nenhuma linha JSON, retorna NULL */
    if (len == 0) { free(msg_array); return NULL; }

    return msg_array;
}

/* Adiciona uma mensagem ao arquivo de sessao */
static int sessao_adicionar(const char *raiz, const char *nome_sessao,
                            const char *role, const char *content)
{
    if (!raiz || !nome_sessao || !nome_sessao[0] || !role || !content) return -1;

    char dir_sessoes[PATH_MAX * 2], path[PATH_MAX * 2];
    snprintf(dir_sessoes, sizeof(dir_sessoes), "%s/sessoes", raiz);

    /* Garante que o diretorio existe */
    struct stat st;
    if (stat(dir_sessoes, &st) != 0)
        mkdir(dir_sessoes, 0775);

    snprintf(path, sizeof(path), "%s/%s", dir_sessoes, nome_sessao);

    /* Escapa o conteudo para JSON */
    char *content_esc = json_escape(content);
    if (!content_esc) return -1;

    /* Monta a linha JSON */
    size_t tam = strlen(role) + strlen(content_esc) + 64;
    char *linha = malloc(tam);
    if (!linha) { free(content_esc); return -1; }
    snprintf(linha, tam, "{\"role\":\"%s\",\"content\":\"%s\"}\n", role, content_esc);
    free(content_esc);

    /* Append ao arquivo */
    FILE *fp = fopen(path, "ab");
    if (!fp) { free(linha); return -1; }
    fputs(linha, fp);
    fclose(fp);
    free(linha);
    return 0;
}

/* ================================================================
 * Agente-AT: publica resposta do parallm para execução pelo espaço SISC
 * ================================================================ */

static int caminho_pp_instalado(char *out, size_t tam)
{
    if (!out || tam == 0) return -1;
    char exe[PATH_MAX];
    ssize_t n = readlink("/proc/self/exe", exe, sizeof(exe) - 1);
    if (n <= 0) return -1;
    exe[n] = '\0';
    char *slash = strrchr(exe, '/');
    if (!slash) return -1;
    *slash = '\0'; /* .../core/comandos */
    slash = strrchr(exe, '/');
    if (!slash) return -1;
    *slash = '\0'; /* .../core */
    slash = strrchr(exe, '/');
    if (!slash) return -1;
    *slash = '\0'; /* raiz sisc */
    if (strlen(exe) + strlen("/pp") + 1 > tam) return -1;
    snprintf(out, tam, "%s/pp", exe);
    return access(out, X_OK) == 0 ? 0 : -1;
}

static int caminho_escuta_sistema(const char *raiz, char *out, size_t tam)
{
    if (!raiz || !out || tam == 0) return -1;
    char caminho[PATH_MAX * 2];
    snprintf(caminho, sizeof(caminho), "%s/escuta/escuta", raiz);
    if (access(caminho, X_OK) == 0) { snprintf(out, tam, "%s", caminho); return 0; }
    return -1;
}

static int executar_programa(char *const args[])
{
    pid_t pid = fork();
    if (pid < 0) return 1;
    if (pid == 0) {
        execv(args[0], args);
        perror(args[0]);
        _exit(127);
    }
    int st = 0;
    if (waitpid(pid, &st, 0) < 0) return 1;
    if (WIFEXITED(st)) return WEXITSTATUS(st);
    if (WIFSIGNALED(st)) return 128 + WTERMSIG(st);
    return 1;
}

static int parallm_publicar_agente_at(const char *raiz, const char *prompt_original,
                                      const char *resposta, const char *contexto_arvore)
{
    if (!raiz || !resposta || !*resposta) {
        fprintf(stderr, "parallm: --executar ignorado porque não houve resposta textual para o agente-at.\n");
        return 1;
    }

    char *p_esc = json_escape(prompt_original ? prompt_original : "");
    char *r_esc = json_escape(resposta ? resposta : "");
    char *h_esc = json_escape(contexto_arvore ? contexto_arvore : "");
    if (!p_esc || !r_esc || !h_esc) { free(p_esc); free(r_esc); free(h_esc); return 1; }

    size_t dados_cap = strlen(p_esc) + strlen(r_esc) + strlen(h_esc) + 1024;
    char *dados = malloc(dados_cap);
    if (!dados) { free(p_esc); free(r_esc); free(h_esc); return 1; }
    snprintf(dados, dados_cap,
        "{\n"
        "  \"origemComando\": \"parallm\",\n"
        "  \"executarAutomaticamente\": true,\n"
        "  \"promptOriginal\": \"%s\",\n"
        "  \"respostaParallm\": \"%s\",\n"
        "  \"contextoHeb\": \"%s\"\n"
        "}\n",
        p_esc, r_esc, h_esc);
    free(p_esc); free(r_esc); free(h_esc);

    char tmp_dados[] = "/tmp/parallm_agente_at_dados_XXXXXX";
    int fd = mkstemp(tmp_dados);
    if (fd < 0) { free(dados); return 1; }
    write(fd, dados, strlen(dados));
    close(fd);
    free(dados);

    char pp_path[PATH_MAX];
    if (caminho_pp_instalado(pp_path, sizeof(pp_path)) != 0) {
        fprintf(stderr, "parallm: não foi possível localizar pp para publicar agente-at.\n");
        unlink(tmp_dados);
        return 1;
    }

    char arg_dados[PATH_MAX * 2];
    snprintf(arg_dados, sizeof(arg_dados), "--dados-arquivo=%s", tmp_dados);
    char *api_args[] = { pp_path, "--api=agente-at.executar", arg_dados,
                         "--origem=sistema__pp", "--prioridade=alta", NULL };

    printf("\n=== PUBLICANDO EXECUÇÃO AUTOMÁTICA NO agente-at ===\n");
    fflush(stdout);
    int rc = executar_programa(api_args);
    unlink(tmp_dados);
    if (rc != 0) {
        fprintf(stderr, "parallm: falha ao publicar mensagem para agente-at (codigo %d).\n", rc);
        return rc;
    }

    char escuta_path[PATH_MAX];
    if (caminho_escuta_sistema(raiz, escuta_path, sizeof(escuta_path)) != 0) {
        fprintf(stderr, "parallm: mensagem publicada, mas não foi possível localizar escuta/escuta para processar automaticamente.\n");
        return 0;
    }

    printf("\n=== ACIONANDO ESCUTA PARA PROCESSAR agente-at ===\n");
    fflush(stdout);
    char *esc_args[] = { escuta_path, "--once", "--lote=20", "--timeout-ator=900", NULL };
    rc = executar_programa(esc_args);
    if (rc != 0) fprintf(stderr, "parallm: escuta retornou código %d ao processar agente-at.\n", rc);
    return rc;
}

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

int main(int argc, char *argv[])
{     if (restricao_verificar("parallm") != 0) return 1; 
    init_mapa_acento();

    const char *prompt     = NULL;
    const char *conta_llm  = "llm.json";
    char        conta_llm_normalizada[PATH_MAX];
    int         conta_llm_explicit = 0;
    int         relatorio  = 0;
    int         sem_contexto = 0;
    char        arvore_nome[256] = "";  /* nome da arvore para --arvore=<nome> */
    int         verbose    = 0;
    int         executar_at = 0;
    int         nivel      = -1;  /* -1 = não especificado */
    const char *sessao     = NULL;

    /* Parse de argumentos */
    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "--rel") == 0) {
            relatorio = 1;
        } else if (strcmp(argv[i], "--sem-contexto") == 0 || strcmp(argv[i], "--sem-heb") == 0) {
            /* --sem-heb mantido como alias de compatibilidade (comandos internos ainda o utilizam). */
            sem_contexto = 1;
        } else if (strcmp(argv[i], "--arvore") == 0) {
            sem_contexto = 0;
            arvore_nome[0] = '\0';
        } else if (strncmp(argv[i], "--arvore=", 9) == 0) {
            sem_contexto = 0;
            snprintf(arvore_nome, sizeof(arvore_nome), "%s", argv[i] + 9);
        } else if (strncmp(argv[i], "--heb=", 6) == 0) {
            /* Compatibilidade: --heb=auto/arvore[:nome] viram modo arvore; --heb=dic foi removido. */
            const char *modo = argv[i] + 6;
            if (strcmp(modo, "dic") == 0) {
                fprintf(stderr, "parallm: o modo '--heb=dic' foi removido; o contexto agora e sempre via arvores SA.\n");
                return 1;
            }
            if (strcmp(modo, "auto") == 0 || strcmp(modo, "arvore") == 0) {
                sem_contexto = 0;
                arvore_nome[0] = '\0';
            } else if (strncmp(modo, "arvore:", 7) == 0) {
                sem_contexto = 0;
                snprintf(arvore_nome, sizeof(arvore_nome), "%s", modo + 7);
            } else {
                fprintf(stderr, "parallm: use --arvore, --arvore=<nome> ou --sem-contexto.\n");
                return 1;
            }
        } else if (strcmp(argv[i], "--verbose") == 0) {
            verbose = 1;
        } else if (strcmp(argv[i], "--executar") == 0 || strcmp(argv[i], "--agente-at") == 0 || strcmp(argv[i], "--executar-agente-at") == 0) {
            executar_at = 1;
        } else if (strncmp(argv[i], "--conta-llm=", 12) == 0) {
            conta_llm = argv[i] + 12;
            conta_llm_explicit = 1;
        } else if (strncmp(argv[i], "--nivel=", 8) == 0) {
            nivel = atoi(argv[i] + 8);
        } else if (strncmp(argv[i], "-s=", 3) == 0) {
            sessao = argv[i] + 3;
        } else if (argv[i][0] != '-') {
            if (!prompt) prompt = argv[i];
            /* O prompt com espaços deve vir entre aspas; argumentos soltos extras são ignorados. */
        } else if (strcmp(argv[i], "--ajuda") == 0 || strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
            fputs(
                "Uso:\n"
                "  pp --parallm=\"texto\" [opcoes]\n"
                "  pp --parallm \"texto\" [opcoes]\n"
                "  pp --parallm --ajuda:<grupo>\n"
                "\n"
                "Descrição:\n"
                "  Envia um prompt para uma LLM usando a conta configurada no SISC.\n"
                "  Anexa contexto compacto via arvores SA (sa-ctx) com evidencias de ra/codigo real.\n"
                "  Não chama agente-at automaticamente; para isso use --executar ou --agente-at.\n"
                "\n"
                "Grupos de ajuda: uso, prompt, contexto, executar, conta, relatorio, sessao, exemplos, duvidas.\n"
                "\n"
                "1) Prompt [prompt entrada texto]\n"
                "  Prompt é a mensagem/instrução enviada para a LLM.\n"
                "  Não existe flag --prompt neste comando.\n"
                "  Formas equivalentes:\n"
                "    pp --parallm=\"Explique o propósito\"\n"
                "    pp --parallm \"Explique o propósito\"\n"
                "  Use aspas quando o prompt tiver espaços; argumentos soltos extras são ignorados.\n"
                "\n"
                "2) Contexto via árvores SA [contexto arvore]\n"
                "  O contexto e montado das arvores SA via sa-ctx: sistema alvo e, quando fizer sentido, ferramenta SISC; inclui ra seguro.\n"
                "  Árvores do sistema alvo ficam em <sistema>/arvores; contexto da ferramenta fica em sisc/arvores.\n"
                "  --arvore                       Usa as arvores SA relevantes (padrao)\n"
                "  --arvore=<nome>                Restringe a uma árvore específica como contexto\n"
                "  --sem-contexto                 Envia somente o prompt, sem contexto local\n"
                "  Política de exclusão: somente sisc/.ignorecontexto, mesmo dentro de sistemas.\n"
                "  Compatibilidade: --heb=arvore[:nome] e --sem-heb ainda sao aceitos; --heb=dic foi removido.\n"
                "\n"
                "3) Execução com agente-at [executar agente-at escuta]\n"
                "  pp --parallm=\"ccc\" chama somente a LLM.\n"
                "  --executar                     Publica a resposta/contexto para agente-at e aciona escuta\n"
                "  --agente-at                    Alias de --executar\n"
                "  Use --executar só quando quiser tentar aplicar/criar/alterar artefatos.\n"
                "\n"
                "4) Conta LLM [conta llm modelo]\n"
                "  --conta-llm=<arquivo.json>     Força uma conta LLM para esta chamada\n"
                "  SISC_CONTA_LLM                 Variável de ambiente também aceita\n"
                "  Sem conta explícita, tenta config/mapa-comandos.json; depois llm.json.\n"
                "\n"
                "5) Relatório, nível e diagnóstico [relatorio nivel verbose]\n"
                "  --rel                          Exibe relatório de tokens/contexto e contagem de fontes: sa-ctx, ra e origem sistema/ferramenta\n"
                "  --nivel=<0..3>                 Nível de reasoning/pensamento, quando suportado\n"
                "  --verbose                      Exibe detalhes do processo\n"
                "\n"
                "6) Sessão [sessao historico]\n"
                "  -s=<nome>                      Envia histórico e salva perguntas/respostas na sessão\n"
                "\n"
                "7) Exemplos [exemplos duvidas]\n"
                "  pp --parallm=\"Explique o propósito deste projeto\"\n"
                "  pp --parallm=\"Quais são as dependências?\" --rel\n"
                "  pp --parallm=\"O que é uma API REST?\" --sem-contexto\n"
                "  pp --parallm=\"Analise a arquitetura\" --arvore --rel\n"
                "  pp --parallm=\"Crie/atualize os artefatos necessários\" --executar\n"
                "  pp --parallm=\"Continue a análise anterior\" -s=minha-sessao\n",
                stdout);
            return 0;
        }
    }

    char *prompt_stdin = NULL;
    if (!prompt && !isatty(STDIN_FILENO)) {
        size_t cap_in = 65536, len_in = 0;
        prompt_stdin = malloc(cap_in);
        if (!prompt_stdin) { fprintf(stderr, "parallm: erro de memória\n"); return 1; }
        char buf_in[8192];
        ssize_t nr;
        while ((nr = read(STDIN_FILENO, buf_in, sizeof(buf_in))) > 0) {
            if (len_in + (size_t)nr + 1 > cap_in) {
                while (len_in + (size_t)nr + 1 > cap_in) cap_in *= 2;
                char *nx = realloc(prompt_stdin, cap_in);
                if (!nx) { free(prompt_stdin); fprintf(stderr, "parallm: erro de memória\n"); return 1; }
                prompt_stdin = nx;
            }
            memcpy(prompt_stdin + len_in, buf_in, (size_t)nr);
            len_in += (size_t)nr;
        }
        prompt_stdin[len_in] = '\0';
        if (len_in > 0) prompt = prompt_stdin;
    }

    if (!prompt) {
        fprintf(stderr, "parallm: nenhum prompt fornecido.\n");
        fprintf(stderr, "Uso: pp --parallm=\"seu texto aqui\" ou envie o prompt via stdin\n");
        free(prompt_stdin);
        return 1;
    }

    if (!conta_llm_explicit) {
        const char *env_conta = getenv("SISC_CONTA_LLM");
        if (env_conta && env_conta[0]) {
            conta_llm = env_conta;
            conta_llm_explicit = 1;
        }
    }
    if (conta_llm_explicit) {
        normalizar_nome_conta_llm(conta_llm, conta_llm_normalizada, sizeof(conta_llm_normalizada));
        if (conta_llm_normalizada[0]) conta_llm = conta_llm_normalizada;
    }

    /* Cada comando trata o diretório de execução como raiz do sistema. */
    char raiz_buf[PATH_MAX];
    if (!getcwd(raiz_buf, sizeof(raiz_buf))) {
        perror("getcwd");
        return 1;
    }
    char *raiz = strdup(raiz_buf);

    /* Carrega filtros de contexto */
    ctx_carregar_ignore(raiz);
    if (!raiz) return 1;

    /* Resolve a conta LLM pelo mapa de comandos, salvo quando --conta-llm foi informado. */
    char conta_mapeada[PATH_MAX];
    if (!conta_llm_explicit && resolver_conta_llm_mapeada(raiz, argv[0], conta_mapeada, sizeof(conta_mapeada), verbose)) {
        conta_llm = conta_mapeada;
    }

    /* Carrega configuração da conta LLM */
    char dir_contas[PATH_MAX * 2];
    resolver_subdir_sistema(raiz, "contas-llm", dir_contas, sizeof(dir_contas));
    char caminho_conta[PATH_MAX * 2];
    snprintf(caminho_conta, sizeof(caminho_conta), "%s/%s", dir_contas, conta_llm);

    char *json_conta = ler_arquivo(caminho_conta);
    if (!json_conta) {
        fprintf(stderr, "parallm: conta não encontrada: %s\n", conta_llm);
        fprintf(stderr, "  Tentado: %s\n", caminho_conta);
        free(raiz);
        return 1;
    }

    char *modelo      = json_extrair_string(json_conta, "modelo");
    char *url         = json_extrair_string(json_conta, "url");
    char *apiKey      = json_extrair_string(json_conta, "apiKey");  /* nome do segredo key */
    char *authArquivo = json_extrair_string(json_conta, "arquivo"); /* nome do segredo oauth */
    char *tipoConta   = json_extrair_string(json_conta, "tipo");
    long  max_tok     = json_extrair_int(json_conta, "maxTokensResposta", 16384);
    long  timeout_seg = json_extrair_int(json_conta, "timeoutSegundos", PARALLM_TIMEOUT_SEG);
    if (timeout_seg < 30) timeout_seg = 30;
    if (timeout_seg > 1800) timeout_seg = 1800;

    if (!modelo || !url) {
        fprintf(stderr, "parallm: JSON de conta inválido (faltam modelo/url)\n");
        free(modelo); free(url); free(apiKey); free(authArquivo); free(tipoConta); free(json_conta); free(raiz);
        return 1;
    }

    /* Resolve a chave/API token real */
    char *chave_api = NULL;
    char *oauth_account_id = NULL;
    char *provedor = json_extrair_string(json_conta, "provedor");
    int usar_codex_oauth = (tipoConta && strcmp(tipoConta, "oauth") == 0 && provedor &&
                            (strcmp(provedor, "codex") == 0 || strcmp(provedor, "openai-codex") == 0));
    /* Tier gratuito do OpenCode Zen (zen/v1) exige cliente/sessao; modelos
     * muse-spark usam a API /responses em vez de /chat/completions. */
    int eh_opencode_zen = (url && strstr(url, "opencode.ai/zen") != NULL);
    int usar_responses = (!usar_codex_oauth && modelo && strncmp(modelo, "muse-spark", 10) == 0);

    /* 1. OAuth: lê access token de secretos/<arquivo>.json (e atualiza se expirado, quando suportado) */
    if (!chave_api && tipoConta && strcmp(tipoConta, "oauth") == 0 && authArquivo) {
        chave_api = resolver_oauth_access(raiz, authArquivo, provedor, verbose);
        oauth_account_id = resolver_oauth_account_id(raiz, authArquivo);
    }

    /* 2. Tenta variável de ambiente: <PROVEDOR>_API_KEY */
    if (!chave_api && provedor) {
        char env_name[512];
        char prov_env[256];
        snprintf(prov_env, sizeof(prov_env), "%s", provedor);
        for (char *p = prov_env; *p; p++) *p = (char)toupper((unsigned char)*p);
        snprintf(env_name, sizeof(env_name), "%s_API_KEY", prov_env);
        char *env_val = getenv(env_name);
        if (env_val && env_val[0]) chave_api = strdup(env_val);
    }

    /* 3. Tenta arquivo secreto pelo nome da conexão */
    if (!chave_api) {
        char *conexao = json_extrair_string(json_conta, "conexao");
        if (conexao) {
            char dir_secretos[PATH_MAX * 2];
            resolver_subdir_sistema(raiz, "secretos", dir_secretos, sizeof(dir_secretos));
            char sec_path[PATH_MAX * 2];
            snprintf(sec_path, sizeof(sec_path), "%s/%s.txt", dir_secretos, conexao);
            chave_api = ler_arquivo(sec_path);
            if (chave_api) { /* trim */ while (*chave_api && isspace((unsigned char)*chave_api)) memmove(chave_api, chave_api + 1, strlen(chave_api)); char *e = chave_api + strlen(chave_api) - 1; while (e >= chave_api && isspace((unsigned char)*e)) *e-- = '\0'; }
            free(conexao);
        }
    }

    /* 4. Tenta pelo nome do apiKey */
    if (!chave_api && apiKey) {
        char dir_secretos[PATH_MAX * 2];
        resolver_subdir_sistema(raiz, "secretos", dir_secretos, sizeof(dir_secretos));
        char sec_path[PATH_MAX * 2];
        snprintf(sec_path, sizeof(sec_path), "%s/%s.txt", dir_secretos, apiKey);
        chave_api = ler_arquivo(sec_path);
        if (chave_api) { while (*chave_api && isspace((unsigned char)*chave_api)) memmove(chave_api, chave_api + 1, strlen(chave_api)); char *e = chave_api + strlen(chave_api) - 1; while (e >= chave_api && isspace((unsigned char)*e)) *e-- = '\0'; }
    }

    free(provedor);
    free(tipoConta);
    free(authArquivo);

    if (usar_codex_oauth && !oauth_account_id) {
        fprintf(stderr, "parallm: OAuth codex sem accountId em secretos/<arquivo>.json\n");
        free(modelo); free(url); free(apiKey); free(chave_api); free(json_conta); free(raiz);
        return 1;
    }

    if (!chave_api) {
        fprintf(stderr, "parallm: não foi possível resolver a chave/API token.\n");
        fprintf(stderr, "  Key: variável <PROVEDOR>_API_KEY ou secretos/<conexao>.txt\n");
        fprintf(stderr, "  OAuth: secretos/<arquivo>.json apontado por autenticacao.arquivo\n");
        free(modelo); free(url); free(apiKey); free(json_conta); free(raiz);
        return 1;
    }

    if (verbose) {
        fprintf(stderr, "  [config] modelo=%s url=%s\n", modelo, url);
        fprintf(stderr, "  [auth] chave resolvida (%zu caracteres)\n", strlen(chave_api));
    }

    /* ================================================================
     * Monta o prompt com contexto das arvores SA (se habilitado)
     * ================================================================ */

    char *contexto_arvore = NULL;
    char modo_contexto_usado[32] = "sem-contexto";

    if (!sem_contexto) {
        if (verbose) fprintf(stderr, "  [arvore] construindo contexto via arvores SA%s%s...\n",
                             arvore_nome[0] ? " filtrada: " : "", arvore_nome);
        contexto_arvore = arvore_construir_contexto(prompt, raiz, arvore_nome, verbose);
        snprintf(modo_contexto_usado, sizeof(modo_contexto_usado), "%s", contexto_arvore ? "arvore" : "nenhum");
    }

    /* System prompt enriquecido com descricao do projeto */
    char *descricao_projeto = extrair_descricao_projeto(raiz);
    const char *sys_base =
        "Voce e um assistente tecnico especializado. "
        "Responda em portugues de forma clara, objetiva e bem estruturada. "
        "Utilize o contexto do projeto fornecido abaixo como fonte primaria; ele prioriza arvores SA e evidencia codigo/arquivos reais via ra quando disponivel. "
        "Se a informacao necessaria nao estiver no contexto, use seu conhecimento geral "
        "mas indique claramente quando estiver inferindo alem do contexto fornecido.\n";

    char *sys_prompt = NULL;
    if (descricao_projeto) {
        size_t len = strlen(sys_base) + strlen(descricao_projeto) + (contexto_arvore ? strlen(contexto_arvore) : 0) + 256;
        sys_prompt = malloc(len);
        if (sys_prompt) {
            if (contexto_arvore)
                snprintf(sys_prompt, len,
                    "%s\n"
                    "=== Sobre este projeto ===\n"
                    "%s\n\n"
                    "=== Contexto do projeto ===\n"
                    "%s",
                    sys_base, descricao_projeto, contexto_arvore);
            else
                snprintf(sys_prompt, len,
                    "%s\n"
                    "=== Sobre este projeto ===\n"
                    "%s\n",
                    sys_base, descricao_projeto);
        }
    } else if (contexto_arvore) {
        size_t len = strlen(sys_base) + strlen(contexto_arvore) + 64;
        sys_prompt = malloc(len);
        if (sys_prompt) snprintf(sys_prompt, len, "%s\nContexto do projeto:\n%s", sys_base, contexto_arvore);
    } else {
        sys_prompt = strdup(sys_base);
    }
    free(descricao_projeto);

    /* Monta payload JSON para a API */
    char *payload = NULL;
    if (usar_codex_oauth) {
        size_t msg_len = strlen(sys_prompt) + strlen(prompt) + 32;
        char *msg = malloc(msg_len);
        if (msg) snprintf(msg, msg_len, "%s\n\nPergunta:\n%s", sys_prompt, prompt);
        char *msg_escaped = msg ? json_escape(msg) : NULL;
        free(msg);

        char reasoning[64] = "";
        if (nivel >= 0) snprintf(reasoning, sizeof(reasoning), ",\"reasoning\":{\"effort\":\"%s\"}",
                                 nivel == 0 ? "low" : nivel == 1 ? "medium" : "high");
        size_t payload_cap = (msg_escaped ? strlen(msg_escaped) : 0) + strlen(modelo) + 700;
        payload = malloc(payload_cap);
        if (payload && msg_escaped) {
            snprintf(payload, payload_cap,
                     "{\"model\":\"%s\",\"store\":false,\"stream\":true,"
                     "\"instructions\":\"Voce e um assistente tecnico.\","
                     "\"input\":[{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"%s\"}]}],"
                     "\"text\":{\"verbosity\":\"low\"},"
                     "\"include\":[\"reasoning.encrypted_content\"],"
                     "\"tool_choice\":\"auto\",\"parallel_tool_calls\":true,"
                     "\"prompt_cache_key\":\"sisc_%ld\"%s}",
                     modelo, msg_escaped, (long)time(NULL), reasoning);
        }
        free(msg_escaped);
    } else if (usar_responses) {
        size_t msg_len = strlen(sys_prompt) + strlen(prompt) + 32;
        char *msg = malloc(msg_len);
        if (msg) snprintf(msg, msg_len, "%s\n\nPergunta:\n%s", sys_prompt, prompt);
        char *msg_escaped = msg ? json_escape(msg) : NULL;
        free(msg);
        size_t payload_cap = (msg_escaped ? strlen(msg_escaped) : 0) + strlen(modelo) + 256;
        payload = malloc(payload_cap);
        if (payload && msg_escaped) {
            snprintf(payload, payload_cap,
                     "{\"model\":\"%s\","
                     "\"input\":[{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"%s\"}]}]}",
                     modelo, msg_escaped);
        }
        free(msg_escaped);
    } else {
        char *prompt_escaped = json_escape(prompt);
        char *sys_escaped    = json_escape(sys_prompt);

        /* Carrega historico da sessao se -s foi informado */
        char *historico = NULL;
        if (sessao && sessao[0]) {
            historico = sessao_carregar_mensagens(raiz, sessao);
            if (verbose && historico)
                fprintf(stderr, "  [sessao] historico carregado de sessoes/%s (%zu bytes)\n",
                        sessao, strlen(historico));
        }

        char nivel_str[32] = "";
        if (nivel >= 0) snprintf(nivel_str, sizeof(nivel_str), ", \"reasoning_effort\": \"%s\"",
                                 nivel == 0 ? "low" : nivel == 1 ? "medium" : "high");

        size_t payload_cap = strlen(prompt_escaped) + strlen(sys_escaped) + strlen(modelo) + 512;
        if (historico) payload_cap += strlen(historico);
        payload = malloc(payload_cap);
        if (payload) {
            if (historico) {
                snprintf(payload, payload_cap,
                     "{"
                     "\"model\": \"%s\","
                     "\"messages\": ["
                       "{\"role\": \"system\", \"content\": \"%s\"},"
                       "%s,"
                       "{\"role\": \"user\", \"content\": \"%s\"}"
                     "],"
                     "\"max_tokens\": %ld"
                     "%s"
                     "}",
                     modelo, sys_escaped, historico, prompt_escaped, max_tok, nivel_str);
            } else {
                snprintf(payload, payload_cap,
                     "{"
                     "\"model\": \"%s\","
                     "\"messages\": ["
                       "{\"role\": \"system\", \"content\": \"%s\"},"
                       "{\"role\": \"user\", \"content\": \"%s\"}"
                     "],"
                     "\"max_tokens\": %ld"
                     "%s"
                     "}",
                     modelo, sys_escaped, prompt_escaped, max_tok, nivel_str);
            }
        }

        free(prompt_escaped);
        free(sys_escaped);
        free(historico);
    }

    if (!payload) {
        fprintf(stderr, "parallm: erro ao montar payload\n");
        free(modelo); free(url); free(chave_api); free(apiKey); free(json_conta);
        free(sys_prompt); free(contexto_arvore); free(raiz);
        return 1;
    }

    /* Salva payload em arquivo temporário para o curl */
    char tmp_payload[] = "/tmp/parallm_payload_XXXXXX";
    int tmp_fd = mkstemp(tmp_payload);
    if (tmp_fd < 0) {
        fprintf(stderr, "parallm: erro ao criar arquivo temporário: %s\n", strerror(errno));
        free(payload); free(modelo); free(url); free(chave_api); free(apiKey);
        free(json_conta); free(sys_prompt); free(contexto_arvore); free(raiz);
        return 1;
    }
    write(tmp_fd, payload, strlen(payload));
    close(tmp_fd);

    /* ================================================================
     * Chama a API via curl
     * ================================================================ */

    char endpoint[PATH_MAX * 2];
    /* Remove trailing slash da url */
    size_t url_len = strlen(url);
    while (url_len > 0 && url[url_len - 1] == '/') url_len--;
    if (usar_codex_oauth) {
        if (url_len >= strlen("/codex/responses") && strncmp(url + url_len - strlen("/codex/responses"), "/codex/responses", strlen("/codex/responses")) == 0) {
            snprintf(endpoint, sizeof(endpoint), "%.*s", (int)url_len, url);
        } else if (url_len >= strlen("/codex") && strncmp(url + url_len - strlen("/codex"), "/codex", strlen("/codex")) == 0) {
            snprintf(endpoint, sizeof(endpoint), "%.*s/responses", (int)url_len, url);
        } else {
            snprintf(endpoint, sizeof(endpoint), "%.*s/codex/responses", (int)url_len, url);
        }
    } else if (usar_responses) {
        if (url_len >= strlen("/responses") && strncmp(url + url_len - strlen("/responses"), "/responses", strlen("/responses")) == 0) {
            snprintf(endpoint, sizeof(endpoint), "%.*s", (int)url_len, url);
        } else {
            snprintf(endpoint, sizeof(endpoint), "%.*s/responses", (int)url_len, url);
        }
    } else {
        snprintf(endpoint, sizeof(endpoint), "%.*s/chat/completions", (int)url_len, url);
    }

    /* Tier gratuito do OpenCode Zen exige identificacao do cliente/sessao */
    char sess_headers[256] = "";
    if (eh_opencode_zen) {
        snprintf(sess_headers, sizeof(sess_headers),
                 "-H 'x-opencode-client: pi' -H 'x-opencode-session: sisc_%ld_%ld' ",
                 (long)getpid(), (long)time(NULL));
    }

    char tmp_resposta[] = "/tmp/parallm_resposta_XXXXXX";
    int tmp_resp_fd = mkstemp(tmp_resposta);
    if (tmp_resp_fd < 0) {
        fprintf(stderr, "parallm: erro ao criar arquivo temporário de resposta: %s\n", strerror(errno));
        unlink(tmp_payload);
        free(payload); free(modelo); free(url); free(chave_api); free(oauth_account_id); free(apiKey);
        free(json_conta); free(sys_prompt); free(contexto_arvore); free(raiz);
        return 1;
    }
    close(tmp_resp_fd);

    /* Monta comando curl */
    char *auth_header = malloc(strlen(chave_api) + 64);
    if (!auth_header) { unlink(tmp_payload); unlink(tmp_resposta); free(payload); free(modelo); free(url); free(chave_api); free(oauth_account_id); free(apiKey); free(json_conta); free(sys_prompt); free(contexto_arvore); free(raiz); return 1; }
    sprintf(auth_header, "Authorization: Bearer %s", chave_api);

    size_t cmd_cap = strlen(auth_header) + strlen(sess_headers) + strlen(tmp_payload) + strlen(tmp_resposta) + strlen(endpoint) + (oauth_account_id ? strlen(oauth_account_id) : 0) + 1024;
    char *cmd = malloc(cmd_cap);
    if (!cmd) { fprintf(stderr, "parallm: erro de memória\n"); unlink(tmp_payload); unlink(tmp_resposta); free(auth_header); free(payload); free(modelo); free(url); free(chave_api); free(oauth_account_id); free(apiKey); free(json_conta); free(sys_prompt); free(contexto_arvore); free(raiz); return 1; }
    if (usar_codex_oauth) {
        snprintf(cmd, cmd_cap,
                 "curl -s -w '\\n%%{http_code}' --max-time %d "
                 "-H '%s' "
                 "-H 'chatgpt-account-id: %s' "
                 "-H 'originator: sisc' "
                 "-H 'User-Agent: sisc' "
                 "-H 'OpenAI-Beta: responses=experimental' "
                 "-H 'accept: text/event-stream' "
                 "-H 'content-type: application/json' "
                 "-H 'session_id: sisc_%ld' "
                 "-H 'x-client-request-id: sisc_%ld' "
                 "-d @%s -o %s '%s'",
                 (int)timeout_seg, auth_header, oauth_account_id ? oauth_account_id : "",
                 (long)time(NULL), (long)time(NULL), tmp_payload, tmp_resposta, endpoint);
    } else {
        snprintf(cmd, cmd_cap,
                 "curl -s -w '\\n%%{http_code}' --max-time %d "
                 "%s"
                 "-H 'Content-Type: application/json' "
                 "-H '%s' "
                 "-d @%s "
                 "-o %s "
                 "'%s'",
                 (int)timeout_seg, sess_headers, auth_header, tmp_payload, tmp_resposta, endpoint);
    }

    if (verbose) {
        fprintf(stderr, "  [curl] %s\n", endpoint);
        fprintf(stderr, "  [payload] %zu bytes -> %s\n", strlen(payload), tmp_payload);
        fprintf(stderr, "  [timeout] %ld segundos\n", timeout_seg);
    }

    FILE *curl_fp = popen(cmd, "r");
    if (!curl_fp) {
        fprintf(stderr, "parallm: erro ao executar curl\n");
        unlink(tmp_payload); unlink(tmp_resposta);
        free(cmd); free(auth_header); free(payload); free(modelo); free(url); free(chave_api);
        free(apiKey); free(json_conta); free(sys_prompt); free(contexto_arvore); free(raiz);
        return 1;
    }

    /* Lê o código HTTP (última linha da saída do curl -w) */
    char http_code_str[16] = "";
    char buf[256];
    while (fgets(buf, sizeof(buf), curl_fp)) {
        /* última linha conterá apenas o código */
        size_t bl = strlen(buf);
        if (bl > 0 && buf[bl - 1] == '\n') buf[bl - 1] = '\0';
        if (buf[0] >= '0' && buf[0] <= '9' && strlen(buf) <= 4) {
            memcpy(http_code_str, buf, sizeof(http_code_str) - 1);
            http_code_str[sizeof(http_code_str) - 1] = '\0';
        }
    }
    int ret = pclose(curl_fp);

    /* Lê a resposta */
    char *json_resposta = ler_arquivo(tmp_resposta);
    if (!json_resposta) {
        fprintf(stderr, "parallm: resposta vazia ou erro ao ler %s (curl ret=%d)\n", tmp_resposta, ret);
        unlink(tmp_payload); unlink(tmp_resposta);
        free(cmd); free(auth_header); free(payload); free(modelo); free(url); free(chave_api);
        free(apiKey); free(json_conta); free(sys_prompt); free(contexto_arvore); free(raiz);
        return 1;
    }

    int http_code = atoi(http_code_str);
    if (ret != 0 || http_code <= 0) {
        fprintf(stderr, "parallm: curl falhou ou não retornou HTTP válido (curl ret=%d, http=%s)\n", ret, http_code_str[0] ? http_code_str : "vazio");
        fprintf(stderr, "Resposta parcial: %.2000s\n", json_resposta);
        unlink(tmp_payload); unlink(tmp_resposta);
        free(cmd); free(auth_header); free(payload); free(modelo); free(url); free(chave_api);
        free(apiKey); free(json_conta); free(sys_prompt); free(contexto_arvore);
        free(json_resposta); free(raiz);
        return 1;
    }
    if (http_code >= 400) {
        fprintf(stderr, "parallm: erro HTTP %d\n", http_code);
        fprintf(stderr, "Resposta: %.2000s\n", json_resposta);
        unlink(tmp_payload); unlink(tmp_resposta);
        free(cmd); free(auth_header); free(payload); free(modelo); free(url); free(chave_api);
        free(apiKey); free(json_conta); free(sys_prompt); free(contexto_arvore);
        free(json_resposta); free(raiz);
        return 1;
    }

    /* ================================================================
     * Extrai a resposta
     * ================================================================ */

    /* Procura resposta: SSE do Codex ou JSON chat/completions */
    char *conteudo_resposta = NULL;
    if (usar_codex_oauth) {
        conteudo_resposta = extrair_codex_texto_sse(json_resposta);
    }

    const char *msg_pos = conteudo_resposta ? NULL : strstr(json_resposta, "\"message\"");
    if (msg_pos) {
        const char *content_pos = strstr(msg_pos, "\"content\"");
        if (content_pos) conteudo_resposta = json_extrair_string(content_pos, "content");
    }

    /* Fallback API /responses: output[].content[] com "output_text"/"text" */
    if (!conteudo_resposta) {
        const char *out_pos = strstr(json_resposta, "\"output_text\"");
        if (out_pos) conteudo_resposta = json_extrair_string(out_pos, "text");
    }

    /* Extrai usage */
    long prompt_tokens     = json_extrair_int(json_resposta, "prompt_tokens", -1);
    long completion_tokens = json_extrair_int(json_resposta, "completion_tokens", -1);
    long total_tokens      = json_extrair_int(json_resposta, "total_tokens", -1);
    if (prompt_tokens < 0) prompt_tokens = json_extrair_int(json_resposta, "input_tokens", -1);
    if (completion_tokens < 0) completion_tokens = json_extrair_int(json_resposta, "output_tokens", -1);
    if (total_tokens < 0 && prompt_tokens >= 0 && completion_tokens >= 0) total_tokens = prompt_tokens + completion_tokens;

    /* ================================================================
     * Salva na sessao (se -s foi informado)
     * ================================================================ */

    if (sessao && sessao[0] && conteudo_resposta) {
        /* Salva a pergunta e a resposta no arquivo de sessao */
        if (sessao_adicionar(raiz, sessao, "user", prompt) == 0 &&
            sessao_adicionar(raiz, sessao, "assistant", conteudo_resposta) == 0) {
            if (verbose)
                fprintf(stderr, "  [sessao] conversa salva em sessoes/%s\n", sessao);
        } else {
            fprintf(stderr, "  [aviso] nao foi possivel salvar sessao sessoes/%s\n", sessao);
        }
    }

    /* ================================================================
     * Exibe resultado
     * ================================================================ */

    printf("\n");
    if (conteudo_resposta) {
        printf("%s\n", conteudo_resposta);
    } else {
        printf("(não foi possível extrair a resposta)\n");
        printf("Resposta bruta: %.1000s\n", json_resposta);
    }

    printf("\n[parallm] método de contexto usado: %s\n", modo_contexto_usado);

    if (relatorio) {
        size_t ctx_bytes = contexto_arvore ? strlen(contexto_arvore) : 0;
        RelSaCtx rel_sa = {0};
        rel_analisar_sa_ctx(contexto_arvore, &rel_sa);

        printf("\n--- Relatorio de Contexto ---\n");
        printf("Contexto via arvores SA: %zu bytes (~%zu tokens)\n", ctx_bytes, estimar_tokens(contexto_arvore, ctx_bytes));
        printf("Prompt (sem contexto):   %zu bytes (~%zu tokens)\n", strlen(prompt), strlen(prompt) / 4);
        printf("Modo solicitado:         %s\n", sem_contexto ? "sem-contexto" : (arvore_nome[0] ? "arvore:<nome>" : "arvore"));
        printf("Modo usado no envio:     %s\n", modo_contexto_usado);

        printf("\n--- Relatorio de Fontes do Contexto ---\n");
        printf("sa-ctx itens retornados: %d (sistema=%d, ferramenta=%d, outros=%d)\n",
               rel_sa.itens_total, rel_sa.itens_sistema, rel_sa.itens_ferramenta, rel_sa.itens_outros);
        printf("sa-ctx RA referenciados: %d arquivo(s)/referencia(s) unico(s)\n", rel_sa.ra_referenciados.total);
        printf("sa-ctx RA anexados:      %d unico(s), %d bloco(s) de evidencia\n",
               rel_sa.ra_anexados.total, rel_sa.ra_blocos);

        printf("\n--- Relatorio de Tokens (API) ---\n");
        printf("Tokens de entrada:  %ld\n", prompt_tokens >= 0 ? prompt_tokens : -1L);
        printf("Tokens de resposta: %ld\n", completion_tokens >= 0 ? completion_tokens : -1L);
        printf("Tokens totais:      %ld\n", total_tokens >= 0 ? total_tokens : -1L);
        rel_sa_ctx_free(&rel_sa);
    }

    int rc_agente_at = 0;
    if (executar_at) {
        rc_agente_at = parallm_publicar_agente_at(raiz, prompt, conteudo_resposta, contexto_arvore);
    }

    /* Limpeza */
    unlink(tmp_payload);
    unlink(tmp_resposta);
    free(cmd);
    free(auth_header);
    free(payload);
    free(modelo);
    free(url);
    free(chave_api);
    free(oauth_account_id);
    free(apiKey);
    free(json_conta);
    free(sys_prompt);
    free(contexto_arvore);
    free(json_resposta);
    free(conteudo_resposta);
    free(raiz);
    free(prompt_stdin);

    return rc_agente_at ? rc_agente_at : 0;
}
