/*
 * api - publica chamadas da API declarativa do SISC no espaco nativo
 * Uso: pp --api=<idmensagem> --dados=<json|arquivo> [opcoes]
 *
 * O comando NAO executa destinatarios diretamente. Ele le web-api/catalogo-mensagens.json,
 * resolve o destino logico da mensagem, monta o envelope canonico de formatos-escuta e
 * publica atomica e exclusivamente em espaco/entrada para a escuta.c entregar.
 */
#define _DEFAULT_SOURCE

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include "../restricao.h"

#ifndef PATH_MAX
#define PATH_MAX 4096
#endif

#define PROTO_NOME "siscore-protocolo-objetos"
#define PROTO_VERSAO 1
#define MAX_ID 128
#define MAX_ENTIDADE 160
#define MAX_ARQ_BYTES (10 * 1024 * 1024)

typedef enum { J_NULL, J_BOOL, J_NUMBER, J_STRING, J_ARRAY, J_OBJECT } JType;

typedef struct Json Json;
typedef struct { char *key; Json *value; } Pair;

struct Json {
    JType type;
    union {
        bool b;
        double n;
        char *s;
        struct { Json **items; size_t len; } array;
        struct { Pair *pairs; size_t len; } object;
    } u;
};

typedef struct {
    const char *p;
    const char *end;
    char erro[256];
} Parser;

typedef struct {
    char id[MAX_ID + 1];
    char destino[MAX_ENTIDADE + 1];
    char tipo_mensagem[32];
    char prioridade[16];
    char origem_padrao[MAX_ENTIDADE + 1];
    char nome[128];
    char catalogo[PATH_MAX];
    bool ativo;
    bool idempotencia_obrigatoria;
    bool correlacao_obrigatoria;
} EntradaCatalogo;

typedef struct {
    char api_id[MAX_ID + 1];
    char dados_arg[PATH_MAX * 2];
    char dados_arquivo[PATH_MAX * 2];
    char origem[MAX_ENTIDADE + 1];
    char processo_id[MAX_ID + 1];
    char mensagem_id[MAX_ID + 1];
    char resposta_a[MAX_ID + 1];
    char idempotencia[256];
    char prioridade[16];
    char tipo_mensagem[32];
    int dry_run;
} Opcoes;

static void uso(void)
{
    fputs(
        "Uso:\n"
        "  pp --api=<idmensagem> --dados=<json|arquivo> [opcoes]\n"
        "  pp --api --ajuda:<grupo>\n"
        "\n"
        "Descrição:\n"
        "  Publica uma chamada da API declarativa do SISC no espaco nativo. Consulta\n"
        "  web-api/catalogo-mensagens.json, resolve o destino e grava uma mensagem\n"
        "  válida em espaco/entrada para a escuta.c entregar.\n"
        "\n"
        "Grupos de ajuda: uso, dados, protocolo, catalogo, exemplos.\n"
        "\n"
        "1) Uso e ajuda [uso ajuda]\n"
        "  --ajuda, --help, -h            Exibe esta ajuda completa\n"
        "  --ajuda:<grupo>                Exibe apenas um grupo. Ex: pp --api --ajuda:dados\n"
        "  --ajuda=<grupo>                Forma equivalente\n"
        "\n"
        "2) Dados de entrada [dados entrada]\n"
        "  --dados='<json>'               JSON inline para payload.dados\n"
        "  --dados=arquivo.json           Atalho para ler dados de um arquivo JSON\n"
        "  --dados-arquivo=arquivo.json   Arquivo JSON explícito\n"
        "  Se o JSON tiver {'idmensagem':'001', 'dados': ...}, dados vira payload.dados.\n"
        "  Exemplo com todas as flags do grupo (duas formas, --dados e --dados-arquivo são mutuamente exclusivos):\n"
        "    pp --api=001 --dados='{cpf:123}'\n"
        "    pp --api=001 --dados-arquivo=cliente.json\n"
        "\n"
        "3) Protocolo da mensagem [protocolo mensagem]\n"
        "  --origem=<entidade>            Origem SISC (padrão: SISC_API_ORIGEM ou sistema__api)\n"
        "  --processo-id=<id>             Define processoId; se ausente, gera automaticamente\n"
        "  --mensagem-id=<id>             Define mensagemId; se ausente, gera automaticamente\n"
        "  --resposta-a=<id>              Define _protocolo.respostaA\n"
        "  --idempotencia=<chave>         Define _protocolo.idempotencia.chave\n"
        "  --prioridade=<baixa|normal|alta|critica>\n"
        "  --tipo=<solicitacao|resposta|evento|erro|comando|consulta>\n"
        "  --dry-run                      Imprime a mensagem gerada sem gravar no espaco\n"
        "  Exemplo com todas as flags do grupo:\n"
        "    pp --api=001 --dados='{cpf:123}' --origem=agente__x --processo-id=proc-1 --mensagem-id=msg-1 --resposta-a=msg-0 --idempotencia=chave12345 --prioridade=alta --tipo=solicitacao --dry-run\n"
        "\n"
        "4) Catálogo e regras [catalogo regras]\n"
        "  web-api/catalogo-mensagens.json deve mapear idmensagem para destino SISC.\n"
        "  Mensagens publicas, comandos e prioridade critica devem declarar idempotenciaObrigatoria=true e correlacaoObrigatoria=true.\n"
        "  Destinos aceitos: agente__id, conector__id ou sistema__id.\n"
        "  pp --api nunca executa arquivo, URL, agente ou conector diretamente.\n"
        "\n"
        "5) Exemplos [exemplos]\n"
        "  pp --api=001 --dados='{cpf:12345678900}' --origem=agente__solicitante\n"
        "  pp --api=001 --dados-arquivo=cliente-dados.json --origem=conector__web\n"
        "  pp --api=001 --dados='{ok:true}' --dry-run\n",
        stdout);
}

static void fmt_checked(char *out, size_t outsz, const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    int n = vsnprintf(out, outsz, fmt, ap);
    va_end(ap);
    if (n < 0 || (size_t)n >= outsz) {
        fprintf(stderr, "api: texto/caminho excede limite interno.\n");
        exit(70);
    }
}

static void path_join(char *out, size_t outsz, const char *a, const char *b) {
    fmt_checked(out, outsz, "%s/%s", a, b);
}

static bool mkdir_p(const char *path) {
    char tmp[PATH_MAX];
    size_t len = strlen(path);
    if (len == 0 || len >= sizeof(tmp)) return false;
    memcpy(tmp, path, len + 1);
    if (tmp[len - 1] == '/') tmp[len - 1] = 0;
    for (char *p = tmp + 1; *p; p++) {
        if (*p == '/') {
            *p = 0;
            if (mkdir(tmp, 0775) != 0 && errno != EEXIST) return false;
            *p = '/';
        }
    }
    return mkdir(tmp, 0775) == 0 || errno == EEXIST;
}

static bool is_regular_file(const char *path) {
    struct stat st;
    return path && *path && stat(path, &st) == 0 && S_ISREG(st.st_mode);
}

static char *read_file(const char *path, size_t *len_out, char *erro, size_t errosz) {
    struct stat st;
    if (stat(path, &st) != 0) { snprintf(erro, errosz, "stat falhou: %s", strerror(errno)); return NULL; }
    if (!S_ISREG(st.st_mode)) { snprintf(erro, errosz, "nao e arquivo regular"); return NULL; }
    if (st.st_size < 0 || st.st_size > MAX_ARQ_BYTES) { snprintf(erro, errosz, "arquivo excede limite de bytes"); return NULL; }
    FILE *f = fopen(path, "rb");
    if (!f) { snprintf(erro, errosz, "fopen falhou: %s", strerror(errno)); return NULL; }
    char *buf = malloc((size_t)st.st_size + 1);
    if (!buf) { fclose(f); snprintf(erro, errosz, "memoria insuficiente"); return NULL; }
    size_t n = fread(buf, 1, (size_t)st.st_size, f);
    fclose(f);
    if (n != (size_t)st.st_size) { free(buf); snprintf(erro, errosz, "leitura incompleta"); return NULL; }
    buf[n] = 0;
    *len_out = n;
    erro[0] = 0;
    return buf;
}

static Json *json_new(JType t) {
    Json *j = calloc(1, sizeof(*j));
    if (!j) exit(70);
    j->type = t;
    return j;
}

static void json_free(Json *j) {
    if (!j) return;
    if (j->type == J_STRING) free(j->u.s);
    else if (j->type == J_ARRAY) {
        for (size_t i = 0; i < j->u.array.len; i++) json_free(j->u.array.items[i]);
        free(j->u.array.items);
    } else if (j->type == J_OBJECT) {
        for (size_t i = 0; i < j->u.object.len; i++) {
            free(j->u.object.pairs[i].key);
            json_free(j->u.object.pairs[i].value);
        }
        free(j->u.object.pairs);
    }
    free(j);
}

static void perr(Parser *p, const char *msg) { if (!p->erro[0]) snprintf(p->erro, sizeof p->erro, "%s", msg); }
static void pws(Parser *p) { while (p->p < p->end && isspace((unsigned char)*p->p)) p->p++; }
static bool plit(Parser *p, const char *lit) { size_t n = strlen(lit); if ((size_t)(p->end - p->p) >= n && strncmp(p->p, lit, n) == 0) { p->p += n; return true; } return false; }

static int hexv(char c) {
    if (c >= '0' && c <= '9') return c - '0';
    if (c >= 'a' && c <= 'f') return 10 + c - 'a';
    if (c >= 'A' && c <= 'F') return 10 + c - 'A';
    return -1;
}

static char *pstring(Parser *p) {
    if (p->p >= p->end || *p->p != '"') { perr(p, "string esperada"); return NULL; }
    p->p++;
    size_t cap = 64, len = 0;
    char *buf = calloc(cap, 1);
    if (!buf) exit(70);
    while (p->p < p->end) {
        unsigned char c = (unsigned char)*p->p++;
        if (c == '"') { buf[len] = 0; return buf; }
        if (c < 0x20) { free(buf); perr(p, "controle invalido em string"); return NULL; }
        if (c == '\\') {
            if (p->p >= p->end) { free(buf); perr(p, "escape incompleto"); return NULL; }
            char e = *p->p++;
            switch (e) {
                case '"': c = '"'; break;
                case '\\': c = '\\'; break;
                case '/': c = '/'; break;
                case 'b': c = '\b'; break;
                case 'f': c = '\f'; break;
                case 'n': c = '\n'; break;
                case 'r': c = '\r'; break;
                case 't': c = '\t'; break;
                case 'u':
                    if (p->end - p->p < 4 || hexv(p->p[0]) < 0 || hexv(p->p[1]) < 0 || hexv(p->p[2]) < 0 || hexv(p->p[3]) < 0) { free(buf); perr(p, "unicode escape invalido"); return NULL; }
                    p->p += 4; c = '?'; break;
                default: free(buf); perr(p, "escape invalido"); return NULL;
            }
        }
        if (len + 2 > cap) { cap *= 2; char *nb = realloc(buf, cap); if (!nb) exit(70); buf = nb; }
        buf[len++] = (char)c;
    }
    free(buf); perr(p, "string nao terminada"); return NULL;
}

static Json *pvalue(Parser *p);

static Json *parray(Parser *p) {
    p->p++;
    Json *a = json_new(J_ARRAY);
    pws(p);
    if (p->p < p->end && *p->p == ']') { p->p++; return a; }
    while (p->p < p->end) {
        Json *v = pvalue(p);
        if (!v) { json_free(a); return NULL; }
        Json **ni = realloc(a->u.array.items, sizeof(Json *) * (a->u.array.len + 1));
        if (!ni) exit(70);
        a->u.array.items = ni;
        a->u.array.items[a->u.array.len++] = v;
        pws(p);
        if (p->p < p->end && *p->p == ',') { p->p++; pws(p); continue; }
        if (p->p < p->end && *p->p == ']') { p->p++; return a; }
        json_free(a); perr(p, "',' ou ']' esperado"); return NULL;
    }
    json_free(a); perr(p, "array nao terminado"); return NULL;
}

static Json *pobject(Parser *p) {
    p->p++;
    Json *o = json_new(J_OBJECT);
    pws(p);
    if (p->p < p->end && *p->p == '}') { p->p++; return o; }
    while (p->p < p->end) {
        pws(p);
        char *k = pstring(p);
        if (!k) { json_free(o); return NULL; }
        pws(p);
        if (p->p >= p->end || *p->p != ':') { free(k); json_free(o); perr(p, "':' esperado"); return NULL; }
        p->p++;
        Json *v = pvalue(p);
        if (!v) { free(k); json_free(o); return NULL; }
        Pair *np = realloc(o->u.object.pairs, sizeof(Pair) * (o->u.object.len + 1));
        if (!np) exit(70);
        o->u.object.pairs = np;
        o->u.object.pairs[o->u.object.len].key = k;
        o->u.object.pairs[o->u.object.len].value = v;
        o->u.object.len++;
        pws(p);
        if (p->p < p->end && *p->p == ',') { p->p++; pws(p); continue; }
        if (p->p < p->end && *p->p == '}') { p->p++; return o; }
        json_free(o); perr(p, "',' ou '}' esperado"); return NULL;
    }
    json_free(o); perr(p, "objeto nao terminado"); return NULL;
}

static Json *pnumber(Parser *p) {
    const char *start = p->p;
    if (*p->p == '-') p->p++;
    if (p->p >= p->end) { perr(p, "numero invalido"); return NULL; }
    if (*p->p == '0') p->p++;
    else if (isdigit((unsigned char)*p->p)) while (p->p < p->end && isdigit((unsigned char)*p->p)) p->p++;
    else { perr(p, "numero invalido"); return NULL; }
    if (p->p < p->end && *p->p == '.') {
        p->p++;
        if (p->p >= p->end || !isdigit((unsigned char)*p->p)) { perr(p, "numero invalido"); return NULL; }
        while (p->p < p->end && isdigit((unsigned char)*p->p)) p->p++;
    }
    if (p->p < p->end && (*p->p == 'e' || *p->p == 'E')) {
        p->p++;
        if (p->p < p->end && (*p->p == '+' || *p->p == '-')) p->p++;
        if (p->p >= p->end || !isdigit((unsigned char)*p->p)) { perr(p, "numero invalido"); return NULL; }
        while (p->p < p->end && isdigit((unsigned char)*p->p)) p->p++;
    }
    char tmp[128]; size_t n = (size_t)(p->p - start); if (n >= sizeof tmp) { perr(p, "numero longo demais"); return NULL; }
    memcpy(tmp, start, n); tmp[n] = 0;
    Json *j = json_new(J_NUMBER); j->u.n = strtod(tmp, NULL); return j;
}

static Json *pvalue(Parser *p) {
    pws(p);
    if (p->p >= p->end) { perr(p, "valor esperado"); return NULL; }
    if (*p->p == '{') return pobject(p);
    if (*p->p == '[') return parray(p);
    if (*p->p == '"') { Json *j = json_new(J_STRING); j->u.s = pstring(p); if (!j->u.s) { free(j); return NULL; } return j; }
    if (*p->p == 't' && plit(p, "true")) { Json *j = json_new(J_BOOL); j->u.b = true; return j; }
    if (*p->p == 'f' && plit(p, "false")) { Json *j = json_new(J_BOOL); j->u.b = false; return j; }
    if (*p->p == 'n' && plit(p, "null")) return json_new(J_NULL);
    if (*p->p == '-' || isdigit((unsigned char)*p->p)) return pnumber(p);
    perr(p, "valor JSON invalido"); return NULL;
}

static Json *json_parse_text(const char *text, size_t len, char *erro, size_t errosz) {
    Parser p = { .p = text, .end = text + len, .erro = {0} };
    Json *j = pvalue(&p);
    if (!j) { snprintf(erro, errosz, "%s", p.erro[0] ? p.erro : "JSON invalido"); return NULL; }
    pws(&p);
    if (p.p != p.end) { json_free(j); snprintf(erro, errosz, "conteudo extra apos JSON"); return NULL; }
    erro[0] = 0;
    return j;
}

static Json *load_json(const char *path, char *erro, size_t errosz) {
    size_t len = 0;
    char *txt = read_file(path, &len, erro, errosz);
    if (!txt) return NULL;
    Json *j = json_parse_text(txt, len, erro, errosz);
    free(txt);
    return j;
}

static Json *obj_get(Json *o, const char *key) {
    if (!o || o->type != J_OBJECT) return NULL;
    for (size_t i = 0; i < o->u.object.len; i++) if (strcmp(o->u.object.pairs[i].key, key) == 0) return o->u.object.pairs[i].value;
    return NULL;
}

static const char *obj_str(Json *o, const char *key) { Json *v = obj_get(o, key); return (v && v->type == J_STRING) ? v->u.s : NULL; }
static bool obj_bool(Json *o, const char *key, bool def) { Json *v = obj_get(o, key); return (v && v->type == J_BOOL) ? v->u.b : def; }

static bool ends_with_str(const char *s, const char *suffix) {
    size_t a = strlen(s ? s : ""), b = strlen(suffix ? suffix : "");
    return a >= b && strcmp(s + a - b, suffix) == 0;
}

static bool rel_formato_conector(Json *manifesto, const char *id, char *out, size_t outsz, char *legado, size_t legadosz) {
    const char *decl = obj_str(manifesto, "formatoConector");
    if (legado && legadosz) legado[0] = 0;
    if (!decl || !*decl) {
        fmt_checked(out, outsz, "conectores/%s/formatos/formato-%s.json", id, id);
        if (legado && legadosz) fmt_checked(legado, legadosz, "conectores/formatos/formato-%s.json", id);
        return true;
    }
    while (!strncmp(decl, "./", 2)) decl += 2;
    if (!*decl || decl[0] == '/' || strstr(decl, "..") || !ends_with_str(decl, ".json")) return false;
    if (strchr(decl, '/')) {
        if (strncmp(decl, "conectores/", 11) != 0) return false;
        fmt_checked(out, outsz, "%s", decl);
        return true;
    }
    fmt_checked(out, outsz, "conectores/%s/formatos/%s", id, decl);
    if (legado && legadosz) fmt_checked(legado, legadosz, "conectores/formatos/%s", decl);
    return true;
}

static bool validar_formato_conector_origem(const char *base, const char *manifesto_path, const char *id, bool biblioteca, char *erro, size_t errosz) {
    Json *manifesto = load_json(manifesto_path, erro, errosz);
    if (!manifesto) return false;
    char rel[PATH_MAX], legado[PATH_MAX], path[PATH_MAX];
    bool ok = rel_formato_conector(manifesto, id, rel, sizeof rel, legado, sizeof legado);
    json_free(manifesto);
    if (!ok) { snprintf(erro, errosz, "formatoConector invalido"); return false; }
    if (biblioteca) return true; /* pacotes da biblioteca são autocontidos; ao importar, o formato vai declarado no pacote. */
    path_join(path, sizeof path, base, rel);
    if (is_regular_file(path)) return true;
    if (legado[0]) {
        path_join(path, sizeof path, base, legado);
        if (is_regular_file(path)) return true;
    }
    snprintf(erro, errosz, "formato do conector nao encontrado no diretorio do conector");
    return false;
}

static void json_escape(FILE *f, const char *s) {
    fputc('"', f);
    for (const unsigned char *p = (const unsigned char *)(s ? s : ""); *p; p++) {
        switch (*p) {
            case '"': fputs("\\\"", f); break;
            case '\\': fputs("\\\\", f); break;
            case '\b': fputs("\\b", f); break;
            case '\f': fputs("\\f", f); break;
            case '\n': fputs("\\n", f); break;
            case '\r': fputs("\\r", f); break;
            case '\t': fputs("\\t", f); break;
            default: if (*p < 0x20) fprintf(f, "\\u%04x", *p); else fputc(*p, f);
        }
    }
    fputc('"', f);
}

static void json_write(FILE *f, const Json *j) {
    if (!j) { fputs("null", f); return; }
    switch (j->type) {
        case J_NULL: fputs("null", f); break;
        case J_BOOL: fputs(j->u.b ? "true" : "false", f); break;
        case J_NUMBER: {
            double n = j->u.n;
            long long ll = (long long)n;
            if ((double)ll == n) fprintf(f, "%lld", ll);
            else fprintf(f, "%.17g", n);
            break;
        }
        case J_STRING: json_escape(f, j->u.s); break;
        case J_ARRAY:
            fputc('[', f);
            for (size_t i = 0; i < j->u.array.len; i++) { if (i) fputc(',', f); json_write(f, j->u.array.items[i]); }
            fputc(']', f);
            break;
        case J_OBJECT:
            fputc('{', f);
            for (size_t i = 0; i < j->u.object.len; i++) {
                if (i) fputc(',', f);
                json_escape(f, j->u.object.pairs[i].key);
                fputc(':', f);
                json_write(f, j->u.object.pairs[i].value);
            }
            fputc('}', f);
            break;
    }
}

static bool valid_id(const char *s) {
    if (!s || !*s || strlen(s) > MAX_ID || !isalnum((unsigned char)s[0])) return false;
    for (size_t i = 1; s[i]; i++) if (!(isalnum((unsigned char)s[i]) || s[i] == '.' || s[i] == '_' || s[i] == '-')) return false;
    return true;
}

static bool starts_with(const char *s, const char *p) { return s && strncmp(s, p, strlen(p)) == 0; }

static bool valid_entidade(const char *s) {
    const char *id = NULL;
    if (starts_with(s, "agente__")) id = s + 8;
    else if (starts_with(s, "conector__")) id = s + 10;
    else if (starts_with(s, "sistema__")) id = s + 9;
    else return false;
    return valid_id(id);
}

static bool valid_tipo_msg(const char *s) {
    return s && (!strcmp(s, "solicitacao") || !strcmp(s, "resposta") || !strcmp(s, "evento") || !strcmp(s, "erro") || !strcmp(s, "comando") || !strcmp(s, "consulta"));
}

static bool valid_prioridade(const char *s) {
    return s && (!strcmp(s, "baixa") || !strcmp(s, "normal") || !strcmp(s, "alta") || !strcmp(s, "critica"));
}

static const char *first_str(Json *o, const char **keys, size_t nkeys) {
    if (!o || o->type != J_OBJECT) return NULL;
    for (size_t i = 0; i < nkeys; i++) {
        const char *s = obj_str(o, keys[i]);
        if (s && *s) return s;
    }
    return NULL;
}

static bool entry_id_matches(Json *entry, const char *id) {
    const char *keys[] = {"idPublico", "idCliente", "codigoPublico", "codigoCliente", "idmensagem", "idMensagem", "id", "codigo", "codigoMensagem"};
    const char *s = first_str(entry, keys, sizeof(keys)/sizeof(keys[0]));
    return s && strcmp(s, id) == 0;
}

static Json *find_entry_in_array(Json *arr, const char *id) {
    if (!arr || arr->type != J_ARRAY) return NULL;
    for (size_t i = 0; i < arr->u.array.len; i++) {
        Json *e = arr->u.array.items[i];
        if (e && e->type == J_OBJECT && entry_id_matches(e, id)) return e;
    }
    return NULL;
}

static bool entry_publica_api(Json *entry) {
    if (!entry || entry->type != J_OBJECT) return false;
    if (obj_bool(entry, "ativo", true) == false) return false;
    const char *bool_keys[] = {"publica", "publico", "expostoApi", "apiPublica", "disponivelCliente"};
    for (size_t i = 0; i < sizeof(bool_keys)/sizeof(bool_keys[0]); i++) {
        if (obj_bool(entry, bool_keys[i], false)) return true;
    }
    const char *id_keys[] = {"idPublico", "idCliente", "codigoPublico", "codigoCliente"};
    return first_str(entry, id_keys, sizeof(id_keys)/sizeof(id_keys[0])) != NULL;
}

static Json *find_catalog_entry(Json *root, const char *id) {
    if (!root) return NULL;
    if (root->type == J_ARRAY) return find_entry_in_array(root, id);
    if (root->type != J_OBJECT) return NULL;
    Json *msgs = obj_get(root, "mensagens");
    Json *e = find_entry_in_array(msgs, id);
    if (e) return e;
    Json *apis = obj_get(root, "apis");
    e = find_entry_in_array(apis, id);
    if (e) return e;
    Json *rotas = obj_get(root, "rotas");
    e = find_entry_in_array(rotas, id);
    if (e) return e;
    if (entry_id_matches(root, id)) return root;
    e = obj_get(root, id);
    if (e && e->type == J_OBJECT) return e;
    return NULL;
}

static int carregar_entrada(const char *base, const char *id, EntradaCatalogo *out) {
    memset(out, 0, sizeof(*out));
    snprintf(out->id, sizeof(out->id), "%s", id);
    snprintf(out->tipo_mensagem, sizeof(out->tipo_mensagem), "consulta");
    snprintf(out->prioridade, sizeof(out->prioridade), "normal");
    out->ativo = true;

    char catalogo[PATH_MAX], erro[256];
    fmt_checked(catalogo, sizeof catalogo, "%s/web-api/catalogo-mensagens.json", base);
    Json *root = NULL;
    Json *entry = NULL;
    if (is_regular_file(catalogo)) {
        root = load_json(catalogo, erro, sizeof erro);
        if (!root) { fprintf(stderr, "api: catalogo invalido %s: %s\n", catalogo, erro); return 1; }
        entry = find_catalog_entry(root, id);
        snprintf(out->catalogo, sizeof(out->catalogo), "web-api/catalogo-mensagens.json");
    }

    if (!entry) {
        json_free(root);
        fprintf(stderr, "api: idmensagem '%s' nao encontrado em web-api/catalogo-mensagens.json\n", id);
        return 1;
    }

    out->ativo = obj_bool(entry, "ativo", true);
    if (!out->ativo) { json_free(root); fprintf(stderr, "api: idmensagem '%s' esta inativo no catalogo.\n", id); return 1; }

    const char *id_catalogo_keys[] = {"idmensagem", "idMensagem", "id", "codigo", "codigoMensagem"};
    const char *id_catalogo = first_str(entry, id_catalogo_keys, sizeof(id_catalogo_keys)/sizeof(id_catalogo_keys[0]));
    if (id_catalogo && *id_catalogo) {
        if (!valid_id(id_catalogo)) { json_free(root); fprintf(stderr, "api: idmensagem invalido no catalogo: %s\n", id_catalogo); return 1; }
        snprintf(out->id, sizeof(out->id), "%s", id_catalogo);
    }

    const char *dest_keys[] = {"destino", "destinatario", "atorDestino", "destinoSisc"};
    const char *dest = first_str(entry, dest_keys, sizeof(dest_keys)/sizeof(dest_keys[0]));
    if (!dest || !*dest) {
        json_free(root);
        fprintf(stderr, "api: idmensagem '%s' sem destino no catalogo.\n", id);
        return 1;
    }
    if (!valid_entidade(dest)) {
        fprintf(stderr, "api: destino invalido para idmensagem '%s': %s\n", id, dest);
        fprintf(stderr, "api: acesso direto nao e permitido. Use entidade SISC: agente__id, conector__id ou sistema__id.\n");
        json_free(root);
        return 1;
    }
    snprintf(out->destino, sizeof(out->destino), "%s", dest);

    const char *tipo_keys[] = {"tipoMensagem", "tipoMensagemSisc", "tipoSisc", "tipo"};
    const char *tipo = first_str(entry, tipo_keys, sizeof(tipo_keys)/sizeof(tipo_keys[0]));
    if (tipo && valid_tipo_msg(tipo)) snprintf(out->tipo_mensagem, sizeof(out->tipo_mensagem), "%s", tipo);

    const char *prio = obj_str(entry, "prioridade");
    if (prio && valid_prioridade(prio)) snprintf(out->prioridade, sizeof(out->prioridade), "%s", prio);

    const char *orig_keys[] = {"origemPadrao", "origem", "origemSisc"};
    const char *orig = first_str(entry, orig_keys, sizeof(orig_keys)/sizeof(orig_keys[0]));
    if (orig && valid_entidade(orig)) snprintf(out->origem_padrao, sizeof(out->origem_padrao), "%s", orig);

    const char *nome = obj_str(entry, "nome");
    if (nome) snprintf(out->nome, sizeof(out->nome), "%s", nome);
    out->idempotencia_obrigatoria = obj_bool(entry, "idempotenciaObrigatoria", false);
    out->correlacao_obrigatoria = obj_bool(entry, "correlacaoObrigatoria", false);
    bool governanca_obrigatoria = strcmp(out->tipo_mensagem, "comando") == 0 || strcmp(out->prioridade, "critica") == 0 || entry_publica_api(entry);
    if (governanca_obrigatoria && (!out->idempotencia_obrigatoria || !out->correlacao_obrigatoria)) {
        fprintf(stderr, "api: catalogo de '%s' deve declarar idempotenciaObrigatoria=true e correlacaoObrigatoria=true para mensagens de negocio, comandos ou prioridade critica.\n", id);
        json_free(root);
        return 1;
    }

    json_free(root);
    return 0;
}

static void entidade_id(const char *entidade, char *tipo, size_t tiposz, char *id, size_t idsz) {
    const char *sep = strstr(entidade, "__");
    if (!sep) { tipo[0] = id[0] = 0; return; }
    snprintf(tipo, tiposz, "%.*s", (int)(sep - entidade), entidade);
    snprintf(id, idsz, "%s", sep + 2);
}

static int validar_destino_registrado(const char *base, const char *destino, const Json *dados) {
    (void)dados;
    char tipo[32], id[160];
    entidade_id(destino, tipo, sizeof tipo, id, sizeof id);
    if (strcmp(tipo, "sistema") == 0) return 0;
    char path[PATH_MAX];
    bool conector_biblioteca = false;
    if (strcmp(tipo, "agente") == 0) {
        fmt_checked(path, sizeof path, "%s/agentes/%s.json", base, id);
        /* fallback agentes: biblioteca central */
        if (!is_regular_file(path)) {
            char raiz_sisc[PATH_MAX];
            snprintf(raiz_sisc, sizeof(raiz_sisc), "%s", base);
            char *slash = strrchr(raiz_sisc, '/');
            if (slash && slash != raiz_sisc) {
                *slash = '\0';
                fmt_checked(path, sizeof path, "%s/core/biblioteca-agentes-fixos/%s.json", raiz_sisc, id);
            }
        }
    } else if (strcmp(tipo, "conector") == 0) {
        fmt_checked(path, sizeof path, "%s/conectores/%s/%s.json", base, id, id);
        if (!is_regular_file(path)) {
            fmt_checked(path, sizeof path, "%s/conectores/%s.json", base, id); /* compatibilidade com layout legado */
        }
        /* fallback conectores: biblioteca central */
        if (!is_regular_file(path)) {
            char raiz_sisc[PATH_MAX];
            snprintf(raiz_sisc, sizeof(raiz_sisc), "%s", base);
            char *slash = strrchr(raiz_sisc, '/');
            if (slash && slash != raiz_sisc) {
                *slash = '\0';
                fmt_checked(path, sizeof path, "%s/core/biblioteca-conectores/%s.json", raiz_sisc, id);
                conector_biblioteca = true;
            }
        }
    } else return 1;
    if (!is_regular_file(path)) {
        fprintf(stderr, "api: destino %s nao possui manifesto local em %s.\n", destino, path);
        fprintf(stderr, "api: a mensagem nao sera publicada para evitar quarentena; registre/sincronize o ator antes.\n");
        return 1;
    }
    if (strcmp(tipo, "conector") == 0) {
        char erro[256];
        if (!validar_formato_conector_origem(base, path, id, conector_biblioteca, erro, sizeof erro)) {
            fprintf(stderr, "api: %s.\n", erro);
            fprintf(stderr, "api: conector em %s deve usar formato local no diretorio do conector ou pacote autocontido importado.\n",
                    conector_biblioteca ? "biblioteca" : "sistema atual");
            return 1;
        }
    }
    return 0;
}

static void now_iso(char *out, size_t outsz) {
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    struct tm tm;
    gmtime_r(&ts.tv_sec, &tm);
    snprintf(out, outsz, "%04d-%02d-%02dT%02d:%02d:%02d.%03ldZ",
             tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
             tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec / 1000000L);
}

static void gerar_id(const char *prefixo, const char *api_id, char *out, size_t outsz) {
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    snprintf(out, outsz, "%s-%s-%ld-%09ld-%d", prefixo, api_id, (long)ts.tv_sec, ts.tv_nsec, getpid());
}

static uint64_t fnv1a64_bytes(const char *s) {
    uint64_t h = 1469598103934665603ULL;
    for (const unsigned char *p = (const unsigned char *)(s ? s : ""); *p; p++) { h ^= *p; h *= 1099511628211ULL; }
    return h;
}

static void sanitize_file_id(const char *in, char *out, size_t outsz) {
    size_t j = 0;
    for (size_t i = 0; in && in[i] && j + 1 < outsz; i++) {
        unsigned char c = (unsigned char)in[i];
        out[j++] = (isalnum(c) || c == '.' || c == '_' || c == '-') ? (char)c : '-';
    }
    if (j == 0 && outsz > 1) out[j++] = 'x';
    out[j] = 0;
}

static int parece_json_inline(const char *s) {
    if (!s) return 0;
    while (*s && isspace((unsigned char)*s)) s++;
    return *s == '{' || *s == '[' || *s == '"' || *s == '-' || isdigit((unsigned char)*s) || *s == 't' || *s == 'f' || *s == 'n';
}

static char *carregar_dados_texto(const Opcoes *op, char *erro, size_t errosz) {
    size_t len = 0;
    if (op->dados_arquivo[0]) return read_file(op->dados_arquivo, &len, erro, errosz);
    if (!op->dados_arg[0]) return strdup("{}");
    if (strncmp(op->dados_arg, "@", 1) == 0) return read_file(op->dados_arg + 1, &len, erro, errosz);
    if (!parece_json_inline(op->dados_arg) && is_regular_file(op->dados_arg)) return read_file(op->dados_arg, &len, erro, errosz);
    char *out = strdup(op->dados_arg);
    if (!out) snprintf(erro, errosz, "memoria insuficiente");
    else erro[0] = 0;
    return out;
}

static const Json *selecionar_dados_payload(const Json *root, const char *api_id) {
    if (!root || root->type != J_OBJECT) return root;
    Json *idv = obj_get((Json *)root, "idmensagem");
    if (!idv) idv = obj_get((Json *)root, "idMensagem");
    if (idv && idv->type == J_STRING && strcmp(idv->u.s, api_id) != 0) {
        fprintf(stderr, "api: dados informam idmensagem '%s', mas comando usou '%s'.\n", idv->u.s, api_id);
        return NULL;
    }
    Json *dados = obj_get((Json *)root, "dados");
    return dados ? dados : root;
}

static void escrever_mensagem(FILE *f, const EntradaCatalogo *cat, const Opcoes *op,
                              const Json *dados, const char *processo_id, const char *mensagem_id,
                              const char *origem, const char *agora, const char *idem) {
    fputs("{\n", f);
    fputs("  \"_sistema\": {\n", f);
    fputs("    \"id\": ", f); json_escape(f, mensagem_id); fputs(",\n", f);
    fputs("    \"criadoEm\": ", f); json_escape(f, agora); fputs(",\n", f);
    fputs("    \"criadoPor\": ", f); json_escape(f, origem); fputs(",\n", f);
    fputs("    \"transporte\": \"pp --api\"\n", f);
    fputs("  },\n", f);
    fputs("  \"_protocolo\": {\n", f);
    fputs("    \"nome\": \"" PROTO_NOME "\",\n", f);
    fprintf(f, "    \"versao\": %d,\n", PROTO_VERSAO);
    fputs("    \"processoId\": ", f); json_escape(f, processo_id); fputs(",\n", f);
    fputs("    \"mensagemId\": ", f); json_escape(f, mensagem_id); fputs(",\n", f);
    if (op->resposta_a[0]) { fputs("    \"respostaA\": ", f); json_escape(f, op->resposta_a); fputs(",\n", f); }
    fputs("    \"origem\": ", f); json_escape(f, origem); fputs(",\n", f);
    fputs("    \"destino\": ", f); json_escape(f, cat->destino); fputs(",\n", f);
    fputs("    \"tipo\": ", f); json_escape(f, op->tipo_mensagem[0] ? op->tipo_mensagem : cat->tipo_mensagem); fputs(",\n", f);
    fputs("    \"prioridade\": ", f); json_escape(f, op->prioridade[0] ? op->prioridade : cat->prioridade); fputs(",\n", f);
    fputs("    \"criadoEm\": ", f); json_escape(f, agora);
    if (idem && *idem) {
        fputs(",\n    \"idempotencia\": {\n", f);
        fputs("      \"chave\": ", f); json_escape(f, idem); fputs(",\n", f);
        fputs("      \"escopo\": ", f); json_escape(f, cat->id); fputs("\n", f);
        fputs("    }", f);
    }
    fputs("\n  },\n", f);
    fputs("  \"payload\": {\n", f);
    fputs("    \"idmensagem\": ", f); json_escape(f, cat->id); fputs(",\n", f);
    fputs("    \"dados\": ", f); json_write(f, dados); fputs(",\n", f);
    fputs("    \"_api\": {\n", f);
    fputs("      \"catalogo\": ", f); json_escape(f, cat->catalogo); fputs(",\n", f);
    if (cat->nome[0]) { fputs("      \"nome\": ", f); json_escape(f, cat->nome); fputs(",\n", f); }
    fputs("      \"destinoResolvido\": ", f); json_escape(f, cat->destino); fputs(",\n", f);
    fprintf(f, "      \"idempotenciaObrigatoria\": %s,\n", cat->idempotencia_obrigatoria ? "true" : "false");
    fprintf(f, "      \"correlacaoObrigatoria\": %s,\n", cat->correlacao_obrigatoria ? "true" : "false");
    fputs("      \"formatoEscuta\": \"formatos-escuta/protocolo-mensagem-siscore.v1.json\",\n", f);
    fputs("      \"regraComunicacao\": \"somente-espaco-sisc-sem-acesso-direto\"\n", f);
    fputs("    }\n", f);
    fputs("  }\n", f);
    fputs("}\n", f);
}

static int publicar_marker_exclusivo(const char *path, const char *tmp, const char *conteudo) {
    if (is_regular_file(path)) return 2;
    FILE *f = fopen(tmp, "wb");
    if (!f) return 1;
    fputs(conteudo, f);
    fflush(f);
    fsync(fileno(f));
    if (fclose(f) != 0) { unlink(tmp); return 1; }
    if (link(tmp, path) != 0) {
        int e = errno;
        unlink(tmp);
        if (e == EEXIST || is_regular_file(path)) return 2;
        return 1;
    }
    unlink(tmp);
    return 0;
}

static int registrar_idempotencia_api(const char *espaco, const EntradaCatalogo *cat,
                                      const char *processo_id, const char *mensagem_id,
                                      const char *origem, const char *agora, const char *idem) {
    if (!idem || !*idem) return 0;
    char dir[PATH_MAX], path[PATH_MAX], tmp[PATH_MAX], comb[1200], conteudo[1400];
    path_join(dir, sizeof dir, espaco, "idempotencia");
    if (!mkdir_p(dir)) return 1;
    snprintf(comb, sizeof comb, "%s|%s|%s|%s", origem, cat->destino, cat->id, idem);
    fmt_checked(path, sizeof path, "%s/%016llx.idx", dir, (unsigned long long)fnv1a64_bytes(comb));
    fmt_checked(tmp, sizeof tmp, "%s.tmp.%d", path, getpid());
    snprintf(conteudo, sizeof conteudo,
             "publicadoPor=pp --api\n"
             "idmensagem=%s\n"
             "idempotenciaEscopo=%s\n"
             "processoId=%s\n"
             "mensagemId=%s\n"
             "origem=%s\n"
             "destino=%s\n"
             "criadoEm=%s\n",
             cat->id, cat->id, processo_id, mensagem_id, origem, cat->destino, agora);
    return publicar_marker_exclusivo(path, tmp, conteudo);
}

static int registrar_publicacao_api(const char *espaco, const EntradaCatalogo *cat,
                                    const char *processo_id, const char *mensagem_id,
                                    const char *origem, const char *agora) {
    char dir[PATH_MAX], safe_mid[256], path[PATH_MAX], tmp[PATH_MAX], conteudo[1400];
    path_join(dir, sizeof dir, espaco, "api-publicadas");
    if (!mkdir_p(dir)) return 1;
    sanitize_file_id(mensagem_id, safe_mid, sizeof safe_mid);
    fmt_checked(path, sizeof path, "%s/%s.idx", dir, safe_mid);
    fmt_checked(tmp, sizeof tmp, "%s.tmp.%d", path, getpid());
    snprintf(conteudo, sizeof conteudo,
             "publicadoPor=pp --api\n"
             "regraComunicacao=somente-espaco-sisc-sem-acesso-direto\n"
             "formatoEscuta=formatos-escuta/protocolo-mensagem-siscore.v1.json\n"
             "idmensagem=%s\n"
             "correlacaoObrigatoria=%s\n"
             "processoId=%s\n"
             "mensagemId=%s\n"
             "origem=%s\n"
             "destino=%s\n"
             "criadoEm=%s\n",
             cat->id, cat->correlacao_obrigatoria ? "true" : "false", processo_id, mensagem_id, origem, cat->destino, agora);
    return publicar_marker_exclusivo(path, tmp, conteudo);
}

static int publicar(const char *base, const EntradaCatalogo *cat, const Opcoes *op,
                    const Json *dados, const char *dados_texto,
                    const char *processo_id, const char *mensagem_id,
                    const char *origem, const char *agora) {
    char idem[512] = {0};
    if (op->idempotencia[0]) snprintf(idem, sizeof idem, "%s", op->idempotencia);
    else if (cat->idempotencia_obrigatoria) snprintf(idem, sizeof idem, "api-%s-%016llx", cat->id, (unsigned long long)fnv1a64_bytes(dados_texto));

    if (op->dry_run) {
        escrever_mensagem(stdout, cat, op, dados, processo_id, mensagem_id, origem, agora, idem);
        return 0;
    }

    char espaco[PATH_MAX], tmpdir[PATH_MAX], entrada[PATH_MAX];
    path_join(espaco, sizeof espaco, base, "espaco");
    path_join(tmpdir, sizeof tmpdir, espaco, "tmp");
    path_join(entrada, sizeof entrada, espaco, "entrada");
    if (!mkdir_p(tmpdir) || !mkdir_p(entrada)) {
        fprintf(stderr, "api: nao foi possivel criar espaco/tmp ou espaco/entrada.\n");
        return 1;
    }

    char safe_api[128], safe_mid[256], tmp[PATH_MAX], out[PATH_MAX];
    sanitize_file_id(cat->id, safe_api, sizeof safe_api);
    sanitize_file_id(mensagem_id, safe_mid, sizeof safe_mid);
    fmt_checked(tmp, sizeof tmp, "%s/api-%s-%s.tmp.%d", tmpdir, safe_api, safe_mid, getpid());
    fmt_checked(out, sizeof out, "%s/api-%s-%s.json", entrada, safe_api, safe_mid);

    FILE *f = fopen(tmp, "wb");
    if (!f) { fprintf(stderr, "api: falha ao abrir temporario: %s\n", strerror(errno)); return 1; }
    escrever_mensagem(f, cat, op, dados, processo_id, mensagem_id, origem, agora, idem);
    fflush(f);
    fsync(fileno(f));
    if (fclose(f) != 0) { unlink(tmp); fprintf(stderr, "api: falha ao fechar temporario.\n"); return 1; }
    int idem_rc = registrar_idempotencia_api(espaco, cat, processo_id, mensagem_id, origem, agora, idem);
    if (idem_rc != 0) {
        unlink(tmp);
        fprintf(stderr, "api: %s\n", idem_rc == 2 ? "idempotencia ja publicada neste escopo" : "nao foi possivel registrar idempotencia obrigatoria em espaco/idempotencia.");
        return 1;
    }
    int pub_rc = registrar_publicacao_api(espaco, cat, processo_id, mensagem_id, origem, agora);
    if (pub_rc != 0) {
        unlink(tmp);
        fprintf(stderr, "api: %s\n", pub_rc == 2 ? "mensagemId ja publicado" : "nao foi possivel registrar publicacao obrigatoria em espaco/api-publicadas.");
        return 1;
    }
    if (rename(tmp, out) != 0) { unlink(tmp); fprintf(stderr, "api: falha no rename atomico para entrada: %s\n", strerror(errno)); return 1; }

    printf("{\n");
    printf("  \"status\": \"publicada\",\n");
    printf("  \"idmensagem\": "); json_escape(stdout, cat->id); printf(",\n");
    printf("  \"origem\": "); json_escape(stdout, origem); printf(",\n");
    printf("  \"destino\": "); json_escape(stdout, cat->destino); printf(",\n");
    printf("  \"processoId\": "); json_escape(stdout, processo_id); printf(",\n");
    printf("  \"mensagemId\": "); json_escape(stdout, mensagem_id); printf(",\n");
    printf("  \"arquivo\": ");
    char rel[PATH_MAX]; fmt_checked(rel, sizeof rel, "espaco/entrada/%s", strrchr(out, '/') ? strrchr(out, '/') + 1 : out);
    json_escape(stdout, rel); printf("\n");
    printf("}\n");
    return 0;
}

static int parse_args(int argc, char **argv, Opcoes *op) {
    memset(op, 0, sizeof(*op));
    if (argc < 2 || !strcmp(argv[1], "--help") || !strcmp(argv[1], "--ajuda") || !strcmp(argv[1], "-h")) { uso(); return argc < 2 ? 1 : 2; }
    snprintf(op->api_id, sizeof(op->api_id), "%s", argv[1]);
    for (int i = 2; i < argc; i++) {
        const char *a = argv[i];
        if (!strncmp(a, "--dados=", 8)) snprintf(op->dados_arg, sizeof(op->dados_arg), "%s", a + 8);
        else if (!strncmp(a, "--dados-arquivo=", 16)) snprintf(op->dados_arquivo, sizeof(op->dados_arquivo), "%s", a + 16);
        else if (!strncmp(a, "--origem=", 9)) snprintf(op->origem, sizeof(op->origem), "%s", a + 9);
        else if (!strncmp(a, "--processo-id=", 14)) snprintf(op->processo_id, sizeof(op->processo_id), "%s", a + 14);
        else if (!strncmp(a, "--mensagem-id=", 14)) snprintf(op->mensagem_id, sizeof(op->mensagem_id), "%s", a + 14);
        else if (!strncmp(a, "--resposta-a=", 13)) snprintf(op->resposta_a, sizeof(op->resposta_a), "%s", a + 13);
        else if (!strncmp(a, "--idempotencia=", 15)) snprintf(op->idempotencia, sizeof(op->idempotencia), "%s", a + 15);
        else if (!strncmp(a, "--prioridade=", 13)) snprintf(op->prioridade, sizeof(op->prioridade), "%s", a + 13);
        else if (!strncmp(a, "--tipo=", 7)) snprintf(op->tipo_mensagem, sizeof(op->tipo_mensagem), "%s", a + 7);
        else if (!strcmp(a, "--dry-run")) op->dry_run = 1;
        else { fprintf(stderr, "api: opcao invalida: %s\n", a); uso(); return 1; }
    }
    if (!valid_id(op->api_id)) { fprintf(stderr, "api: idmensagem invalido: %s\n", op->api_id); return 1; }
    if (op->dados_arg[0] && op->dados_arquivo[0]) { fprintf(stderr, "api: use --dados ou --dados-arquivo, nao ambos.\n"); return 1; }
    if (op->prioridade[0] && !valid_prioridade(op->prioridade)) { fprintf(stderr, "api: prioridade invalida.\n"); return 1; }
    if (op->tipo_mensagem[0] && !valid_tipo_msg(op->tipo_mensagem)) { fprintf(stderr, "api: tipo de mensagem invalido.\n"); return 1; }
    if (op->processo_id[0] && !valid_id(op->processo_id)) { fprintf(stderr, "api: processo-id invalido.\n"); return 1; }
    if (op->mensagem_id[0] && !valid_id(op->mensagem_id)) { fprintf(stderr, "api: mensagem-id invalido.\n"); return 1; }
    if (op->resposta_a[0] && !valid_id(op->resposta_a)) { fprintf(stderr, "api: resposta-a invalido.\n"); return 1; }
    if (op->idempotencia[0] && (strlen(op->idempotencia) < 8 || strlen(op->idempotencia) >= 512)) { fprintf(stderr, "api: idempotencia deve ter entre 8 e 511 caracteres.\n"); return 1; }
    return 0;
}

int main(int argc, char **argv) {
    if (restricao_verificar("api") != 0) return 1;

    Opcoes op;
    int pr = parse_args(argc, argv, &op);
    if (pr == 2) return 0;
    if (pr != 0) return 1;

    char base[PATH_MAX];
    if (!getcwd(base, sizeof base)) { perror("api: getcwd"); return 1; }

    EntradaCatalogo cat;
    if (carregar_entrada(base, op.api_id, &cat) != 0) return 1;
    if (op.tipo_mensagem[0]) snprintf(cat.tipo_mensagem, sizeof(cat.tipo_mensagem), "%s", op.tipo_mensagem);
    if (op.prioridade[0]) snprintf(cat.prioridade, sizeof(cat.prioridade), "%s", op.prioridade);

    char origem[MAX_ENTIDADE + 1] = {0};
    if (op.origem[0]) snprintf(origem, sizeof origem, "%s", op.origem);
    else if (cat.origem_padrao[0]) snprintf(origem, sizeof origem, "%s", cat.origem_padrao);
    else {
        const char *env = getenv("SISC_API_ORIGEM");
        if (!env || !*env) env = getenv("SISC_ORIGEM");
        snprintf(origem, sizeof origem, "%s", (env && *env) ? env : "sistema__api");
    }
    if (!valid_entidade(origem)) {
        fprintf(stderr, "api: origem invalida: %s\n", origem);
        fprintf(stderr, "api: use entidade SISC: agente__id, conector__id ou sistema__id.\n");
        return 1;
    }

    char erro[256];
    char *dados_texto = carregar_dados_texto(&op, erro, sizeof erro);
    if (!dados_texto) { fprintf(stderr, "api: nao foi possivel carregar dados: %s\n", erro); return 1; }
    Json *dados_root = json_parse_text(dados_texto, strlen(dados_texto), erro, sizeof erro);
    if (!dados_root) { fprintf(stderr, "api: dados JSON invalidos: %s\n", erro); free(dados_texto); return 1; }
    const Json *dados_payload = selecionar_dados_payload(dados_root, op.api_id);
    if (!dados_payload) { json_free(dados_root); free(dados_texto); return 1; }
    if (validar_destino_registrado(base, cat.destino, dados_payload) != 0) { json_free(dados_root); free(dados_texto); return 1; }

    char processo_id[MAX_ID + 1], mensagem_id[MAX_ID + 1], agora[64];
    if (op.processo_id[0]) snprintf(processo_id, sizeof processo_id, "%s", op.processo_id); else gerar_id("proc", op.api_id, processo_id, sizeof processo_id);
    if (op.mensagem_id[0]) snprintf(mensagem_id, sizeof mensagem_id, "%s", op.mensagem_id); else gerar_id("msg", op.api_id, mensagem_id, sizeof mensagem_id);
    if (cat.correlacao_obrigatoria && (!valid_id(processo_id) || !valid_id(mensagem_id))) {
        fprintf(stderr, "api: correlacao obrigatoria invalida; informe processo-id e mensagem-id validos.\n");
        json_free(dados_root);
        free(dados_texto);
        return 1;
    }
    now_iso(agora, sizeof agora);

    int rc = publicar(base, &cat, &op, dados_payload, dados_texto, processo_id, mensagem_id, origem, agora);
    json_free(dados_root);
    free(dados_texto);
    return rc;
}
