/*
 * ler-web - extrai estrutura de pagina web e materializa via pp --sa em contexto-usuario/
 * Uso: pp --ler-web=<url> [--conta-llm=arquivo.json] [--nome=xxx]
 */

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

typedef struct { int code; char *out; char *err; } Resultado;

static const char *filtro_ajuda_arg_ler(const char *arg)
{
    if (!arg) return NULL;
    if (!strncmp(arg, "--ajuda:", 8) || !strncmp(arg, "--ajuda=", 8)) return arg + 8;
    if (!strncmp(arg, "--help:", 7) || !strncmp(arg, "--help=", 7)) return arg + 7;
    return NULL;
}

static int eh_ajuda_arg_ler(const char *arg)
{
    return arg && (!strcmp(arg, "--help") || !strcmp(arg, "--ajuda") || !strcmp(arg, "-h") || filtro_ajuda_arg_ler(arg));
}

static void uso(const char *filtro)
{
    char exe[PATH_MAX];
    ssize_t n = readlink("/proc/self/exe", exe, sizeof(exe) - 1);
    if (n <= 0) {
        perror("ler-web: readlink");
        return;
    }
    exe[n] = '\0';

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

    char parent[PATH_MAX];
    snprintf(parent, sizeof(parent), "%s", raiz);
    char *base = strrchr(parent, '/');
    if (!base || !base[1]) {
        fprintf(stderr, "ler-web: nao foi possivel resolver a raiz para ajuda\n");
        return;
    }
    *base++ = '\0';

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

    char sa_arg[PATH_MAX * 2];
    snprintf(sa_arg, sizeof(sa_arg), "--sa=%s/ajuda/ler-web.json", base);

    if (chdir(parent) != 0) {
        perror("ler-web: chdir");
        return;
    }

    char proc_arg[PATH_MAX * 2];
    char *args[4];
    args[0] = pp;
    args[1] = sa_arg;
    if (filtro && *filtro) {
        snprintf(proc_arg, sizeof(proc_arg), "--proc=%s", filtro);
        args[2] = proc_arg;
        args[3] = NULL;
    } else {
        args[2] = NULL;
    }
    execv(pp, args);
    perror("ler-web: exec pp --sa=sisc/ajuda/ler-web.json");
}

static int termina_com(const char *s, const char *suf){size_t a=strlen(s),b=strlen(suf);return a>=b&&strcasecmp(s+a-b,suf)==0;}
static int garantir_dir(const char*p){struct stat st;if(stat(p,&st)==0)return S_ISDIR(st.st_mode)?0:-1;return mkdir(p,0775)==0||errno==EEXIST?0:-1;}

static char *ler_fd(int fd)
{ size_t cap=4096,len=0; char*b=malloc(cap); if(!b)return NULL; b[0]='\0'; char tmp[2048]; ssize_t n; while((n=read(fd,tmp,sizeof(tmp)))>0){ if(len+(size_t)n+1>=cap){cap=(len+(size_t)n+1)*2; char*nb=realloc(b,cap); if(!nb){free(b);return NULL;} b=nb;} memcpy(b+len,tmp,(size_t)n); len+=(size_t)n; b[len]='\0'; } return b; }

static char *ler_arquivo(const char *p)
{ FILE *fp=fopen(p,"rb"); if(!fp)return NULL; fseek(fp,0,SEEK_END); long t=ftell(fp); if(t<0||t>20*1024*1024){fclose(fp);return NULL;} fseek(fp,0,SEEK_SET); 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 Resultado run_capture(char *const argv[], const char *cwd)
{ Resultado r={1,strdup(""),strdup("")}; int op[2],ep[2]; if(pipe(op)!=0||pipe(ep)!=0)return r; pid_t pid=fork(); if(pid==0){ if(cwd)chdir(cwd); close(op[0]);close(ep[0]);dup2(op[1],1);dup2(ep[1],2);close(op[1]);close(ep[1]);execvp(argv[0],argv);_exit(127); } close(op[1]);close(ep[1]);free(r.out);free(r.err);r.out=ler_fd(op[0]);r.err=ler_fd(ep[0]);close(op[0]);close(ep[0]);int st=0;waitpid(pid,&st,0);r.code=WIFEXITED(st)?WEXITSTATUS(st):1;if(!r.out)r.out=strdup("");if(!r.err)r.err=strdup("");return r; }

static Resultado run_capture_input(char *const argv[], const char *cwd, const char *input)
{ Resultado r={1,strdup(""),strdup("")}; int op[2],ep[2],ip[2]; if(pipe(op)!=0||pipe(ep)!=0||pipe(ip)!=0)return r; pid_t pid=fork(); if(pid==0){ if(cwd)chdir(cwd); close(op[0]);close(ep[0]);close(ip[1]);dup2(ip[0],0);dup2(op[1],1);dup2(ep[1],2);close(ip[0]);close(op[1]);close(ep[1]);execvp(argv[0],argv);_exit(127); } close(op[1]);close(ep[1]);close(ip[0]); if(input&&*input)write(ip[1],input,strlen(input)); close(ip[1]); free(r.out);free(r.err);r.out=ler_fd(op[0]);r.err=ler_fd(ep[0]);close(op[0]);close(ep[0]);int st=0;waitpid(pid,&st,0);r.code=WIFEXITED(st)?WEXITSTATUS(st):1;if(!r.out)r.out=strdup("");if(!r.err)r.err=strdup("");return r; }

static int caminho_pp(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*s=strrchr(exe,'/'); if(!s)return -1; *s='\0'; s=strrchr(exe,'/'); if(!s)return -1; *s='\0'; s=strrchr(exe,'/'); if(!s)return -1; *s='\0'; snprintf(out,tam,"%s/pp",exe); return 0; }

static char *argj(const char *prefixo, const char *valor)
{ size_t a=strlen(prefixo), b=strlen(valor?valor:""); char*out=malloc(a+b+1); if(!out)return NULL; memcpy(out,prefixo,a); memcpy(out+a,valor?valor:"",b+1); return out; }

static void texto_galho(const char *in, char *out, size_t tam)
{ size_t j=0; int sp=0; if(!out||tam==0)return; out[0]='\0'; for(const char*p=in?in:"";*p&&j+1<tam;p++){ unsigned char c=(unsigned char)*p; if(isspace(c)||c<32){ if(!sp&&j+1<tam){out[j++]=' ';sp=1;} continue; } out[j++]=(char)c; sp=0; } while(j>0&&out[j-1]==' ')j--; if(j+4<tam && in && strlen(in)>j){out[j++]=' ';out[j++]='.';out[j++]='.';out[j++]='.';} out[j]='\0'; }

static int sa_adicionar(const char *pp, const char *arvore, const char *nome, const char *em, const char *mm, int confirmar)
{ char *sa=argj("--sa=",arvore), *ag=argj("--ag=",nome), *emarg=em&&*em?argj("--em=",em):NULL, *mmarg=mm&&*mm?argj("--mm=",mm):NULL; if(!sa||!ag||(em&&*em&&!emarg)||(mm&&*mm&&!mmarg)){free(sa);free(ag);free(emarg);free(mmarg);return 1;} char *args[7]; int p=0; args[p++]=(char*)pp; args[p++]=sa; args[p++]=ag; if(emarg)args[p++]=emarg; if(mmarg)args[p++]=mmarg; args[p]=NULL; Resultado r=run_capture_input(args,NULL,confirmar?"s\n":NULL); int code=r.code; if(code!=0){fprintf(stderr,"ler-web: falha ao materializar com pp --sa (%s)\n",nome?nome:""); if(r.err&&*r.err)fprintf(stderr,"%s",r.err); if(r.out&&*r.out)fprintf(stderr,"%s",r.out);} free(r.out);free(r.err);free(sa);free(ag);free(emarg);free(mmarg); return code; }

static char *json_get_string(const char *json, const char *key);

static int materializar_resultado_sa(const char *path, const char *url, const char *titulo, const char *conta, const char *json_estr)
{ char pp[PATH_MAX*2]; if(caminho_pp(pp,sizeof(pp))!=0){fprintf(stderr,"ler-web: nao foi possivel localizar pp para usar --sa\n");return 1;} unlink(path); char tcurto[256], raiz[320], meta[512]; texto_galho(titulo&&*titulo?titulo:"pagina",tcurto,sizeof(tcurto)); snprintf(raiz,sizeof(raiz),"Leitura web: %s",tcurto[0]?tcurto:"pagina"); if(sa_adicionar(pp,path,raiz,NULL,json_estr&&*json_estr?json_estr:"{}",1)!=0)return 1; snprintf(meta,sizeof(meta),"URL: %s",url?url:""); if(sa_adicionar(pp,path,meta,"1",NULL,0)!=0)return 1; snprintf(meta,sizeof(meta),"Título: %s",titulo?titulo:""); if(sa_adicionar(pp,path,meta,"1",NULL,0)!=0)return 1; snprintf(meta,sizeof(meta),"Conta LLM: %s",conta?conta:""); if(sa_adicionar(pp,path,meta,"1",NULL,0)!=0)return 1; char *res=json_get_string(json_estr?json_estr:"{}","resumo"); if(res&&*res){ char rcurto[512]; texto_galho(res,rcurto,sizeof(rcurto)); snprintf(meta,sizeof(meta),"Resumo: %s",rcurto); if(sa_adicionar(pp,path,meta,"1",NULL,0)!=0){free(res);return 1;} } free(res); if(sa_adicionar(pp,path,"Estrutura JSON completa anexada no mm do item 1","1",NULL,0)!=0)return 1; return 0; }

static int normalizar_conta(const char *in, char *out, size_t tam)
{ if(!in||!*in||!out||tam<8)return -1; while(isspace((unsigned char)*in))in++; if(strchr(in,'/')||strchr(in,'\\')||strstr(in,".."))return -1; snprintf(out,tam,"%s",in); size_t n=strlen(out); while(n>0&&isspace((unsigned char)out[n-1]))out[--n]='\0'; if(n==0)return -1; if(!termina_com(out,".json")){ if(n+5>=tam)return -1; strcat(out,".json"); } for(const char*p=out;*p;p++) if(!(isalnum((unsigned char)*p)||*p=='.'||*p=='_'||*p=='-'))return -1; return 0; }

static char *json_get_string(const char *json, const char *key)
{ char pat[128]; snprintf(pat,sizeof(pat),"\"%s\"",key); const char*p=strstr(json,pat); if(!p)return NULL; p=strchr(p+strlen(pat),':'); if(!p)return NULL; p++; while(*p&&isspace((unsigned char)*p))p++; if(*p!='"')return NULL; p++; char*out=malloc(strlen(p)+1); if(!out)return NULL; size_t j=0; while(*p&&*p!='"'){ if(*p=='\\'&&p[1])p++; out[j++]=*p++; } out[j]='\0'; return out; }

static char *json_get_string_ate(const char *json,const char *key,const char *limite)
{ char pat[128]; snprintf(pat,sizeof(pat),"\"%s\"",key); const char*p=strstr(json,pat); if(!p||(limite&&p>=limite))return NULL; p=strchr(p+strlen(pat),':'); if(!p||(limite&&p>=limite))return NULL; p++; while(*p&&isspace((unsigned char)*p))p++; if((limite&&p>=limite)||*p!='"')return NULL; p++; char*out=malloc((size_t)((limite&&limite>p)?(limite-p+1):(strlen(p)+1))); if(!out)return NULL; size_t j=0; while(*p&&*p!='"'&&(!limite||p<limite)){ if(*p=='\\'&&p[1]&&(!limite||p+1<limite))p++; out[j++]=*p++; } out[j]='\0'; return out; }

static char *mapa_comandos_primeiro_filho_nome(const char*obj)
{ const char*f=strstr(obj,"\"filhos\""); if(!f)return NULL; const char*a=strchr(f,'['); if(!a)return NULL; const char*fe=strchr(a,']'); const char*n=strstr(a,"\"nome\""); if(!n||!fe||n>fe)return NULL; return json_get_string_ate(n,"nome",fe); }

static char *mapa_comandos_sa_conta(const char *json,const char *comando)
{ char*tipo=json_get_string(json,"tipo"); int eh=tipo&&!strcmp(tipo,"arvore-sa"); free(tipo); if(!eh||!comando||!*comando)return NULL; char alt[PATH_MAX]; snprintf(alt,sizeof(alt),"%s%s",strncmp(comando,"--",2)?"--":"",comando); const char*p=json; while((p=strstr(p,"\"nome\""))){ const char*prox=strstr(p+6,"\"nome\""); char*nome=json_get_string_ate(p,"nome",prox); int achou=nome&&(!strcmp(nome,comando)||!strcmp(nome,alt)); free(nome); if(achou){ char*v=json_get_string_ate(p,"mm",prox); if(!v)v=json_get_string_ate(p,"valor",prox); if(!v)v=mapa_comandos_primeiro_filho_nome(p); return v; } p+=6; } return NULL; }

static void conta_mapeada(const char *comando, char *out, size_t tam)
{ snprintf(out,tam,"llm.json");
  /* Passo 1: config local do sistema atual */
  char raiz[PATH_MAX];
  if(!getcwd(raiz,sizeof(raiz))) return;
  char mapa[PATH_MAX*2];
  snprintf(mapa,sizeof(mapa),"%s/config/mapa-comandos.json",raiz);
  char*j=ler_arquivo(mapa);
  /* Passo 2: fallback core/config/ */
  if(!j){ char*s=strrchr(raiz,'/'); if(s&&s!=raiz){ *s='\0';
    snprintf(mapa,sizeof(mapa),"%s/core/config/mapa-comandos.json",raiz);
    j=ler_arquivo(mapa); }}
  /* Passo 3: fallback pelo executavel */
  if(!j){ char exe[PATH_MAX]; ssize_t n=readlink("/proc/self/exe",exe,sizeof(exe)-1); if(n<=0)return; exe[n]='\0'; char*s2=strrchr(exe,'/'); if(s2)*s2='\0'; s2=strrchr(exe,'/'); if(s2)*s2='\0';
    snprintf(mapa,sizeof(mapa),"%s/config/mapa-comandos.json",exe); j=ler_arquivo(mapa); }
  if(!j)return;
  char *v=mapa_comandos_sa_conta(j,comando); if(!v)v=json_get_string(j,comando); if(v){normalizar_conta(v,out,tam);free(v);} free(j); }

static int ip_publico(const char *ip)
{ unsigned char b[16]; int fam=strchr(ip,':')?AF_INET6:AF_INET; if(inet_pton(fam,ip,b)!=1)return 0; if(fam==AF_INET){ unsigned int x=((unsigned int)b[0]<<24)|((unsigned int)b[1]<<16)|((unsigned int)b[2]<<8)|b[3]; if((x>>24)==10||(x>>20)==0xAC1||(x>>16)==0xC0A8||(x>>24)==127||(x>>24)==0||(x>>24)>=224)return 0; } else { if((b[0]&0xFE)==0xFC||(b[0]==0xFE&&(b[1]&0xC0)==0x80)||!memcmp(b,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1",16))return 0; } return 1; }

static int validar_url(const char *url)
{ if(strncmp(url,"http://",7)!=0&&strncmp(url,"https://",8)!=0){fprintf(stderr,"ler-web: URL invalida\n");return 0;} const char *h=strstr(url,"://"); h+=3; char host[512]; size_t i=0; while(h[i]&&h[i]!='/'&&h[i]!=':'&&h[i]!='?'&&i+1<sizeof(host)){host[i]=h[i];i++;} host[i]='\0'; if(!host[0]){fprintf(stderr,"ler-web: host nao identificado\n");return 0;} if(!strcasecmp(host,"localhost")||!strcmp(host,"127.0.0.1")||!strcmp(host,"::1")){fprintf(stderr,"ler-web: localhost bloqueado\n");return 0;} struct addrinfo hints={0},*res=NULL; hints.ai_socktype=SOCK_STREAM; if(getaddrinfo(host,NULL,&hints,&res)==0){ for(struct addrinfo*p=res;p;p=p->ai_next){ char ip[INET6_ADDRSTRLEN]; void*addr=p->ai_family==AF_INET?(void*)&((struct sockaddr_in*)p->ai_addr)->sin_addr:(void*)&((struct sockaddr_in6*)p->ai_addr)->sin6_addr; if(inet_ntop(p->ai_family,addr,ip,sizeof(ip))&&!ip_publico(ip)){freeaddrinfo(res);fprintf(stderr,"ler-web: IP privado bloqueado\n");return 0;} } freeaddrinfo(res); } return 1; }

static char *extrair_titulo(const char *html)
{ const char*p=strcasestr(html,"<title"); if(!p)return strdup(""); p=strchr(p,'>'); if(!p)return strdup(""); p++; const char*q=strcasestr(p,"</title>"); if(!q)return strdup(""); size_t n=(size_t)(q-p); char*out=malloc(n+1); memcpy(out,p,n); out[n]='\0'; for(char*x=out;*x;x++) if(*x=='\n'||*x=='\r'||*x=='\t')*x=' '; return out; }

static char *strip_bloco(const char *html, const char *tag)
{
    size_t len = strlen(html), tlen = strlen(tag);
    char *out = malloc(len + 1);
    if (!out) return NULL;
    size_t j = 0;
    for (size_t i = 0; i < len; i++) {
        if (html[i] == '<' && strncasecmp(html + i + 1, tag, tlen) == 0 &&
            (html[i + 1 + tlen] == '>' || isspace((unsigned char)html[i + 1 + tlen]))) {
            while (i < len && html[i] != '>') i++;
            int depth = 1;
            while (i < len && depth > 0) {
                i++;
                if (html[i] == '<' && html[i+1] == '/' && strncasecmp(html + i + 2, tag, tlen) == 0 && html[i+2+tlen] == '>') {
                    depth--;
                    if (!depth) { i += 2 + tlen; break; }
                }
                else if (html[i] == '<' && strncasecmp(html + i + 1, tag, tlen) == 0 &&
                         (html[i + 1 + tlen] == '>' || isspace((unsigned char)html[i + 1 + tlen]))) {
                    depth++;
                }
            }
            continue;
        }
        out[j++] = html[i];
    }
    out[j] = '\0';
    return out;
}
static char *limpar_texto(const char *html, int max, int *trunc)
{ char*s1=strip_bloco(html,"style"); char*s2=strip_bloco(s1?s1:html,"script"); char*s3=strip_bloco(s2?s2:(s1?s1:html),"noscript"); free(s1);free(s2); const char*src=s3?s3:html; size_t cap=8192,len=0; char*out=malloc(cap); int tag=0,sp=1; *trunc=0; if(!out){free(s3);return NULL;} for(const char*p=src;*p&&len<(size_t)max;p++){ if(*p=='<'){tag=1;if(!sp){out[len++]=' ';sp=1;}continue;} if(tag){if(*p=='>')tag=0;continue;} unsigned char c=(unsigned char)*p; if(isspace(c)){if(!sp){out[len++]=' ';sp=1;}}else{if(len+2>=cap){cap*=2;out=realloc(out,cap);if(!out){free(s3);return NULL;}}out[len++]=(char)c;sp=0;} } if(src[len])*trunc=1; out[len]='\0'; free(s3); return out; }

static char *json_escape(const char *s)
{ size_t cap=strlen(s)*2+16,len=0; char*out=malloc(cap); for(const char*p=s;*p;p++){ if(len+8>=cap){cap*=2;out=realloc(out,cap);} switch(*p){case '"':out[len++]='\\';out[len++]='"';break;case '\\':out[len++]='\\';out[len++]='\\';break;case '\n':out[len++]='\\';out[len++]='n';break;case '\r':break;case '\t':out[len++]=' ';break;default:out[len++]=*p;break;} } out[len]='\0'; return out; }

static void nome_seguro(const char *in, char *out, size_t tam)
{ size_t j=0; for(size_t i=0;in&&in[i]&&j+1<tam&&j<60;i++){ unsigned char c=(unsigned char)in[i]; out[j++]=(isalnum(c)||c=='_'||c=='-')?(char)c:'_'; } if(j==0)snprintf(out,tam,"pagina"); else{while(j>0&&out[j-1]=='_')j--;out[j]='\0';} }

int main(int argc, char **argv)
{
    for(int i=1;i<argc;i++) if(eh_ajuda_arg_ler(argv[i])){uso(filtro_ajuda_arg_ler(argv[i]));return 0;}
    if (restricao_verificar("ler-web") != 0) return 1; 
    char url[4096]="", conta[PATH_MAX]="", nome[PATH_MAX]="";
    for(int i=1;i<argc;i++){ if(eh_ajuda_arg_ler(argv[i])){uso(filtro_ajuda_arg_ler(argv[i]));return 0;} if(strncmp(argv[i],"--conta-llm=",12)==0){ if(normalizar_conta(argv[i]+12,conta,sizeof(conta))!=0){fprintf(stderr,"ler-web: conta LLM invalida\n");return 1;} continue;} if(strncmp(argv[i],"--nome=",7)==0){ nome_seguro(argv[i]+7,nome,sizeof(nome)); if(!nome[0]){fprintf(stderr,"ler-web: nome invalido\n");return 1;} continue;} if(argv[i][0]=='-'){uso(NULL);return 1;} if(url[0]){fprintf(stderr,"ler-web: informe apenas uma URL.\n");return 1;} snprintf(url,sizeof(url),"%s",argv[i]); }
    if(!url[0]){uso(NULL);return 1;} if(!conta[0]){const char*e=getenv("SISC_CONTA_LLM"); if(e&&*e) normalizar_conta(e,conta,sizeof(conta));} if(!conta[0]) conta_mapeada("ler-web",conta,sizeof(conta));
    if(!validar_url(url)) return 1;
    printf("Baixando: %s\n",url);
    char tmp[]="/tmp/sisc_ler_web_XXXXXX"; int fd=mkstemp(tmp); if(fd<0){perror("mkstemp");return 1;} close(fd);
    char *curlv[]={"curl","-L","--max-redirs","5","--max-time","30","--connect-timeout","10","-A","Mozilla/5.0 (compatible; sisc-ler-web/1.0)","--proto","=http,https","--proto-redir","=http,https","-sS","-o",tmp,"-w","%{http_code}\n%{content_type}\n%{url_effective}",url,NULL};
    Resultado cr=run_capture(curlv,NULL); if(cr.code!=0){fprintf(stderr,"ler-web: erro ao acessar URL: %s\n",cr.err);unlink(tmp);return 1;}
    char *html=ler_arquivo(tmp); unlink(tmp); if(!html||!*html){fprintf(stderr,"ler-web: pagina retornou conteudo vazio\n");return 1;}
    int http=atoi(cr.out); if(http<200||http>=400){fprintf(stderr,"ler-web: HTTP %d\n",http);return 1;}
    printf("  HTTP %d | %zu bytes\n",http,strlen(html));
    char *titulo=extrair_titulo(html); printf("  Titulo: %s\n",titulo&&*titulo?titulo:"(sem titulo)");
    int trunc=0; char *texto=limpar_texto(html,8000,&trunc); printf("  Texto: %zu caracteres%s\n",strlen(texto),trunc?" (truncado)":"");
    printf("\nAnalisando estrutura com IA usando %s...\n",conta);
    char *te=json_escape(texto); size_t plen=strlen(te)+3000; char *prompt=malloc(plen); snprintf(prompt,plen,"Voce eh um arquiteto de informacao. Analise o conteudo textual da pagina web abaixo e extraia APENAS sua ESTRUTURA em JSON: secoes, navegacao, paginas, funcionalidades e um resumo.\n\nConteudo:\n---\n%s\n---\n\nResponda SOMENTE com JSON valido com os campos: tipo, titulo, secoes (array de {nome, descricao, subsecoes}), navegacao (array de {item, url, filhos}), funcionalidades (array de strings), paginasDetectadas (array de {titulo, url, tipo}), resumo.",te);
    char bin[PATH_MAX*2]; ssize_t l=readlink("/proc/self/exe",bin,sizeof(bin)-1); if(l>0){bin[l]='\0';char*s=strrchr(bin,'/');if(s)strcpy(s+1,"parallm");} else snprintf(bin,sizeof(bin),"parallm");
    char carg[PATH_MAX+32]; snprintf(carg,sizeof(carg),"--conta-llm=%s",conta); char *pargv[]={bin,prompt,"--sem-heb",carg,NULL};
    Resultado pr=run_capture(pargv,NULL); if(pr.code!=0||!pr.out||!*pr.out){fprintf(stderr,"ler-web: erro ao chamar LLM\n");return 1;}
    printf("  Estrutura extraida.\n");
    char raiz[PATH_MAX]; getcwd(raiz,sizeof(raiz)); char dir[PATH_MAX*2]; snprintf(dir,sizeof(dir),"%s/contexto-usuario",raiz); if(garantir_dir(dir)!=0){fprintf(stderr,"ler-web: erro ao criar contexto-usuario\n");return 1;}
    time_t now=time(NULL); char stamp[32]; if(nome[0])snprintf(stamp,sizeof(stamp),"%s",nome); else{struct tm tmv;gmtime_r(&now,&tmv);strftime(stamp,sizeof(stamp),"%Y%m%d-%H%M%S",&tmv);}
    char safe[80]; nome_seguro(titulo&&*titulo?titulo:"pagina",safe,sizeof(safe)); char path[PATH_MAX*2]; snprintf(path,sizeof(path),"%s/ler-web-%s-%s.json",dir,stamp,safe);
    /* Extrai soh o JSON da resposta do LLM (ignora texto fora de {}) */
    char *json_estr=pr.out; char *abre=strchr(json_estr,'{'); char *fecha=abre?strrchr(abre,'}'):NULL;
    if(abre&&fecha){fecha[1]='\0';json_estr=abre;}else json_estr="{}";
    if(materializar_resultado_sa(path,url,titulo?titulo:"",conta,json_estr)!=0)return 1;
    const char *base=strrchr(path,'/'); printf("\nSalvo via pp --sa em: contexto-usuario/%s\n",base?base+1:path);
    printf("Abra com: pp --sa=contexto-usuario/%s\n",base?base+1:path);
    return 0;
}
