#!/usr/bin/env node
"use strict";var N=require("child_process"),$=require("fs"),v=require("path");function j(e){return e.replace(/[A-Z]/g,a=>`_${a.toLowerCase()}`)}function y(e){let a=JSON.parse(e);return{functions:a.functions.map(n=>({jsName:n.js_name,rustName:n.rust_name,paramNames:n.param_names,params:n.params,ret:n.ret,isAsync:n.is_async})),classes:(a.classes??[]).map(n=>({name:n.name,methods:n.methods.map(r=>({jsName:r.js_name,rustName:r.rust_name,paramNames:r.param_names,params:r.params,ret:r.ret,isAsync:r.is_async,isGetter:r.is_getter,isStatic:r.is_static??!1}))})),objects:(a.objects??[]).map(n=>({name:n.name,fieldNames:n.field_names,fieldTypes:n.field_types})),constants:(a.constants??[]).map(n=>({jsName:n.js_name,rustName:n.rust_name,tsType:n.ts_type,value:n.value}))}}function p(e){return e.params.map((a,n)=>{let r=e.paramNames[n]??`arg${n}`,i=a.endsWith("| undefined | null");return`${r}${i?"?":""}: ${a}`}).join(", ")}function h(e){return e.isAsync?`Promise<${e.ret}>`:e.ret}function x(e){return`[${e.functions.filter(n=>n.isAsync).map(n=>`'${n.jsName}'`).join(", ")}]`}function S(e){return`[${e.functions.filter(n=>n.ret==="ExternalObject").map(n=>`'${n.jsName}'`).join(", ")}]`}function w(e){return`{ ${e.functions.filter(n=>j(n.jsName)!==n.rustName).map(n=>`'${n.jsName}': '${n.rustName}'`).join(", ")} }`}function b(e,a="Bindings"){let n=new Set(e.classes.map(t=>t.name)),r=e.functions.filter(t=>n.has(t.ret)),i=e.objects.map(k).join(`

`),o=(e.constants??[]).map(t=>`export const ${t.jsName}: ${t.tsType} = ${JSON.stringify(t.value)};`).join(`
`),g=e.functions.map(t=>`  ${t.jsName}(${p(t)}): ${h(t)};`).join(`
`),s=e.classes.map(t=>P(t,e)).join(`

`),l=e.classes.map(t=>`  ${t.name}: typeof ${t.name};`).join(`
`),c=e.classes.length?`{ ${e.classes.map(t=>t.name).join(", ")} } as unknown as Record<string, new (...a: unknown[]) => unknown>`:"{}",f=r.length?`{ ${r.map(t=>`${t.jsName}: { cls: ${t.ret}, isAsync: ${t.isAsync}, rustName: '${t.rustName}' }`).join(", ")} } as unknown as Record<string, { cls: { __fromHandle(p: SyncProvider, h: unknown): unknown }; isAsync: boolean; rustName: string }>`:"{}",d=e.classes.some(t=>t.methods.some(_=>_.isStatic)),u=d?`/** Provider for class statics (associated fns), bound by \`bind()\`. */
let __staticProvider = undefined as unknown as SyncProvider;

`:"",m=d?`
  __staticProvider = provider;`:"";return`// Generated from the Rust #[napi] manifest. Do not edit.
import {
  createSyncBinding,
  bindClasses,
  type ExternalObject,
  type SyncProvider,
} from 'napi-oop-runtime';

export type { ExternalObject };

${u}${i}${i?`

`:""}${o}${o?`

`:""}export interface ${a} {
${g}
${l}
}

${s}

const asyncFns: string[] = ${x(e)};
const externalFns: string[] = ${S(e)};
const wireNames: Record<string, string> = ${w(e)};

export const bind = (provider: SyncProvider): ${a} => {${m}
  return bindClasses(
    createSyncBinding<${a}>(provider, asyncFns, externalFns, wireNames),
    provider,
    ${c},
    ${f}
  );
};
`}function k(e){let a=e.fieldNames.map((n,r)=>`  ${n}: ${e.fieldTypes[r]??"unknown"};`).join(`
`);return`export interface ${e.name} {
${a}
}`}function P(e,a){let n=new Set(a.classes.map(s=>s.name)),r=e.methods.find(s=>s.jsName==="constructor"),i=r?p(r):"",o=r?r.paramNames.join(", "):"",g=e.methods.filter(s=>s.jsName!=="constructor").map(s=>{let l=n.has(s.ret),c=s.isAsync?`Promise<${s.ret}>`:s.ret,f=s.paramNames.length?", "+s.paramNames.join(", "):"",d=s.isAsync?"callAsync":"call";if(s.isStatic){let t=`__staticProvider.${d}('${s.rustName}', [${s.paramNames.join(", ")}])`,_=s.isAsync?l?`const r = (await ${t}) as ExternalObject; return ${s.ret}.__fromHandle(__staticProvider, r);`:`return (await ${t}) as ${s.ret};`:l?`const r = ${t} as ExternalObject; return ${s.ret}.__fromHandle(__staticProvider, r);`:`return ${t} as ${s.ret};`;return s.isAsync?`  static async ${s.jsName}(${p(s)}): ${c} { ${_} }`:`  static ${s.jsName}(${p(s)}): ${c} { ${_} }`}let u=`this.__provider.${d}('${s.rustName}', [this.__handle${f}])`,m=s.isAsync?l?`const r = (await ${u}) as ExternalObject; return ${s.ret}.__fromHandle(this.__provider, r);`:`return (await ${u}) as ${s.ret};`:l?`const r = ${u} as ExternalObject; return ${s.ret}.__fromHandle(this.__provider, r);`:`return ${u} as ${s.ret};`;return s.isGetter?s.isAsync?`  get ${s.jsName}(): ${c} { return (async () => { ${m} })(); }`:`  get ${s.jsName}(): ${c} { ${m} }`:s.isAsync?`  async ${s.jsName}(${p(s)}): ${c} { ${m} }`:`  ${s.jsName}(${p(s)}): ${c} { ${m} }`}).join(`
`);return`export class ${e.name} {
  private __provider: SyncProvider;
  private __handle: ExternalObject;
  constructor(${i}${r&&o?", ":""}__provider?: SyncProvider) {
    this.__provider = __provider as SyncProvider;
    this.__handle = this.__provider.call('${r?.rustName??e.name+".constructor"}', [${o}]) as ExternalObject;
    this.__provider.trackExternal(this.__handle);
  }
  static __fromHandle(provider: SyncProvider, handle: ExternalObject): ${e.name} {
    const o = Object.create(${e.name}.prototype) as ${e.name};
    (o as unknown as { __provider: SyncProvider }).__provider = provider;
    (o as unknown as { __handle: ExternalObject }).__handle = handle;
    provider.trackExternal(handle);
    return o;
  }
${g}
}`}function A(e){let[a,n,r="Bindings"]=e;(!a||!n)&&(console.error("usage: napi-oop-codegen <provider-binary> <out-dir> [InterfaceName]"),process.exit(2));let i=(0,N.execFileSync)(a,["--emit-manifest"],{encoding:"utf8"}),o=y(i);(0,$.mkdirSync)(n,{recursive:!0}),(0,$.writeFileSync)((0,v.join)(n,"bindings.ts"),b(o,r)),console.error(`generated ${o.functions.length} binding(s) -> ${n}/bindings.ts`)}A(process.argv.slice(2));
