/*
 * importar-conectores - importa pacotes autocontidos da biblioteca central
 * Uso: pp --importar-conectores
 *
 * Lê pacotes tar.gz em sisc/core/biblioteca-conectores, descompacta no sistema
 * atual e mescla no catalogo web-api o bloco de mensagens do conector quando
 * o idmensagem ainda nao existir.
 */

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

typedef struct { char nome[PATH_MAX]; char arquivo[PATH_MAX]; char caminho[PATH_MAX*2]; } Pacote;

typedef struct {
    char papel[128];
    char origem[PATH_MAX*2];
    char destino[PATH_MAX*2];
    char modo[64];
    int obrigatorio;
} Dependencia;

typedef struct {
    Dependencia *v;
    int n;
    int cap;
} ListaDependencias;

static void uso(void) {
    fputs(
        "Uso:\n"
        "  pp --importar-conectores\n"
        "  pp --importar-conectores --todos\n"
        "  pp --importar-conectores --remover=<conector> [--purge-vestigios] [--purge-biblioteca]\n"
        "\n"
        "Descrição:\n"
        "  Lista pacotes em sisc/core/biblioteca-conectores/*.tar.gz e importa os\n"
        "  conectores selecionados para o sistema atual. O campo dependencias do\n"
        "  manifesto do conector é a fonte segura para decidir quais arquivos copiar\n"
        "  e em quais destinos alocar. Arquivos declarados no manifesto anterior e\n"
        "  ausentes no novo pacote sao removidos automaticamente. Se o pacote trouxer\n"
        "  bloco de catalogo, mescla em web-api/catalogo-mensagens.json apenas\n"
        "  idmensagem ainda ausente.\n"
        "\n"
        "Remoção:\n"
        "  --remover=<conector> remove o diretorio conectores/<conector>, o catalogo\n"
        "  modular web-api/catalogo-<conector>.json e regenera o catalogo agregado.\n"
        "  --purge-vestigios tambem limpa referencias do espaco do sistema atual.\n"
        "  --purge-biblioteca remove o pacote central core/biblioteca-conectores/<conector>.tar.gz.\n"
        "\n"
        "Exemplos:\n"
        "  pp --importar-conectores\n"
        "  pp --importar-conectores --todos\n"
        "  pp --importar-conectores --remover=conector-exemplo --purge-vestigios\n",
        stdout);
}

static int termina_com(const char *s, const char *suf) { size_t a=strlen(s),b=strlen(suf); return a>=b && strcmp(s+a-b,suf)==0; }

static int shell_quote(const char *s, char *out, size_t outsz) {
    size_t n=0; if(n+1>=outsz) return -1; out[n++]='\'';
    for(const char *p=s?s:""; *p; p++) {
        if(*p=='\'') { const char *q="'\\''"; for(int i=0;q[i];i++){ if(n+1>=outsz) return -1; out[n++]=q[i]; } }
        else { if(n+1>=outsz) return -1; out[n++]=*p; }
    }
    if(n+2>=outsz) return -1;
    out[n++]='\'';
    out[n]='\0';
    return 0;
}

static void rm_rf(const char *dir) {
    char q[PATH_MAX*4], cmd[PATH_MAX*5];
    if(shell_quote(dir,q,sizeof(q))==0){ snprintf(cmd,sizeof(cmd),"rm -rf %s",q); system(cmd); }
}

static int cmp_pacote(const void *a,const void *b){const Pacote*pa=a,*pb=b;return strcasecmp(pa->nome,pb->nome);} 

static int listar_pacotes(const char *dir, Pacote **out, int *total_out) {
    DIR *d=opendir(dir); if(!d) return -1;
    int cap=16,total=0; Pacote *v=calloc((size_t)cap,sizeof(Pacote)); if(!v){closedir(d);return -1;}
    struct dirent *e;
    while((e=readdir(d))) {
        const char *n=e->d_name;
        if(n[0]=='.' || !termina_com(n,".tar.gz")) continue;
        char path[PATH_MAX*2]; snprintf(path,sizeof(path),"%s/%s",dir,n);
        struct stat st; if(stat(path,&st)!=0 || !S_ISREG(st.st_mode)) continue;
        if(total>=cap){cap*=2; Pacote*nv=realloc(v,(size_t)cap*sizeof(Pacote)); if(!nv){free(v);closedir(d);return -1;} v=nv;}
        snprintf(v[total].arquivo,sizeof(v[total].arquivo),"%s",n);
        snprintf(v[total].caminho,sizeof(v[total].caminho),"%s",path);
        snprintf(v[total].nome,sizeof(v[total].nome),"%s",n);
        v[total].nome[strlen(v[total].nome)-7]='\0';
        total++;
    }
    closedir(d); qsort(v,(size_t)total,sizeof(Pacote),cmp_pacote); *out=v; *total_out=total; return 0;
}

static int parse_selecao(const char *entrada, int total, int *sel, int max_sel) {
    char *dup=strdup(entrada?entrada:""); if(!dup) return -1;
    char *p=dup; while(isspace((unsigned char)*p)) p++;
    char *fim=p+strlen(p); while(fim>p && isspace((unsigned char)fim[-1])) *--fim='\0';
    if(!*p){free(dup);return -1;}
    if(!strcasecmp(p,"todas") || !strcasecmp(p,"todos") || !strcmp(p,"*")) { int n=0; for(int i=0;i<total && n<max_sel;i++) sel[n++]=i; free(dup); return n; }
    int n=0; char *save=NULL; char *tok=strtok_r(p,", \t",&save);
    while(tok && n<max_sel) { if(isdigit((unsigned char)tok[0])) { int x=atoi(tok); if(x>=1 && x<=total) { int ja=0; for(int i=0;i<n;i++) if(sel[i]==x-1) ja=1; if(!ja) sel[n++]=x-1; } } tok=strtok_r(NULL,", \t",&save); }
    free(dup); return n>0?n:-1;
}

static int entrada_tar_segura(const char *linha) {
    if(!linha || !*linha) return 1;
    char rel[PATH_MAX*2]; snprintf(rel,sizeof(rel),"%s",linha);
    while(!strncmp(rel,"./",2)) memmove(rel,rel+2,strlen(rel+2)+1);
    if(!*rel || !strcmp(rel,".")) return 1;
    if(rel[0]=='/' || strstr(rel,"..")) return 0;
    if(termina_com(rel,"/")) return 1;
    if(!strcmp(rel,"PACOTE-CONECTOR.json")) return 1;
    if(!strcmp(rel,"CATALOGO-MENSAGENS-CONECTOR.json")) return 1;
    if(strncmp(rel,"web-api/catalogo-",16)==0 && termina_com(rel,".json")) return 1;
    return strncmp(rel,"conectores/",11)==0;
}

static int validar_pacote(const char *pkg) {
    char q[PATH_MAX*4], cmd[PATH_MAX*5]; if(shell_quote(pkg,q,sizeof(q))!=0) return -1;
    snprintf(cmd,sizeof(cmd),"tar -tzf %s",q);
    FILE *fp=popen(cmd,"r"); if(!fp) return -1;
    char linha[PATH_MAX*2]; int ok=1, arquivos=0;
    while(fgets(linha,sizeof(linha),fp)) {
        linha[strcspn(linha,"\r\n")]='\0';
        if(!entrada_tar_segura(linha)) { fprintf(stderr,"  [erro] entrada insegura no pacote: %s\n",linha); ok=0; }
        if(!termina_com(linha,"/")) arquivos++;
    }
    int rc=pclose(fp);
    return ok && rc==0 && arquivos>0 ? 0 : -1;
}

static int perguntar_sobrescrita_dependencias(const ListaDependencias *deps, const char *cwd) {
    int existe=0;
    for(int i=0; deps && i<deps->n; i++) {
        const Dependencia *d=&deps->v[i];
        if(!strcmp(d->modo,"mesclar") || !strcmp(d->destino,"web-api/catalogo-mensagens.json")) continue;
        char dst[PATH_MAX*3]; snprintf(dst,sizeof(dst),"%s/%s",cwd,d->destino);
        if(access(dst,F_OK)==0){ printf("  existente: %s\n",d->destino); existe=1; }
    }
    if(!existe) return 1;
    char resp[16]; printf("  Arquivos existentes serao sobrescritos. Continuar? [s/N]: "); fflush(stdout);
    if(!fgets(resp,sizeof(resp),stdin)) return 0;
    return resp[0]=='s'||resp[0]=='S'||resp[0]=='y'||resp[0]=='Y';
}

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

static char *ler_arquivo_texto(const char *path) {
    FILE *fp=fopen(path,"rb");
    if(!fp) return NULL;
    if(fseek(fp,0,SEEK_END)!=0){fclose(fp);return NULL;}
    long t=ftell(fp);
    if(t<0 || t>20*1024*1024){fclose(fp);return NULL;}
    rewind(fp);
    char *b=malloc((size_t)t+1);
    if(!b){fclose(fp);return NULL;}
    size_t n=fread(b,1,(size_t)t,fp);
    fclose(fp);
    b[n]='\0';
    return b;
}

static int garantir_dir(const char *dir, mode_t modo) {
    struct stat st;
    if (stat(dir,&st)==0) return S_ISDIR(st.st_mode)?0:-1;
    if (mkdir(dir,modo)!=0 && errno!=EEXIST) return -1;
    chmod(dir,modo);
    return 0;
}

static int mkdir_p(const char *path, mode_t modo) {
    char tmp[PATH_MAX*2];
    snprintf(tmp,sizeof(tmp),"%s",path?path:"");
    for (char *p=tmp+1; *p; p++) {
        if (*p=='/') { *p='\0'; if (garantir_dir(tmp,modo)!=0) return -1; *p='/'; }
    }
    return garantir_dir(tmp,modo);
}

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

static int copiar_arquivo_preservando(const char *origem, const char *destino) {
    struct stat st;
    if(lstat(origem,&st)!=0 || S_ISLNK(st.st_mode) || !S_ISREG(st.st_mode)) return -1;
    FILE *in=fopen(origem,"rb");
    if(!in) return -1;
    if(garantir_dir_pai(destino,0775)!=0){fclose(in);return -1;}
    FILE *out=fopen(destino,"wb");
    if(!out){fclose(in);return -1;}
    char buf[8192]; size_t n; int ok=0;
    while((n=fread(buf,1,sizeof(buf),in))>0) if(fwrite(buf,1,n,out)!=n){ok=-1;break;}
    if(ferror(in)) ok=-1;
    fclose(in); fclose(out);
    chmod(destino, st.st_mode & 0777);
    return ok;
}

static int gravar_texto_atomico(const char *path, const char *txt) {
    if (garantir_dir_pai(path,0775)!=0) return -1;
    char tmp[PATH_MAX*2]; snprintf(tmp,sizeof(tmp),"%s.tmp.%ld",path,(long)getpid());
    FILE *f=fopen(tmp,"wb");
    if(!f) return -1;
    size_t n=strlen(txt?txt:"");
    int ok=fwrite(txt?txt:"",1,n,f)==n ? 0 : -1;
    if(fclose(f)!=0) ok=-1;
    if(ok!=0){unlink(tmp);return -1;}
    chmod(tmp,0644);
    if(rename(tmp,path)!=0){unlink(tmp);return -1;}
    return 0;
}

static const char *pular_espacos_json(const char *p) {
    while (p && *p && isspace((unsigned char)*p)) p++;
    return p;
}

static int extrair_string_json_simples(const char *txt, const char *chave, char *out, size_t tam) {
    if (!txt || !chave || !out || tam==0) return 0;
    char padrao[128]; snprintf(padrao,sizeof(padrao),"\"%s\"",chave);
    const char *p=txt;
    while ((p=strstr(p,padrao)) != NULL) {
        p += strlen(padrao);
        p = pular_espacos_json(p);
        if (!p || *p!=':') continue;
        p++;
        p = pular_espacos_json(p);
        if (!p || *p!='\"') continue;
        p++;
        size_t j=0; int escape=0;
        while (*p) {
            if (escape) { if (j+1<tam) out[j++]=*p; escape=0; }
            else if (*p=='\\') escape=1;
            else if (*p=='\"') { out[j]='\0'; return j>0; }
            else { if (j+1<tam) out[j++]=*p; }
            p++;
        }
    }
    return 0;
}

static int extrair_bool_json_simples(const char *txt, const char *chave, int *valor) {
    if (!txt || !chave || !valor) return 0;
    char padrao[128]; snprintf(padrao,sizeof(padrao),"\"%s\"",chave);
    const char *p=strstr(txt,padrao);
    if(!p) return 0;
    p += strlen(padrao);
    p = pular_espacos_json(p);
    if(!p || *p!=':') return 0;
    p++;
    p = pular_espacos_json(p);
    if(p && !strncmp(p,"true",4)){ *valor=1; return 1; }
    if(p && !strncmp(p,"false",5)){ *valor=0; return 1; }
    return 0;
}

static const char *json_composto_fim(const char *ini, char abre, char fecha, const char *limite) {
    int nivel=0, em_string=0, escape=0;
    for (const char *p=ini; p && *p && (!limite || p<=limite); p++) {
        if (em_string) {
            if (escape) escape=0;
            else if (*p=='\\') escape=1;
            else if (*p=='\"') em_string=0;
            continue;
        }
        if (*p=='\"') em_string=1;
        else if (*p==abre) nivel++;
        else if (*p==fecha) {
            nivel--;
            if (nivel==0) return p;
            if (nivel<0) return NULL;
        }
    }
    return NULL;
}

static int localizar_array_json_chave(const char *json, const char *chave, const char **ini, const char **fim) {
    char padrao[128]; snprintf(padrao,sizeof(padrao),"\"%s\"",chave);
    const char *p=json;
    while ((p=strstr(p,padrao)) != NULL) {
        p += strlen(padrao);
        p = pular_espacos_json(p);
        if (!p || *p!=':') continue;
        p++;
        p = pular_espacos_json(p);
        if (!p || *p!='[') continue;
        const char *f=json_composto_fim(p,'[',']',NULL);
        if (!f) return -1;
        *ini=p; *fim=f; return 0;
    }
    return -1;
}

static int localizar_objeto_json_chave(const char *json, const char *chave, const char **ini, const char **fim) {
    char padrao[128]; snprintf(padrao,sizeof(padrao),"\"%s\"",chave);
    const char *p=json;
    while ((p=strstr(p,padrao)) != NULL) {
        p += strlen(padrao);
        p = pular_espacos_json(p);
        if (!p || *p!=':') continue;
        p++;
        p = pular_espacos_json(p);
        if (!p || *p!='{') continue;
        const char *f=json_composto_fim(p,'{','}',NULL);
        if (!f) return -1;
        *ini=p; *fim=f; return 0;
    }
    return -1;
}

static int proximo_objeto_json(const char *p, const char *limite, const char **ini, const char **fim) {
    int em_string=0, escape=0;
    for (; p && p<limite && *p; p++) {
        if (em_string) {
            if (escape) escape=0;
            else if (*p=='\\') escape=1;
            else if (*p=='\"') em_string=0;
            continue;
        }
        if (*p=='\"') em_string=1;
        else if (*p=='{') {
            const char *f=json_composto_fim(p,'{','}',limite);
            if (!f) return -1;
            *ini=p; *fim=f; return 1;
        }
    }
    return 0;
}

static char *substr_objeto_json(const char *ini, const char *fim) {
    if (!ini || !fim || fim<ini) return NULL;
    size_t n=(size_t)(fim-ini+1);
    char *s=malloc(n+1);
    if(!s) return NULL;
    memcpy(s,ini,n); s[n]='\0';
    return s;
}

static void deps_init(ListaDependencias *l) { memset(l,0,sizeof(*l)); }

static void deps_free(ListaDependencias *l) {
    if(!l) return;
    free(l->v);
    memset(l,0,sizeof(*l));
}

static int contem_segredos_rel(const char *rel) {
    return rel && (strstr(rel,"/secretos/") || strstr(rel,"/secrets/") || termina_com(rel,"/secretos") || termina_com(rel,"/secrets"));
}

static int normalizar_rel_dependencia(const char *in, char *out, size_t outsz) {
    if(!in || !*in || !out || outsz==0) return -1;
    while(!strncmp(in,"./",2)) in += 2;
    if(!*in || in[0]=='/' || strstr(in,"..")) return -1;
    snprintf(out,outsz,"%s",in);
    size_t n=strlen(out);
    while(n>0 && out[n-1]=='/') out[--n]='\0';
    if(!*out || contem_segredos_rel(out)) return -1;
    return 0;
}

static int dependencia_catalogo(const Dependencia *d) {
    return d && (!strcmp(d->modo,"mesclar") || !strcmp(d->destino,"web-api/catalogo-mensagens.json"));
}

static int dependencia_segura(const Dependencia *d) {
    if(!d || !d->origem[0] || !d->destino[0]) return 0;
    if(dependencia_catalogo(d))
        return !strcmp(d->origem,"CATALOGO-MENSAGENS-CONECTOR.json") && !strcmp(d->destino,"web-api/catalogo-mensagens.json");
    if(!strncmp(d->origem,"web-api/catalogo-",16) && !strncmp(d->destino,"web-api/catalogo-",16)
       && termina_com(d->origem,".json") && termina_com(d->destino,".json")
       && strcmp(d->destino,"web-api/catalogo-mensagens.json")!=0) return 1;
    return !strncmp(d->origem,"conectores/",11) && !strncmp(d->destino,"conectores/",11);
}

static int deps_add(ListaDependencias *l, Dependencia *d) {
    if(!dependencia_segura(d)) return -1;
    for(int i=0;i<l->n;i++) if(!strcmp(l->v[i].destino,d->destino) && !strcmp(l->v[i].origem,d->origem)) return 0;
    if(l->n >= l->cap) {
        int ncap = l->cap ? l->cap * 2 : 8;
        Dependencia *nv=realloc(l->v,(size_t)ncap*sizeof(Dependencia));
        if(!nv) return -1;
        l->v=nv; l->cap=ncap;
    }
    l->v[l->n++]=*d;
    return 0;
}

static int ler_string_json_token(const char **p_io, const char *limite, char *out, size_t outsz) {
    const char *p=*p_io;
    if(!p || p>=limite || *p!='\"') return -1;
    p++;
    size_t j=0; int esc=0;
    while(p<limite && *p) {
        if(esc) { if(j+1<outsz) out[j++]=*p; esc=0; }
        else if(*p=='\\') esc=1;
        else if(*p=='\"') { p++; out[j]='\0'; *p_io=p; return 0; }
        else { if(j+1<outsz) out[j++]=*p; }
        p++;
    }
    return -1;
}

static int coletar_dependencias_manifesto(const char *manifesto, ListaDependencias *deps) {
    deps_init(deps);
    const char *dep_ini=NULL,*dep_fim=NULL;
    if(localizar_objeto_json_chave(manifesto,"dependencias",&dep_ini,&dep_fim)!=0) return -1;
    char *dep_obj=substr_objeto_json(dep_ini,dep_fim);
    if(!dep_obj) return -1;
    const char *arr_ini=NULL,*arr_fim=NULL;
    if(localizar_array_json_chave(dep_obj,"arquivos",&arr_ini,&arr_fim)!=0){free(dep_obj);return -1;}
    const char *p=arr_ini+1;
    while(p<arr_fim) {
        while(p<arr_fim && (isspace((unsigned char)*p) || *p==',')) p++;
        if(p>=arr_fim) break;
        Dependencia d; memset(&d,0,sizeof(d));
        snprintf(d.modo,sizeof(d.modo),"copiar");
        d.obrigatorio=1;
        if(*p=='\"') {
            char rel[PATH_MAX*2];
            if(ler_string_json_token(&p,arr_fim,rel,sizeof(rel))!=0){deps_free(deps);free(dep_obj);return -1;}
            if(normalizar_rel_dependencia(rel,d.origem,sizeof(d.origem))!=0 || normalizar_rel_dependencia(rel,d.destino,sizeof(d.destino))!=0){deps_free(deps);free(dep_obj);return -1;}
            snprintf(d.papel,sizeof(d.papel),"arquivo");
        } else if(*p=='{') {
            const char *of=json_composto_fim(p,'{','}',arr_fim);
            if(!of){deps_free(deps);free(dep_obj);return -1;}
            char *obj=substr_objeto_json(p,of);
            if(!obj){deps_free(deps);free(dep_obj);return -1;}
            char origem[PATH_MAX*2]="", destino[PATH_MAX*2]="", papel[128]="", modo[64]="";
            extrair_string_json_simples(obj,"origem",origem,sizeof(origem));
            if(!origem[0]) extrair_string_json_simples(obj,"arquivo",origem,sizeof(origem));
            if(!origem[0]) extrair_string_json_simples(obj,"caminho",origem,sizeof(origem));
            extrair_string_json_simples(obj,"destino",destino,sizeof(destino));
            extrair_string_json_simples(obj,"papel",papel,sizeof(papel));
            extrair_string_json_simples(obj,"modo",modo,sizeof(modo));
            extrair_bool_json_simples(obj,"obrigatorio",&d.obrigatorio);
            free(obj);
            if(!origem[0] && destino[0]) snprintf(origem,sizeof(origem),"%s",destino);
            if(!destino[0] && origem[0]) snprintf(destino,sizeof(destino),"%s",origem);
            if(normalizar_rel_dependencia(origem,d.origem,sizeof(d.origem))!=0 || normalizar_rel_dependencia(destino,d.destino,sizeof(d.destino))!=0){deps_free(deps);free(dep_obj);return -1;}
            snprintf(d.papel,sizeof(d.papel),"%s",papel[0]?papel:"arquivo");
            if(modo[0]) snprintf(d.modo,sizeof(d.modo),"%s",modo);
            p=of+1;
        } else {
            deps_free(deps); free(dep_obj); return -1;
        }
        if(deps_add(deps,&d)!=0){deps_free(deps);free(dep_obj);return -1;}
    }
    free(dep_obj);
    return deps->n>0 ? 0 : -1;
}

static int validar_dependencias_minimas(const ListaDependencias *deps, const char *nome, const char *formato_rel) {
    char manifesto_rel[PATH_MAX*2], manifesto_legado[PATH_MAX*2];
    snprintf(manifesto_rel,sizeof(manifesto_rel),"conectores/%s/%s.json",nome,nome);
    snprintf(manifesto_legado,sizeof(manifesto_legado),"conectores/%s.json",nome);
    int tem_manifesto=0, tem_formato=0;
    for(int i=0; deps && i<deps->n; i++) {
        if(!strcmp(deps->v[i].destino,manifesto_rel) || !strcmp(deps->v[i].origem,manifesto_rel) ||
           !strcmp(deps->v[i].destino,manifesto_legado) || !strcmp(deps->v[i].origem,manifesto_legado)) tem_manifesto=1;
        if(!strcmp(deps->v[i].destino,formato_rel) || !strcmp(deps->v[i].origem,formato_rel)) tem_formato=1;
    }
    if(!tem_manifesto) { fprintf(stderr,"  [erro] dependencias sem manifesto %s\n",manifesto_rel); return -1; }
    if(!tem_formato) { fprintf(stderr,"  [erro] dependencias sem o proprio formato %s\n",formato_rel); return -1; }
    return 0;
}

static int formato_rel_manifesto(const char *manifesto, const char *nome, char *out, size_t outsz) {
    char decl[PATH_MAX*2]="";
    if(!extrair_string_json_simples(manifesto,"formatoConector",decl,sizeof(decl)) || !decl[0]) {
        snprintf(out,outsz,"conectores/%s/formatos/formato-%s.json",nome,nome);
        return 0;
    }
    while(!strncmp(decl,"./",2)) memmove(decl,decl+2,strlen(decl+2)+1);
    if(strchr(decl,'/')) return normalizar_rel_dependencia(decl,out,outsz);
    if(decl[0]=='/' || strstr(decl,"..")) return -1;
    char tmp[PATH_MAX*2]; snprintf(tmp,sizeof(tmp),"conectores/%s/formatos/%s",nome,decl);
    return normalizar_rel_dependencia(tmp,out,outsz);
}

static int conjunto_contem(const char *rel, char **v, int n) {
    for(int i=0;i<n;i++) if(!strcmp(v[i],rel)) return 1;
    return 0;
}

static int listar_arquivos_rel_recursivo(const char *base, const char *subdir, char ***out, int *n_out, int *cap) {
    char path[PATH_MAX*3];
    if(subdir && *subdir) snprintf(path,sizeof(path),"%s/%s",base,subdir);
    else snprintf(path,sizeof(path),"%s",base);
    DIR *d=opendir(path);
    if(!d) return -1;
    struct dirent *e;
    while((e=readdir(d))!=NULL) {
        if(!strcmp(e->d_name,".") || !strcmp(e->d_name,"..")) continue;
        char rel[PATH_MAX*2];
        if(subdir && *subdir) snprintf(rel,sizeof(rel),"%s/%s",subdir,e->d_name);
        else snprintf(rel,sizeof(rel),"%s",e->d_name);
        char full[PATH_MAX*3]; snprintf(full,sizeof(full),"%s/%s",base,rel);
        struct stat st;
        if(lstat(full,&st)!=0) continue;
        if(S_ISDIR(st.st_mode)) {
            if(listar_arquivos_rel_recursivo(base,rel,out,n_out,cap)!=0){ closedir(d); return -1; }
        } else if(S_ISREG(st.st_mode)) {
            if(*n_out >= *cap) {
                int ncap=*cap?*cap*2:16;
                char **nv=realloc(*out,(size_t)ncap*sizeof(char*));
                if(!nv){ closedir(d); return -1; }
                *out=nv; *cap=ncap;
            }
            (*out)[*n_out]=strdup(rel);
            if(!(*out)[*n_out]){ closedir(d); return -1; }
            (*n_out)++;
        }
    }
    closedir(d);
    return 0;
}

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

static char **listar_arquivos_rel(const char *base, int *n_out) {
    char **v=NULL; int n=0,cap=0;
    if(listar_arquivos_rel_recursivo(base,NULL,&v,&n,&cap)!=0){ stringlist_free(v,n); *n_out=0; return NULL; }
    *n_out=n;
    return v;
}

static int coletar_destinos_obsoletos_manifesto(const char *manifesto, char ***out, int *n_out) {
    *out=NULL; *n_out=0;
    ListaDependencias deps;
    if(coletar_dependencias_manifesto(manifesto,&deps)!=0) return 0;
    int cap=0,n=0;
    for(int i=0;i<deps.n;i++) {
        const Dependencia *d=&deps.v[i];
        if(dependencia_catalogo(d)) continue;
        const char *ref = d->destino[0] ? d->destino : d->origem;
        if(!ref[0] || strncmp(ref,"conectores/",11)!=0) continue;
        char rel[PATH_MAX*2];
        if(normalizar_rel_dependencia(ref,rel,sizeof(rel))!=0) continue;
        if(n>=cap){ cap=cap?cap*2:8; char **nv=realloc(*out,(size_t)cap*sizeof(char*)); if(!nv){deps_free(&deps);stringlist_free(*out,n);*out=NULL;*n_out=0;return -1;} *out=nv; }
        (*out)[n]=strdup(rel);
        if(!(*out)[n]){deps_free(&deps);stringlist_free(*out,n);*out=NULL;*n_out=0;return -1;}
        n++;
    }
    deps_free(&deps);
    *n_out=n;
    return 0;
}

static int limpar_obsoletos_conector(const char *cwd, const char *tmp, const char *manifesto_antigo) {
    if(!manifesto_antigo || !*manifesto_antigo) return 0;
    char **antigos=NULL; int nantigos=0;
    if(coletar_destinos_obsoletos_manifesto(manifesto_antigo,&antigos,&nantigos)!=0){ return -1; }
    if(nantigos==0){ stringlist_free(antigos,nantigos); return 0; }

    char **novos=NULL; int nnovos=0;
    novos=listar_arquivos_rel(tmp,&nnovos);
    if(!novos){ stringlist_free(antigos,nantigos); return -1; }

    int removidos=0;
    for(int i=0;i<nantigos;i++) {
        const char *rel=antigos[i];
        if(conjunto_contem(rel,novos,nnovos)) continue;
        char path[PATH_MAX*3]; snprintf(path,sizeof(path),"%s/%s",cwd,rel);
        struct stat st;
        if(lstat(path,&st)!=0) continue;
        if(S_ISREG(st.st_mode)) {
            if(unlink(path)!=0) { fprintf(stderr,"  [aviso] nao foi possivel remover obsoleto: %s\n",rel); continue; }
            printf("  [ok] removido obsoleto: %s\n",rel); removidos++;
        } else if(S_ISDIR(st.st_mode)) {
            char cmd[PATH_MAX*5], q[PATH_MAX*4];
            if(shell_quote(path,q,sizeof(q))!=0) continue;
            snprintf(cmd,sizeof(cmd),"find %s -type f -delete 2>/dev/null; rmdir %s 2>/dev/null",q,q);
            system(cmd);
            printf("  [ok] removido diretorio obsoleto: %s\n",rel); removidos++;
        }
    }
    for(int i=0;i<nantigos;i++) {
        const char *rel=antigos[i];
        if(conjunto_contem(rel,novos,nnovos)) continue;
        char path[PATH_MAX*3]; snprintf(path,sizeof(path),"%s/%s",cwd,rel);
        struct stat st;
        if(lstat(path,&st)==0) continue;
        char dir[PATH_MAX*2]; snprintf(dir,sizeof(dir),"%s",rel);
        char *s=strrchr(dir,'/');
        if(!s || s==dir) continue;
        *s='\0';
        char dirpath[PATH_MAX*3]; snprintf(dirpath,sizeof(dirpath),"%s/%s",cwd,dir);
        rmdir(dirpath);
    }
    stringlist_free(antigos,nantigos);
    stringlist_free(novos,nnovos);
    printf("  [info] obsoletos removidos: %d\n", removidos);
    return 0;
}

static int localizar_manifesto_com_dependencias(const char *tmp, const char *nome, char *rel_out, size_t relsz, char **txt_out) {
    char rel[PATH_MAX*2]; snprintf(rel,sizeof(rel),"conectores/%s/%s.json",nome,nome);
    char path[PATH_MAX*3]; snprintf(path,sizeof(path),"%s/%s",tmp,rel);
    char *txt=ler_arquivo_texto(path);
    if(!txt) {
        snprintf(rel,sizeof(rel),"conectores/%s.json",nome); /* compatibilidade com pacotes legados */
        snprintf(path,sizeof(path),"%s/%s",tmp,rel);
        txt=ler_arquivo_texto(path);
    }
    if(!txt) return -1;
    const char *di=NULL,*df=NULL;
    if(localizar_objeto_json_chave(txt,"dependencias",&di,&df)!=0){ free(txt); return -1; }
    snprintf(rel_out,relsz,"%s",rel);
    *txt_out=txt;
    return 0;
}

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

static int coletar_blocos_mensagens(const char *json, char ***blocos_out, int *n_out) {
    *blocos_out=NULL; *n_out=0;
    const char *arr_ini=NULL,*arr_fim=NULL;
    if(localizar_array_json_chave(json,"mensagens",&arr_ini,&arr_fim)!=0) return -1;
    int cap=0,n=0;
    const char *p=arr_ini+1;
    while(p<arr_fim) {
        const char *oi=NULL,*of=NULL;
        int r=proximo_objeto_json(p,arr_fim,&oi,&of);
        if(r<0){liberar_blocos(*blocos_out,n);*blocos_out=NULL;*n_out=0;return -1;}
        if(r==0) break;
        char *obj=substr_objeto_json(oi,of);
        if(!obj){liberar_blocos(*blocos_out,n);*blocos_out=NULL;*n_out=0;return -1;}
        if(n>=cap){cap=cap?cap*2:4; char **nv=realloc(*blocos_out,(size_t)cap*sizeof(char*)); if(!nv){free(obj);liberar_blocos(*blocos_out,n);*blocos_out=NULL;*n_out=0;return -1;} *blocos_out=nv;}
        (*blocos_out)[n++]=obj;
        p=of+1;
    }
    *n_out=n;
    return 0;
}

static int catalogo_contem_idmensagem(const char *catalogo, const char *id) {
    const char *arr_ini=NULL,*arr_fim=NULL;
    if(!catalogo || !id || !*id || localizar_array_json_chave(catalogo,"mensagens",&arr_ini,&arr_fim)!=0) return 0;
    const char *p=arr_ini+1;
    while(p<arr_fim) {
        const char *oi=NULL,*of=NULL;
        int r=proximo_objeto_json(p,arr_fim,&oi,&of);
        if(r<=0) break;
        char *obj=substr_objeto_json(oi,of);
        if(!obj) return 0;
        char cid[512]="";
        int igual=extrair_string_json_simples(obj,"idmensagem",cid,sizeof(cid)) && strcmp(cid,id)==0;
        free(obj);
        if(igual) return 1;
        p=of+1;
    }
    return 0;
}

static int array_json_tem_item(const char *ini, const char *fim) {
    for(const char *p=ini+1; p<fim; p++) if(!isspace((unsigned char)*p)) return 1;
    return 0;
}

static char *inserir_objeto_catalogo(const char *catalogo, const char *obj) {
    const char *arr_ini=NULL,*arr_fim=NULL;
    if(localizar_array_json_chave(catalogo,"mensagens",&arr_ini,&arr_fim)!=0) return NULL;
    const char *ins=arr_fim;
    while(ins>arr_ini+1 && isspace((unsigned char)ins[-1])) ins--;
    int tem_item=array_json_tem_item(arr_ini,arr_fim);
    const char *prefixo=tem_item ? ",\n    " : "\n    ";
    const char *sufixo=(*ins=='\n' || *ins=='\r') ? "" : "\n  ";
    size_t antes=(size_t)(ins-catalogo), tail=strlen(ins);
    size_t len=antes+strlen(prefixo)+strlen(obj)+strlen(sufixo)+tail+1;
    char *novo=malloc(len);
    if(!novo) return NULL;
    char *w=novo;
    memcpy(w,catalogo,antes); w+=antes;
    strcpy(w,prefixo); w+=strlen(prefixo);
    strcpy(w,obj); w+=strlen(obj);
    strcpy(w,sufixo); w+=strlen(sufixo);
    strcpy(w,ins);
    return novo;
}

static void escrever_bloco_indentado(FILE *f, const char *txt, const char *indent) {
    fputs(indent,f);
    for(const char *p=txt?txt:""; *p; p++) {
        fputc(*p,f);
        if(*p=='\n' && p[1]) fputs(indent,f);
    }
}

static int gravar_catalogo_novo(const char *path, char **mensagens, int nmensagens) {
    if(garantir_dir_pai(path,0775)!=0) return -1;
    FILE *f=fopen(path,"wb");
    if(!f) return -1;
    fputs("{\n  \"tipo\": \"catalogo-mensagens-sisc\",\n  \"versao\": 3,\n  \"descricao\": \"Catalogo declarativo SISC criado ao importar conector.\",\n  \"formatoEscuta\": \"formatos-escuta/protocolo-mensagem-siscore.v1.json\",\n  \"regraComunicacao\": \"somente-espaco-sisc-sem-acesso-direto\",\n  \"modeloProdutoFinal\": \"microservicos-sisc-via-bff-e-catalogo\",\n  \"mensagens\": [\n", f);
    for(int i=0;i<nmensagens;i++) {
        escrever_bloco_indentado(f,mensagens[i],"    ");
        fputs(i+1<nmensagens ? ",\n" : "\n", f);
    }
    fputs("  ]\n}\n", f);
    int ok=fclose(f)==0 ? 0 : -1;
    chmod(path,0644);
    return ok;
}

static int mesclar_catalogo_fragmento_arquivo(const char *frag_path, const char *cwd, int remover) {
    if(!arquivo_existe(frag_path)) return 0;
    char *frag=ler_arquivo_texto(frag_path);
    if(!frag){fprintf(stderr,"  [erro] nao foi possivel ler fragmento de catalogo do pacote\n");return -1;}
    char **msgs=NULL; int nmsgs=0;
    int rc=coletar_blocos_mensagens(frag,&msgs,&nmsgs);
    free(frag);
    if(rc!=0){fprintf(stderr,"  [erro] fragmento de catalogo invalido no pacote\n");return -1;}
    if(nmsgs==0){liberar_blocos(msgs,nmsgs); if(remover) unlink(frag_path); return 0;}

    char cat_path[PATH_MAX*2]; snprintf(cat_path,sizeof(cat_path),"%s/web-api/catalogo-mensagens.json",cwd);
    int inseridos=0, existentes=0, ignorados=0;
    if(!arquivo_existe(cat_path)) {
        if(gravar_catalogo_novo(cat_path,msgs,nmsgs)!=0){liberar_blocos(msgs,nmsgs);fprintf(stderr,"  [erro] falha ao criar web-api/catalogo-mensagens.json\n");return -1;}
        inseridos=nmsgs;
    } else {
        char *catalogo=ler_arquivo_texto(cat_path);
        if(!catalogo){liberar_blocos(msgs,nmsgs);fprintf(stderr,"  [erro] nao foi possivel ler web-api/catalogo-mensagens.json\n");return -1;}
        for(int i=0;i<nmsgs;i++) {
            char id[512]="";
            if(!extrair_string_json_simples(msgs[i],"idmensagem",id,sizeof(id))){ignorados++;continue;}
            if(catalogo_contem_idmensagem(catalogo,id)){existentes++;continue;}
            char *novo=inserir_objeto_catalogo(catalogo,msgs[i]);
            if(!novo){free(catalogo);liberar_blocos(msgs,nmsgs);fprintf(stderr,"  [erro] catalogo local sem array mensagens valido\n");return -1;}
            free(catalogo); catalogo=novo; inseridos++;
        }
        if(inseridos>0 && gravar_texto_atomico(cat_path,catalogo)!=0){free(catalogo);liberar_blocos(msgs,nmsgs);fprintf(stderr,"  [erro] falha ao atualizar web-api/catalogo-mensagens.json\n");return -1;}
        free(catalogo);
    }
    liberar_blocos(msgs,nmsgs);
    if(remover) unlink(frag_path);
    printf("  [ok] catalogo de mensagens: %d inserida(s), %d existente(s), %d ignorada(s)\n", inseridos, existentes, ignorados);
    return 0;
}

static int instalar_dependencias(const ListaDependencias *deps, const char *tmp, const char *cwd) {
    for(int i=0; deps && i<deps->n; i++) {
        const Dependencia *d=&deps->v[i];
        if(dependencia_catalogo(d)) continue;
        char origem[PATH_MAX*3], destino[PATH_MAX*3];
        snprintf(origem,sizeof(origem),"%s/%s",tmp,d->origem);
        snprintf(destino,sizeof(destino),"%s/%s",cwd,d->destino);
        if(!arquivo_existe(origem)) {
            if(!d->obrigatorio){ printf("  [info] dependencia opcional ausente: %s\n",d->origem); continue; }
            fprintf(stderr,"  [erro] dependencia obrigatoria ausente no pacote: %s\n",d->origem);
            return -1;
        }
        if(copiar_arquivo_preservando(origem,destino)!=0) {
            fprintf(stderr,"  [erro] falha ao alocar %s em %s\n",d->origem,d->destino);
            return -1;
        }
        printf("  [ok] %s -> %s\n", d->origem, d->destino);
    }
    for(int i=0; deps && i<deps->n; i++) {
        const Dependencia *d=&deps->v[i];
        if(!dependencia_catalogo(d)) continue;
        char origem[PATH_MAX*3]; snprintf(origem,sizeof(origem),"%s/%s",tmp,d->origem);
        if(!arquivo_existe(origem)) {
            if(!d->obrigatorio){ printf("  [info] catalogo opcional ausente: %s\n",d->origem); continue; }
            fprintf(stderr,"  [erro] fragmento de catalogo obrigatorio ausente: %s\n",d->origem);
            return -1;
        }
        if(mesclar_catalogo_fragmento_arquivo(origem,cwd,0)!=0) return -1;
    }
    return 0;
}

static int remover_conector_instalado(const char *cwd, const char *raiz_sisc, const char *nome, int purge_vestigios, int purge_biblioteca) {
    if(!nome || strncmp(nome,"conector-",9)!=0) {
        fprintf(stderr,"importar-conectores: nome de conector invalido para remocao: %s\n", nome?nome:"(nulo)");
        return 1;
    }
    for(const char *p=nome; *p; p++) {
        if(!((*p>='a'&&*p<='z') || (*p>='0'&&*p<='9') || *p=='-')) {
            fprintf(stderr,"importar-conectores: nome de conector invalido para remocao: %s\n", nome);
            return 1;
        }
    }

    int removidos=0;
    char path[PATH_MAX*3];

    snprintf(path,sizeof(path),"%s/conectores/%s",cwd,nome);
    if(access(path,F_OK)==0) { rm_rf(path); printf("  [ok] removido diretorio: conectores/%s\n",nome); removidos++; }

    snprintf(path,sizeof(path),"%s/web-api/catalogo-%s.json",cwd,nome);
    if(access(path,F_OK)==0) { if(unlink(path)==0){ printf("  [ok] removido catalogo modular: web-api/catalogo-%s.json\n",nome); removidos++; } else fprintf(stderr,"  [aviso] falha ao remover catalogo modular: %s\n",path); }

    char qcwd[PATH_MAX*4], cmd[PATH_MAX*8];
    if(shell_quote(cwd,qcwd,sizeof(qcwd))==0) {
        snprintf(cmd,sizeof(cmd),"cd %s && ../pp --atualizar-catalogo-mensagens >/dev/null 2>&1",qcwd);
        if(system(cmd)==0) printf("  [ok] catalogo agregado regenerado\n");
        else fprintf(stderr,"  [aviso] nao foi possivel regenerar catalogo agregado automaticamente\n");
    }

    if(purge_vestigios) {
        char espaco[PATH_MAX*3], qesp[PATH_MAX*4], qnome[PATH_MAX*4];
        snprintf(espaco,sizeof(espaco),"%s/espaco",cwd);
        if(access(espaco,F_OK)==0 && shell_quote(espaco,qesp,sizeof(qesp))==0 && shell_quote(nome,qnome,sizeof(qnome))==0) {
            snprintf(cmd,sizeof(cmd),"find %s -type f -name '*%s*' -delete 2>/dev/null; grep -RIl -- %s %s 2>/dev/null | xargs -r rm -f; find %s -depth -type d -name '*%s*' -exec rm -rf {} + 2>/dev/null",qesp,nome,qnome,qesp,qesp,nome);
            system(cmd);
            printf("  [ok] vestigios do espaco limpos para: %s\n",nome);
        }
    }

    if(purge_biblioteca) {
        snprintf(path,sizeof(path),"%s/core/biblioteca-conectores/%s.tar.gz",raiz_sisc,nome);
        if(access(path,F_OK)==0) { if(unlink(path)==0){ printf("  [ok] removido pacote da biblioteca: core/biblioteca-conectores/%s.tar.gz\n",nome); removidos++; } else fprintf(stderr,"  [aviso] falha ao remover pacote da biblioteca: %s\n",path); }
    }

    if(removidos==0) printf("  [info] nenhum artefato instalado encontrado para %s\n",nome);
    return 0;
}

static int extrair_pacote(const char *pkg, const char *cwd, const char *nome, int *pulou) {
    if(pulou) *pulou=0;
    char tmp[PATH_MAX*2]; snprintf(tmp,sizeof(tmp),"/tmp/sisc_importar_conector_%ld_%s",(long)getpid(),nome?nome:"pkg");
    rm_rf(tmp);
    if(mkdir_p(tmp,0775)!=0){ perror("importar-conectores: tmp"); return -1; }

    char qpkg[PATH_MAX*4], qtmp[PATH_MAX*4], cmd[PATH_MAX*10];
    if(shell_quote(pkg,qpkg,sizeof(qpkg))!=0 || shell_quote(tmp,qtmp,sizeof(qtmp))!=0){ rm_rf(tmp); return -1; }
    snprintf(cmd,sizeof(cmd),"tar -xzf %s -C %s",qpkg,qtmp);
    if(system(cmd)!=0){ rm_rf(tmp); return -1; }

    char manifesto_rel[PATH_MAX*2]="";
    char *manifesto_txt=NULL;
    if(localizar_manifesto_com_dependencias(tmp,nome,manifesto_rel,sizeof(manifesto_rel),&manifesto_txt)!=0) {
        fprintf(stderr,"  [erro] manifesto do conector sem campo dependencias no pacote\n");
        rm_rf(tmp); return -1;
    }
    char formato_rel[PATH_MAX*2]="";
    if(formato_rel_manifesto(manifesto_txt,nome,formato_rel,sizeof(formato_rel))!=0) {
        free(manifesto_txt);
        fprintf(stderr,"  [erro] formatoConector inseguro no manifesto do pacote\n");
        rm_rf(tmp); return -1;
    }

    ListaDependencias deps;
    if(coletar_dependencias_manifesto(manifesto_txt,&deps)!=0) {
        free(manifesto_txt);
        fprintf(stderr,"  [erro] campo dependencias invalido no manifesto do conector\n");
        rm_rf(tmp); return -1;
    }
    free(manifesto_txt);
    if(validar_dependencias_minimas(&deps,nome,formato_rel)!=0) { deps_free(&deps); rm_rf(tmp); return -1; }

    printf("  fonte segura: %s -> dependencias.arquivos (%d item(ns))\n", manifesto_rel, deps.n);
    if(!perguntar_sobrescrita_dependencias(&deps,cwd)) {
        if(pulou) *pulou=1;
        deps_free(&deps); rm_rf(tmp); return 0;
    }

    char manifesto_local[PATH_MAX*3]; snprintf(manifesto_local,sizeof(manifesto_local),"%s/conectores/%s/%s.json",cwd,nome,nome);
    if(!arquivo_existe(manifesto_local)) snprintf(manifesto_local,sizeof(manifesto_local),"%s/conectores/%s.json",cwd,nome);
    char *manifesto_antigo = arquivo_existe(manifesto_local) ? ler_arquivo_texto(manifesto_local) : NULL;

    int rc=instalar_dependencias(&deps,tmp,cwd);
    if(rc==0) {
        if(limpar_obsoletos_conector(cwd,tmp,manifesto_antigo)!=0) {
            fprintf(stderr,"  [aviso] falha ao remover arquivos obsoletos do conector %s\n",nome);
        }
        int tem_catalogo_modular=0;
        for(int i=0;i<deps.n;i++) if(!strncmp(deps.v[i].destino,"web-api/catalogo-",16) && strcmp(deps.v[i].destino,"web-api/catalogo-mensagens.json")!=0) tem_catalogo_modular=1;
        if(tem_catalogo_modular) {
            int grc=system("../pp --atualizar-catalogo-mensagens >/dev/null 2>&1");
            if(grc==0) printf("  [ok] catalogo agregado atualizado via pp --atualizar-catalogo-mensagens\n");
            else printf("  [aviso] rode pp --atualizar-catalogo-mensagens para regenerar web-api/catalogo-mensagens.json\n");
        }
    }
    free(manifesto_antigo);
    deps_free(&deps);
    rm_rf(tmp);
    return rc;
}

int main(int argc, char **argv) {
    if(argc>1 && (!strcmp(argv[1],"--ajuda") || !strcmp(argv[1],"--help") || !strcmp(argv[1],"-h"))) { uso(); return 0; }
    if(restricao_verificar("importar-conectores")!=0) return 1;
    int todos=0, purge_vestigios=0, purge_biblioteca=0;
    const char *remover_nome=NULL;
    for(int i=1;i<argc;i++) {
        if(!strcmp(argv[i],"--todos") || !strcmp(argv[i],"--todas")) todos=1;
        else if(!strncmp(argv[i],"--remover=",10)) remover_nome=argv[i]+10;
        else if(!strcmp(argv[i],"--purge-vestigios")) purge_vestigios=1;
        else if(!strcmp(argv[i],"--purge-biblioteca")) purge_biblioteca=1;
    }

    char raiz_sisc[PATH_MAX]; if(raiz_sisc_por_executavel(raiz_sisc,sizeof(raiz_sisc))!=0){fprintf(stderr,"importar-conectores: raiz SISC nao localizada.\n");return 1;}
    char cwd[PATH_MAX]; if(!getcwd(cwd,sizeof(cwd))){perror("getcwd");return 1;}

    if(remover_nome) return remover_conector_instalado(cwd,raiz_sisc,remover_nome,purge_vestigios,purge_biblioteca);

    char bib[PATH_MAX*2]; snprintf(bib,sizeof(bib),"%s/core/biblioteca-conectores",raiz_sisc);

    Pacote *pacotes=NULL; int total=0;
    if(listar_pacotes(bib,&pacotes,&total)!=0 || total==0){ fprintf(stderr,"importar-conectores: nenhum pacote encontrado em core/biblioteca-conectores/*.tar.gz\n"); free(pacotes); return 1; }

    int selecionados[512], nsel=0;
    printf("Pacotes disponiveis em core/biblioteca-conectores:\n");
    for(int i=0;i<total;i++) printf("%2d) %s\n",i+1,pacotes[i].nome);
    printf("\n");
    if(todos) { for(int i=0;i<total;i++) selecionados[nsel++]=i; }
    else {
        char entrada[4096]; printf("Digite os numeros dos conectores (ex: 1,3) ou 'todos': "); fflush(stdout);
        if(!fgets(entrada,sizeof(entrada),stdin)){free(pacotes);return 1;}
        entrada[strcspn(entrada,"\r\n")]='\0';
        nsel=parse_selecao(entrada,total,selecionados,512);
        if(nsel<=0){fprintf(stderr,"importar-conectores: selecao invalida.\n");free(pacotes);return 1;}
    }

    int ok=0,pulados=0,erros=0;
    for(int i=0;i<nsel;i++) {
        Pacote *p=&pacotes[selecionados[i]];
        printf("[%d/%d] %s\n",i+1,nsel,p->nome);
        if(validar_pacote(p->caminho)!=0){ fprintf(stderr,"  [erro] pacote invalido: %s\n",p->arquivo); erros++; continue; }
        int pulou_operador=0;
        if(extrair_pacote(p->caminho,cwd,p->nome,&pulou_operador)!=0){ fprintf(stderr,"  [erro] falha ao importar %s\n",p->arquivo); erros++; continue; }
        if(pulou_operador){ printf("  -> pulado\n"); pulados++; continue; }
        printf("  [ok] pacote importado\n"); ok++;
    }

    printf("\nImportacao concluida. Importados: %d; pulados: %d; erros: %d\n",ok,pulados,erros);
    free(pacotes);
    return erros?1:0;
}
