/*
 * setar-configuracoes-llm - Editor interativo de contas LLM
 * Uso: pp --setar-configuracoes-llm
 *
 * Lista as contas, edita os campos da conta escolhida.
 * ENTER preserva; espaco remove; novo valor substitui.
 */

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

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

typedef struct Json Json;
typedef struct { Json **itens; int total, cap; } JArray;
typedef struct { char **chaves; Json **valores; int total, cap; } JObject;

struct Json {
    JTipo tipo;
    union {
        int booleano;
        char *numero;
        char *string;
        JArray array;
        JObject objeto;
    } v;
};

typedef struct { char *buf; size_t len, cap; } Str;
typedef struct { const char *p; } Parser;

typedef struct { char **paths; Json **vals; int total, cap; } Flat;

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

static void str_init(Str *s) { s->cap = 4096; s->len = 0; s->buf = malloc(s->cap); if (s->buf) s->buf[0] = '\0'; }
static int str_addn(Str *s, const char *txt, size_t n)
{
    if (s->len + n + 1 > s->cap) {
        while (s->len + n + 1 > s->cap) s->cap *= 2;
        char *novo = realloc(s->buf, s->cap);
        if (!novo) return -1;
        s->buf = novo;
    }
    memcpy(s->buf + s->len, txt, n);
    s->len += n;
    s->buf[s->len] = '\0';
    return 0;
}
static int str_add(Str *s, const char *txt) { return str_addn(s, txt, strlen(txt)); }
static void str_indent(Str *s, int n) { for (int i = 0; i < n; i++) str_add(s, " "); }

static Json *j_new(JTipo t) { Json *j = calloc(1, sizeof(Json)); if (j) j->tipo = t; return j; }
static Json *j_string(const char *s) { Json *j = j_new(J_STRING); if (j) j->v.string = strdup(s ? s : ""); return j; }
static Json *j_number(const char *s) { Json *j = j_new(J_NUMBER); if (j) j->v.numero = strdup(s ? s : "0"); return j; }
static Json *j_bool(int b) { Json *j = j_new(J_BOOL); if (j) j->v.booleano = b; return j; }
static Json *j_null(void) { return j_new(J_NULL); }
static Json *j_object(void) { return j_new(J_OBJECT); }
static Json *j_array(void) { return j_new(J_ARRAY); }

static void j_free(Json *j)
{
    if (!j) return;
    if (j->tipo == J_STRING) free(j->v.string);
    else if (j->tipo == J_NUMBER) free(j->v.numero);
    else if (j->tipo == J_ARRAY) { for (int i = 0; i < j->v.array.total; i++) j_free(j->v.array.itens[i]); free(j->v.array.itens); }
    else if (j->tipo == J_OBJECT) { for (int i = 0; i < j->v.objeto.total; i++) { free(j->v.objeto.chaves[i]); j_free(j->v.objeto.valores[i]); } free(j->v.objeto.chaves); free(j->v.objeto.valores); }
    free(j);
}

static int obj_idx(Json *obj, const char *chave)
{ if (!obj || obj->tipo != J_OBJECT) return -1; for (int i = 0; i < obj->v.objeto.total; i++) if (strcmp(obj->v.objeto.chaves[i], chave) == 0) return i; return -1; }

static Json *obj_get(Json *obj, const char *chave) { int i = obj_idx(obj, chave); return i >= 0 ? obj->v.objeto.valores[i] : NULL; }

static int obj_set(Json *obj, const char *chave, Json *valor)
{
    if (!obj || obj->tipo != J_OBJECT) return -1;
    int i = obj_idx(obj, chave);
    if (i >= 0) { j_free(obj->v.objeto.valores[i]); obj->v.objeto.valores[i] = valor; return 0; }
    if (obj->v.objeto.total >= obj->v.objeto.cap) {
        int nc = obj->v.objeto.cap ? obj->v.objeto.cap * 2 : 8;
        char **ks = realloc(obj->v.objeto.chaves, (size_t)nc * sizeof(char *));
        Json **vs = realloc(obj->v.objeto.valores, (size_t)nc * sizeof(Json *));
        if (!ks || !vs) return -1;
        obj->v.objeto.chaves = ks; obj->v.objeto.valores = vs; obj->v.objeto.cap = nc;
    }
    int n = obj->v.objeto.total++;
    obj->v.objeto.chaves[n] = strdup(chave);
    obj->v.objeto.valores[n] = valor;
    return obj->v.objeto.chaves[n] ? 0 : -1;
}

static void obj_unset(Json *obj, const char *chave)
{
    int i = obj_idx(obj, chave); if (i < 0) return;
    free(obj->v.objeto.chaves[i]); j_free(obj->v.objeto.valores[i]);
    memmove(&obj->v.objeto.chaves[i], &obj->v.objeto.chaves[i+1], (size_t)(obj->v.objeto.total-i-1)*sizeof(char*));
    memmove(&obj->v.objeto.valores[i], &obj->v.objeto.valores[i+1], (size_t)(obj->v.objeto.total-i-1)*sizeof(Json*));
    obj->v.objeto.total--;
}

static int arr_add(Json *arr, Json *valor)
{
    if (!arr || arr->tipo != J_ARRAY) return -1;
    if (arr->v.array.total >= arr->v.array.cap) {
        int nc = arr->v.array.cap ? arr->v.array.cap * 2 : 8;
        Json **it = realloc(arr->v.array.itens, (size_t)nc * sizeof(Json *));
        if (!it) return -1;
        arr->v.array.itens = it; arr->v.array.cap = nc;
    }
    arr->v.array.itens[arr->v.array.total++] = valor;
    return 0;
}

static void ps_ws(Parser *ps) { while (*ps->p && isspace((unsigned char)*ps->p)) ps->p++; }
static int ps_chr(Parser *ps, char c) { ps_ws(ps); if (*ps->p == c) { ps->p++; return 1; } return 0; }

static char *parse_string(Parser *ps)
{
    ps_ws(ps); if (*ps->p != '"') return NULL; ps->p++;
    Str s; str_init(&s);
    while (*ps->p && *ps->p != '"') {
        char c = *ps->p++;
        if (c == '\\' && *ps->p) { c = *ps->p++; if (c == 'n') c = '\n'; else if (c == 'r') c = '\r'; else if (c == 't') c = '\t'; }
        str_addn(&s, &c, 1);
    }
    if (*ps->p == '"') ps->p++;
    return s.buf;
}

static Json *parse_value(Parser *ps);

static Json *parse_object(Parser *ps)
{
    if (!ps_chr(ps, '{')) return NULL;
    Json *obj = j_object(); ps_ws(ps); if (ps_chr(ps, '}')) return obj;
    while (*ps->p) {
        char *k = parse_string(ps); if (!k) { j_free(obj); return NULL; }
        if (!ps_chr(ps, ':')) { free(k); j_free(obj); return NULL; }
        Json *v = parse_value(ps); if (!v) { free(k); j_free(obj); return NULL; }
        obj_set(obj, k, v); free(k);
        ps_ws(ps); if (ps_chr(ps, '}')) break; if (!ps_chr(ps, ',')) { j_free(obj); return NULL; }
    }
    return obj;
}

static Json *parse_array(Parser *ps)
{
    if (!ps_chr(ps, '[')) return NULL;
    Json *arr = j_array(); ps_ws(ps); if (ps_chr(ps, ']')) return arr;
    while (*ps->p) {
        Json *v = parse_value(ps); if (!v) { j_free(arr); return NULL; }
        arr_add(arr, v);
        ps_ws(ps); if (ps_chr(ps, ']')) break; if (!ps_chr(ps, ',')) { j_free(arr); return NULL; }
    }
    return arr;
}

static Json *parse_number(Parser *ps)
{
    ps_ws(ps); const char *ini = ps->p;
    if (*ps->p == '-') ps->p++;
    while (isdigit((unsigned char)*ps->p)) ps->p++;
    if (*ps->p == '.') { ps->p++; while (isdigit((unsigned char)*ps->p)) ps->p++; }
    if (*ps->p == 'e' || *ps->p == 'E') { ps->p++; if (*ps->p == '-' || *ps->p == '+') ps->p++; while (isdigit((unsigned char)*ps->p)) ps->p++; }
    size_t n = (size_t)(ps->p - ini); char *tmp = malloc(n+1); memcpy(tmp, ini, n); tmp[n]='\0';
    Json *j = j_number(tmp); free(tmp); return j;
}

static Json *parse_value(Parser *ps)
{
    ps_ws(ps);
    if (*ps->p == '{') return parse_object(ps);
    if (*ps->p == '[') return parse_array(ps);
    if (*ps->p == '"') { char *s = parse_string(ps); Json *j = j_string(s); free(s); return j; }
    if (strncmp(ps->p, "true", 4) == 0) { ps->p += 4; return j_bool(1); }
    if (strncmp(ps->p, "false", 5) == 0) { ps->p += 5; return j_bool(0); }
    if (strncmp(ps->p, "null", 4) == 0) { ps->p += 4; return j_null(); }
    return parse_number(ps);
}

static Json *json_parse(const char *txt) { Parser ps = { txt }; return parse_value(&ps); }

static void json_write(Str *s, Json *j, int ind, int compact);
static void json_write_string(Str *s, const char *v)
{
    str_add(s, "\"");
    for (const char *p = v ? v : ""; *p; p++) {
        if (*p == '"') str_add(s, "\\\""); else if (*p == '\\') str_add(s, "\\\\");
        else if (*p == '\n') str_add(s, "\\n"); else if (*p == '\r') str_add(s, "\\r");
        else if (*p == '\t') str_add(s, "\\t"); else str_addn(s, p, 1);
    }
    str_add(s, "\"");
}

static void json_write(Str *s, Json *j, int ind, int compact)
{
    if (!j) { str_add(s, "null"); return; }
    switch (j->tipo) {
        case J_NULL: str_add(s, "null"); break;
        case J_BOOL: str_add(s, j->v.booleano ? "true" : "false"); break;
        case J_NUMBER: str_add(s, j->v.numero); break;
        case J_STRING: json_write_string(s, j->v.string); break;
        case J_ARRAY:
            str_add(s, "["); for (int i = 0; i < j->v.array.total; i++) { if (i) str_add(s, compact ? "," : ", "); json_write(s, j->v.array.itens[i], ind, compact); }
            str_add(s, "]"); break;
        case J_OBJECT:
            str_add(s, "{"); if (!compact && j->v.objeto.total) str_add(s, "\n");
            for (int i = 0; i < j->v.objeto.total; i++) {
                if (!compact) str_indent(s, ind + 4);
                json_write_string(s, j->v.objeto.chaves[i]); str_add(s, compact ? ":" : ": ");
                json_write(s, j->v.objeto.valores[i], ind + 4, compact);
                if (i < j->v.objeto.total - 1) str_add(s, ",");
                if (!compact) str_add(s, "\n");
            }
            if (!compact && j->v.objeto.total) str_indent(s, ind);
            str_add(s, "}"); break;
    }
}

static char *json_to_string(Json *j, int compact) { Str s; str_init(&s); json_write(&s, j, 0, compact); if (!compact) str_add(&s, "\n"); return s.buf; }

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){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 int escrever_arquivo(const char *p, const char *c)
{
    FILE *fp=fopen(p,"wb"); if(!fp)return -1; size_t l=strlen(c), n=fwrite(c,1,l,fp); fclose(fp); return n==l?0:-1;
}
static int raiz_projeto(char *raiz, size_t tam) { return getcwd(raiz, tam) ? 0 : -1; }

static int eh_diretorio(const char *c){struct stat st;return stat(c,&st)==0&&S_ISDIR(st.st_mode);}
static void resolver_subdir_sistema(const char *raiz, const char *nome, char *out, size_t tam)
{
    char d[PATH_MAX*2];
    /* contas-llm deve ser sempre local ao sistema atual, sem fallback para biblioteca. */
    snprintf(d,sizeof(d),"%s/%s",raiz,nome);if(eh_diretorio(d)||strcmp(nome,"contas-llm")==0){snprintf(out,tam,"%s",d);return;}
    /* Passo 2: biblioteca central */
    char rs[PATH_MAX];snprintf(rs,sizeof(rs),"%s",raiz);
    char*sl=strrchr(rs,'/');if(sl&&sl!=rs){*sl='\0';
        snprintf(d,sizeof(d),"%s/core/biblioteca-%s",rs,nome);
        if(eh_diretorio(d)){snprintf(out,tam,"%s",d);return;}}
    snprintf(out,tam,"%s/%s",raiz,nome);
}
static int termina_com(const char *s, const char *x){size_t a=strlen(s),b=strlen(x);return a>=b&&strcasecmp(s+a-b,x)==0;}

static int cmp_conta(const void *a,const void *b){return strcasecmp(((const ContaLLM*)a)->nome,((const ContaLLM*)b)->nome);}
static int listar_contas(const char *dir, ContaLLM **out, int *tot)
{
    DIR *d=opendir(dir);if(!d)return-1;int cap=32,n=0;ContaLLM*v=calloc((size_t)cap,sizeof(ContaLLM));
    struct dirent*e;while((e=readdir(d))){const char*nm=e->d_name;if(nm[0]=='.'||!termina_com(nm,".json"))continue;
        if(n>=cap){cap*=2;v=realloc(v,(size_t)cap*sizeof(ContaLLM));}
        snprintf(v[n].arquivo,sizeof(v[n].arquivo),"%s",nm);snprintf(v[n].nome,sizeof(v[n].nome),"%s",nm);v[n].nome[strlen(v[n].nome)-5]='\0';
        snprintf(v[n].caminho,sizeof(v[n].caminho),"%s/%s",dir,nm);n++;}
    closedir(d);qsort(v,(size_t)n,sizeof(ContaLLM),cmp_conta);*out=v;*tot=n;return 0;
}
static int ler_numero(int total)
{
    char linha[128];printf("Digite o numero: ");fflush(stdout);if(!fgets(linha,sizeof(linha),stdin))return-1;
    char*p=linha;while(isspace((unsigned char)*p))p++;if(!isdigit((unsigned char)*p))return-1;
    int n=atoi(p);return n>=1&&n<=total?n:-1;
}

static const char *j_tipo(Json *j)
{
    if(!j)return"null";
    switch(j->tipo){case J_STRING:return"texto";case J_NUMBER:return strchr(j->v.numero,'.')?"decimal":"inteiro";case J_BOOL:return"booleano";case J_NULL:return"null";case J_ARRAY:return"lista JSON";case J_OBJECT:return"objeto JSON";}
    return"valor";
}
static char *valor_linha(Json *j) { return json_to_string(j, 1); }

static void flat_add(Flat *f, const char *path, Json *v)
{
    if(f->total>=f->cap){int nc=f->cap?f->cap*2:32;f->paths=realloc(f->paths,(size_t)nc*sizeof(char*));f->vals=realloc(f->vals,(size_t)nc*sizeof(Json*));f->cap=nc;}
    f->paths[f->total]=strdup(path);f->vals[f->total]=v;f->total++;
}
static void flatten(Json *j, const char *prefix, Flat *f)
{
    if(!j||j->tipo!=J_OBJECT)return;
    for(int i=0;i<j->v.objeto.total;i++){
        char path[1024];snprintf(path,sizeof(path),"%s%s%s",prefix&&*prefix?prefix:"",prefix&&*prefix?".":"",j->v.objeto.chaves[i]);
        Json *v=j->v.objeto.valores[i];
        if(v->tipo==J_OBJECT&&v->v.objeto.total>0)flatten(v,path,f);else flat_add(f,path,v);
    }
}
static void flat_free(Flat *f){for(int i=0;i<f->total;i++)free(f->paths[i]);free(f->paths);free(f->vals);}

static Json *parse_cli_valor(const char *s, Json *ant)
{
    if(strcmp(s,"null")==0)return j_null();
    if(strcmp(s,"true")==0)return j_bool(1);
    if(strcmp(s,"false")==0)return j_bool(0);
    if(*s=='{'||*s=='[')return json_parse(s);
    if(ant&&ant->tipo==J_NUMBER)return j_number(s);
    if(!ant&&(*s=='-'||isdigit((unsigned char)*s))){int ok=1;for(const char*p=s;*p;p++)if(!isdigit((unsigned char)*p)&&*p!='-'&&*p!='.'&&*p!='e'&&*p!='E'&&*p!='+')ok=0;if(ok)return j_number(s);}
    return j_string(s);
}

static char **split_path(const char *path,int*n){char*dup=strdup(path);int cap=8,total=0;char**p=malloc((size_t)cap*sizeof(char*));char*save=NULL,*tok=strtok_r(dup,".",&save);while(tok){if(total>=cap){cap*=2;p=realloc(p,(size_t)cap*sizeof(char*));}p[total++]=strdup(tok);tok=strtok_r(NULL,".",&save);}free(dup);*n=total;return p;}
static void split_free(char **p,int n){for(int i=0;i<n;i++)free(p[i]);free(p);}
static void set_path(Json *root,const char*path,Json*val){int n=0;char**parts=split_path(path,&n);if(n==0){split_free(parts,n);return;}Json*cur=root;for(int i=0;i<n-1;i++){Json*nx=obj_get(cur,parts[i]);if(!nx||nx->tipo!=J_OBJECT){nx=j_object();obj_set(cur,parts[i],nx);}cur=nx;}obj_set(cur,parts[n-1],val);split_free(parts,n);}
static void unset_path(Json *root,const char*path){int n=0;char**parts=split_path(path,&n);if(n==0){split_free(parts,n);return;}Json*cur=root;for(int i=0;i<n-1;i++){cur=obj_get(cur,parts[i]);if(!cur||cur->tipo!=J_OBJECT){split_free(parts,n);return;}}obj_unset(cur,parts[n-1]);split_free(parts,n);}

static void uso(void)
{
    fputs(
        "Uso:\n"
        "  pp --setar-configuracoes-llm\n"
        "  pp --setar-configuracoes-llm --ajuda:<grupo>\n"
        "\n"
        "Descrição:\n"
        "  Editor interativo de configurações de contas LLM em contas-llm/, incluindo\n"
        "  llm.json, que representa a conta ativa.\n"
        "\n"
        "Grupos de ajuda: uso, processo, valores, exemplos.\n"
        "\n"
        "1) Uso e ajuda [uso ajuda]\n"
        "  --ajuda, --help, -h            Exibe esta ajuda\n"
        "  --ajuda:<grupo>                Filtra um grupo. Ex: pp --setar-configuracoes-llm --ajuda:valores\n"
        "\n"
        "2) Processo [processo]\n"
        "  Lista contas, solicita seleção, exibe campos e permite preservar, zerar ou\n"
        "  substituir cada valor.\n"
        "\n"
        "3) Valores aceitos [valores tipos]\n"
        "  Texto, números, booleanos true/false, null e objetos/arrays JSON inline.\n"
        "  ENTER preserva; espaço + ENTER zera/remove o campo.\n"
        "  Exemplo com todas as flags do grupo:\n"
        "    pp --setar-configuracoes-llm  (todos os valores são aceitos interativamente)\n"
        "\n"
        "4) Exemplos [exemplos]\n"
        "  pp --setar-configuracoes-llm\n",
        stdout);
}

int main(int argc, char **argv)
{     if (restricao_verificar("setar-configuracoes-llm") != 0) return 1; 
    if (argc > 1) {
        if (!strcmp(argv[1],"--help")||!strcmp(argv[1],"--ajuda")||!strcmp(argv[1],"-h")){uso();return 0;}
        uso();return 1;
    }
    char raiz[PATH_MAX]; if(raiz_projeto(raiz,sizeof(raiz))!=0){fprintf(stderr,"erro raiz\n");return 1;}
    char dir_contas[PATH_MAX*2]; resolver_subdir_sistema(raiz,"contas-llm",dir_contas,sizeof(dir_contas));
    ContaLLM *contas=NULL; int total=0;
    if (listar_contas(dir_contas,&contas,&total)!=0||total==0){fprintf(stderr,"Nenhuma conta encontrada em contas-llm/.\n");return 1;}
    printf("Contas LLM disponiveis:\n");
    for(int i=0;i<total;i++)printf("%2d) %s\n",i+1,contas[i].nome);
    int esc=ler_numero(total);if(esc<1){fprintf(stderr,"Numero invalido.\n");free(contas);return 1;}
    ContaLLM *sel=&contas[esc-1];
    char *txt=ler_arquivo(sel->caminho);if(!txt){fprintf(stderr,"Nao foi possivel ler %s\n",sel->caminho);free(contas);return 1;}
    Json *root=json_parse(txt);free(txt);if(!root||root->tipo!=J_OBJECT){fprintf(stderr,"JSON invalido em %s\n",sel->arquivo);j_free(root);free(contas);return 1;}

    printf("\nEditando: %s\n",sel->nome);
    printf("ENTER mantem o valor atual | espaco e ENTER zera o campo | digite para alterar\n\n");
    char **alts=calloc((size_t)128,sizeof(char*));int nalt=0;
    Flat f={0};flatten(root,"",&f);
    for(int i=0;i<f.total;i++){char *at=valor_linha(f.vals[i]);printf("%s [%s]: %s\n> ",f.paths[i],j_tipo(f.vals[i]),at);char linha[8192];if(!fgets(linha,sizeof(linha),stdin)){free(at);flat_free(&f);free(contas);j_free(root);return 1;}linha[strcspn(linha,"\r\n")]='\0';if(strcmp(linha,"")==0){free(at);continue;}if(strcmp(linha," ")==0){unset_path(root,f.paths[i]);if(nalt<128)alts[nalt++]=strdup(f.paths[i]);printf("  -> campo zerado\n");free(at);continue;}Json*v=parse_cli_valor(linha,f.vals[i]);if(!v){fprintf(stderr,"Valor invalido.\n");free(at);continue;}set_path(root,f.paths[i],v);printf("  -> alterado\n");if(nalt<128)alts[nalt++]=strdup(f.paths[i]);free(at);}flat_free(&f);

    char *out=json_to_string(root,0);int r=escrever_arquivo(sel->caminho,out);free(out);
    printf("Configuracoes LLM salvas em contas-llm/%s (%d alteracao%s).\n",sel->arquivo,nalt,nalt==1?"":"es");
    for(int i=0;i<nalt;i++)free(alts[i]);free(alts);
    j_free(root);free(contas);
    return r==0?0:1;
}
