// src/abi.c - C header generation + ABI checks
#include "abi.h"
#include "vm.h"
#include "types.h"
#include "util.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct {
  const char** name;
  int* len;
  int n, cap;
} NameSet;

static void* xrealloc(void* p, size_t n){ void* q=realloc(p,n); if(!q) die("out of memory"); return q; }

static int nameset_has(NameSet* s, const char* name, int len){
  for(int i=0;i<s->n;i++){
    if(s->len[i]==len && strncmp(s->name[i], name, (size_t)len)==0) return 1;
  }
  return 0;
}

static void nameset_add(NameSet* s, const char* name, int len){
  if(!name || len<=0) return;
  if(nameset_has(s, name, len)) return;
  if(s->n==s->cap){
    s->cap = s->cap? s->cap*2 : 32;
    s->name = (const char**)xrealloc(s->name, sizeof(char*)*(size_t)s->cap);
    s->len  = (int*)xrealloc(s->len, sizeof(int)*(size_t)s->cap);
  }
  s->name[s->n] = name;
  s->len[s->n] = len;
  s->n++;
}

static void nameset_free(NameSet* s){
  if(!s) return;
  if(s->name) free(s->name);
  if(s->len) free(s->len);
  s->name = NULL;
  s->len = NULL;
  s->n = 0;
  s->cap = 0;
}

static int is_c_ident_name(const char* s, int len){
  if(!s || len<=0 || len>128) return 0;
  char c0 = s[0];
  int head_ok = ((c0>='a'&&c0<='z') || (c0>='A'&&c0<='Z') || c0=='_');
  if(!head_ok) return 0;
  for(int i=1;i<len;i++){
    char c = s[i];
    int ok = ((c>='a'&&c<='z') || (c>='A'&&c<='Z') || (c>='0'&&c<='9') || c=='_');
    if(!ok) return 0;
  }
  return 1;
}

static void emit_ident(FILE* out, const char* name){
  if(!out || !name) return;
  char c0 = name[0];
  int head_ok = ((c0>='a'&&c0<='z') || (c0>='A'&&c0<='Z') || c0=='_');
  if(!head_ok) return;
  fputc(c0, out);
  int i=1;
  while(name[i]){
    char c = name[i];
    int ok = ((c>='a'&&c<='z') || (c>='A'&&c<='Z') || (c>='0'&&c<='9') || c=='_');
    if(!ok) break;
    fputc(c, out);
    i++;
  }
}

static void emit_c_base(FILE* out, Type* t){
  if(!t){ fputs("int64_t", out); return; }
  switch(t->k){
    case TY_I64: fputs("int64_t", out); return;
    case TY_U8:  fputs("uint8_t", out); return;
    case TY_F64: fputs("double", out); return;
    case TY_VOID: fputs("void", out); return;
    case TY_STRUCT:
      if(t->sname) fprintf(out, "%.*s", t->sname_len, t->sname);
      else fputs("void", out);
      return;
    default:
      fputs("void", out);
      return;
  }
}

static void emit_fn_ptr(FILE* out, Type* fnty, const char* name){
  if(!fnty || fnty->k!=TY_FN){
    fputs("void (*", out);
    emit_ident(out, name);
    fputs(")(void)", out);
    return;
  }
  emit_c_base(out, fnty->fret);
  fputs(" (*", out);
  emit_ident(out, name);
  fputs(")(", out);
  for(int i=0;i<fnty->fparam_n;i++){
    if(i) fputs(", ", out);
    Type* pt = fnty->fparams[i];
    if(pt && pt->k==TY_ARRAY){
      emit_c_base(out, pt->elem);
      fputs(" *", out);
    } else if(pt && pt->k==TY_PTR && pt->elem && pt->elem->k==TY_FN){
      emit_fn_ptr(out, pt->elem, NULL);
    } else if(pt && pt->k==TY_PTR){
      // pointer params
      Type* base = pt->elem;
      emit_c_base(out, base);
      fputs(" *", out);
    } else {
      emit_c_base(out, pt);
    }
  }
  if(fnty->fparam_n==0) fputs("void", out);
  fputs(")", out);
}

static void emit_c_decl(FILE* out, Type* t, const char* name, int as_param){
  if(!t){
    fputs("int64_t", out);
    if(name){ fputc(' ', out); emit_ident(out, name); }
    return;
  }

  // function pointer (ptr to fn)
  if(t->k==TY_PTR && t->elem && t->elem->k==TY_FN){
    emit_fn_ptr(out, t->elem, name);
    return;
  }

  if(t->k==TY_ARRAY && !as_param){
    emit_c_base(out, t->elem);
    if(name){ fputc(' ', out); emit_ident(out, name); }
    fprintf(out, "[%lld]", t->len);
    return;
  }

  int ptr = 0;
  Type* base = t;
  if(base->k==TY_ARRAY && as_param){
    ptr = 1;
    base = base->elem;
  }
  while(base && base->k==TY_PTR && base->elem && base->elem->k!=TY_FN){
    ptr++;
    base = base->elem;
  }
  emit_c_base(out, base);
  for(int i=0;i<ptr;i++) fputs(" *", out);
  if(name){ fputc(' ', out); emit_ident(out, name); }
}

static void emit_structs(FILE* out, Module* M, NameSet* done, TypeEnv* tenv){
  if(!M) return;
  for(int i=0;i<M->struct_n;i++){
    StructDecl* st = M->structs[i];
    if(!st || !st->name || st->len<=0) continue;
    if(!is_c_ident_name(st->name, st->len)) continue;
    if(nameset_has(done, st->name, st->len)) continue;
    nameset_add(done, st->name, st->len);

    fprintf(out, "typedef struct %.*s {\n", st->len, st->name);
    for(int f=0; f<st->field_n; f++){
      fputs("  ", out);
      emit_c_decl(out, st->fields[f].ty, st->fields[f].name, 0);
      fputs(";\n", out);
    }
    fprintf(out, "} %.*s;\n", st->len, st->name);

    Type* t = tenv_find_struct(tenv, st->name, st->len);
    if(t){
      fprintf(out, "_Static_assert(sizeof(%.*s) == %d, \"ABI size mismatch for %.*s\");\n",
              st->len, st->name, type_size(t), st->len, st->name);
      fprintf(out, "_Static_assert(_Alignof(%.*s) == %d, \"ABI align mismatch for %.*s\");\n",
              st->len, st->name, type_align(t), st->len, st->name);
    }
    fputc('\n', out);
  }
}

static void emit_typedefs(FILE* out, Module* M, NameSet* done){
  if(!M) return;
  for(int i=0;i<M->typedef_n;i++){
    TypedefDecl* td = M->typedefs[i];
    if(!td || !td->name || td->len<=0) continue;
    if(!is_c_ident_name(td->name, td->len)) continue;
    if(nameset_has(done, td->name, td->len)) continue;
    nameset_add(done, td->name, td->len);
    fputs("typedef ", out);
    emit_c_decl(out, td->ty, td->name, 0);
    fputs(";\n", out);
  }
  if(M->typedef_n>0) fputc('\n', out);
}

static void emit_enums(FILE* out, Module* M, NameSet* done){
  if(!M) return;
  for(int i=0;i<M->enum_n;i++){
    EnumDecl* ed = M->enums[i];
    if(!ed) continue;
    if(ed->len>0 && !is_c_ident_name(ed->name, ed->len)) continue;
    if(ed->len>0 && !nameset_has(done, ed->name, ed->len)){
      nameset_add(done, ed->name, ed->len);
      fprintf(out, "typedef int64_t %.*s;\n", ed->len, ed->name);
    }
    for(int j=0;j<ed->item_n;j++){
      EnumItem* it = &ed->items[j];
      if(!is_c_ident_name(it->name, it->len)) continue;
      fprintf(out, "#define %.*s ((int64_t)%lld)\n", it->len, it->name, it->val);
    }
    fputc('\n', out);
  }
}

static void emit_globals(FILE* out, Module* M){
  if(!M) return;
  for(int i=0;i<M->global_n;i++){
    struct GlobalVar* g = &M->globals[i];
    if(!g || g->is_static || g->is_extern) continue;
    fputs("extern ", out);
    emit_c_decl(out, g->ty, g->name, 0);
    fputs(";\n", out);
  }
  if(M->global_n>0) fputc('\n', out);
}

static void emit_fns(FILE* out, Module* M){
  if(!M) return;
  for(int i=0;i<M->fn_n;i++){
    FnDecl* fn = M->fns[i];
    if(!fn || fn->is_extern) continue;
    fputs("TEZZ_API ", out);
    emit_c_decl(out, fn->ret, fn->name, 1);
    fputc('(', out);
    for(int p=0;p<fn->arity;p++){
      if(p) fputs(", ", out);
      emit_c_decl(out, fn->ptypes[p], fn->pnames[p], 1);
    }
    if(fn->arity==0) fputs("void", out);
    fputs(");\n", out);
  }
  if(M->fn_n>0) fputc('\n', out);
}

static void emit_tnx_str(FILE* out, const char* s, int len){
  fputc('"', out);
  for(int i=0;i<len;i++){
    char c = s[i];
    if(c == '\"') fputs("\\\"", out);
    else if(c == '\\') fputs("\\\\", out);
    else if(c == '\n') fputs("\\n", out);
    else if(c == '\r') fputs("\\r", out);
    else if(c == '\t') fputs("\\t", out);
    else fputc(c, out);
  }
  fputc('"', out);
}

static const char* ty_kind_name(Type* t){
  if(!t) return "i64";
  switch(t->k){
    case TY_I64: return "i64";
    case TY_U8: return "u8";
    case TY_F64: return "f64";
    case TY_PTR: return "ptr";
    case TY_ARRAY: return "array";
    case TY_STRUCT: return "struct";
    case TY_FN: return "fn";
    case TY_VOID: return "void";
    default: return "unknown";
  }
}

static void emit_tnx_type(FILE* out, Type* t){
  const char* k = ty_kind_name(t);
  emit_tnx_str(out, k, (int)strlen(k));
  if(t && (t->k==TY_PTR || t->k==TY_ARRAY)){
    fputs(",\"elem\":", out);
    emit_tnx_type(out, t->elem);
  }
  if(t && t->k==TY_ARRAY){
    fprintf(out, ",\"len\":%lld", t->len);
  }
  if(t && t->k==TY_STRUCT && t->sname){
    fputs(",\"name\":", out);
    emit_tnx_str(out, t->sname, t->sname_len);
  }
}

void abi_emit_tnx(struct ModuleGraph* G, TypeEnv* tenv, struct Module* root, const char* out_path){
  if(!root || !out_path) die("abi_emit_tnx: invalid args");
  FILE* out = fopen(out_path, "wb");
  if(!out) die("abi_emit_tnx: cannot open output");

  NameSet struct_set = {0};

  fputs("{\"structs\":[", out);
  int first = 1;
  for(int mi=0; mi<(G ? G->mod_n : 0); mi++){
    Module* M = G->mods[mi];
    if(!M) continue;
    for(int i=0;i<M->struct_n;i++){
      StructDecl* st = M->structs[i];
      if(!st || !st->name || st->len<=0) continue;
      if(!is_c_ident_name(st->name, st->len)) continue;
      if(nameset_has(&struct_set, st->name, st->len)) continue;
      nameset_add(&struct_set, st->name, st->len);
      Type* t = tenv_find_struct(tenv, st->name, st->len);
      if(!t || t->incomplete) continue;
      if(!first) fputc(',', out);
      first = 0;
      fputs("{\"name\":", out);
      emit_tnx_str(out, st->name, st->len);
      fprintf(out, ",\"size\":%d,\"align\":%d,\"fields\":[", type_size(t), type_align(t));
      for(int f=0; f<st->field_n; f++){
        if(f) fputc(',', out);
        fputs("{\"name\":", out);
        emit_tnx_str(out, st->fields[f].name, st->fields[f].name_len);
        fprintf(out, ",\"off\":%d,\"size\":%d,\"align\":%d,\"type\":",
                st->fields[f].off, type_size(st->fields[f].ty), type_align(st->fields[f].ty));
        emit_tnx_type(out, st->fields[f].ty);
        fputc('}', out);
      }
      fputs("]}", out);
    }
  }
  fputs("],\"globals\":[", out);
  int gfirst = 1;
  for(int i=0;i<root->global_n;i++){
    struct GlobalVar* g = &root->globals[i];
    if(!g || g->is_static) continue;
    if(!gfirst) fputc(',', out);
    gfirst = 0;
    fputs("{\"name\":", out);
    emit_tnx_str(out, g->name, g->len);
    fputs(",\"type\":", out);
    emit_tnx_type(out, g->ty);
    fputc('}', out);
  }
  fputs("],\"fns\":[", out);
  int ffirst = 1;
  for(int i=0;i<root->fn_n;i++){
    FnDecl* fn = root->fns[i];
    if(!fn) continue;
    if(!ffirst) fputc(',', out);
    ffirst = 0;
    fputs("{\"name\":", out);
    emit_tnx_str(out, fn->name, fn->len);
    fputs(",\"extern\":", out);
    fputs(fn->is_extern ? "true" : "false", out);
    fputs(",\"ret\":", out);
    emit_tnx_type(out, fn->ret);
    fputs(",\"params\":[", out);
    for(int p=0;p<fn->arity;p++){
      if(p) fputc(',', out);
      emit_tnx_type(out, fn->ptypes[p]);
    }
    fputs("]}", out);
  }
  fputs("]}", out);
  fclose(out);

  nameset_free(&struct_set);
}

void abi_emit_header(struct ModuleGraph* G, TypeEnv* tenv, struct Module* root, const char* out_path){
  if(!root || !out_path) die("abi_emit_header: invalid args");
  FILE* out = fopen(out_path, "wb");
  if(!out) die("abi_emit_header: cannot open output");

  fputs("// Generated by tezzc cheader\n", out);
  fputs("#pragma once\n", out);
  fputs("#include <stdint.h>\n", out);
  fputs("#include <stddef.h>\n\n", out);
  fputs("#ifndef TEZZ_API\n", out);
  fputs("#ifdef _WIN32\n", out);
  fputs("#define TEZZ_API __declspec(dllimport)\n", out);
  fputs("#else\n", out);
  fputs("#define TEZZ_API\n", out);
  fputs("#endif\n", out);
  fputs("#endif\n\n", out);

  fputs("#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n", out);

  NameSet struct_set = {0};
  NameSet typedef_set = {0};
  NameSet enum_set = {0};

  // forward declares for all structs
  for(int mi=0; mi<(G ? G->mod_n : 0); mi++){
    Module* M = G->mods[mi];
    if(!M) continue;
    for(int i=0;i<M->struct_n;i++){
      StructDecl* st = M->structs[i];
      if(!st || !st->name || st->len<=0) continue;
      if(nameset_has(&struct_set, st->name, st->len)) continue;
      nameset_add(&struct_set, st->name, st->len);
      fprintf(out, "typedef struct %.*s %.*s;\n", st->len, st->name, st->len, st->name);
    }
  }
  if(struct_set.n>0) fputc('\n', out);

  nameset_free(&struct_set);
  struct_set.name = NULL; struct_set.len = NULL; struct_set.n = 0; struct_set.cap = 0;

  // typedefs + enums + structs are emitted from root only.
  // This keeps C interop headers scoped to the package API surface and
  // avoids leaking imported stdlib/internal declarations.
  emit_typedefs(out, root, &typedef_set);
  emit_enums(out, root, &enum_set);
  emit_structs(out, root, &struct_set, tenv);

  // globals + fns from root module only
  emit_globals(out, root);
  emit_fns(out, root);

  fputs("#ifdef __cplusplus\n}\n#endif\n", out);
  fclose(out);

  nameset_free(&struct_set);
  nameset_free(&typedef_set);
  nameset_free(&enum_set);
}
