// AUTO-GENERATED — do not edit by hand.
// Source: services.proto  |  Regenerate: make generate  (or: python3 scripts/generators/code/generate.py --lang javascript)

import koffi from "koffi";
import path from "path";
// @ts-ignore - generated CommonJS module
import { types } from "./generated/proto.js";

// Standard Node.js __dirname
declare const __dirname: string;
const _dirname = __dirname;

// ── Config ────────────────────────────────────────────────────────────────────

/**
 * Connection configuration for the gRPC client.
 * Field names must be snake_case — they are serialised to JSON and sent to the
 * Rust FFI layer which deserialises them into GrpcConfigInput.
 * 
 * The connector_config field should contain the connector-specific authentication
 * and configuration in the format expected by the server:
 * {"config": {"ConnectorName": {"api_key": "...", ...}}}
 */
export interface GrpcConfig {
  endpoint: string;
  connector: string;
  connector_config: Record<string, unknown>;
}

// ── koffi FFI bindings ────────────────────────────────────────────────────────

interface GrpcFfi {
  call: (
    method:    string,
    configPtr: Buffer,
    configLen: number,
    reqPtr:    Buffer,
    reqLen:    number,
    outLen:    number[],
  ) => unknown;
  free: (ptr: unknown, len: number) => void;
}

function loadGrpcFfi(libPath?: string): GrpcFfi {
  if (!libPath) {
    const ext = process.platform === "darwin" ? "dylib" : "so";
    libPath = path.join(_dirname, "generated", `libhyperswitch_grpc_ffi.${ext}`);
  }

  const lib = koffi.load(libPath);

  const call = lib.func("hyperswitch_grpc_call",
    koffi.pointer("uint8"),
    [
      "str",
      koffi.pointer("uint8"),
      "uint32",
      koffi.pointer("uint8"),
      "uint32",
      koffi.out(koffi.pointer("uint32")),
    ],
  );

  const free = lib.func("hyperswitch_grpc_free", "void", [
    koffi.pointer("uint8"),
    "uint32",
  ]);

  return { call, free };
}

// ── SecretString field normalization ─────────────────────────────────────────

const _SECRET_STRING_FIELDS: Record<string, readonly string[]> = {
{% for msg, fields in secret_string_fields.items() %}
  {{ msg }}: [{{ fields | map("tojson") | join(", ") }}],
{% endfor %}
};

const _MSG_FIELD_TYPES: Record<string, Record<string, string>> = {
{% for msg, fields in msg_field_types.items() %}
  {{ msg }}: { {% for fname, tname in fields.items() %}{{ fname | tojson }}: {{ tname | tojson }}{% if not loop.last %}, {% endif %}{% endfor %} },
{% endfor %}
};

function _wrapSecretStrings(obj: unknown, msgName: string): unknown {
  if (typeof obj !== "object" || obj === null) return obj;
  if (Array.isArray(obj)) return (obj as unknown[]).map(item => _wrapSecretStrings(item, msgName));

  const secretSet = new Set(_SECRET_STRING_FIELDS[msgName] ?? []);
  const fieldTypes = _MSG_FIELD_TYPES[msgName] ?? {};
  const result: Record<string, unknown> = {};

  for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {
    if (secretSet.has(k) && typeof v === "string") {
      result[k] = { value: v };
    } else if (typeof v === "string") {
      const nestedType = fieldTypes[k];
      if (nestedType) {
        const nestedSecrets = _SECRET_STRING_FIELDS[nestedType];
        if (nestedSecrets && nestedSecrets.length === 1) {
          result[k] = { [nestedSecrets[0]]: { value: v } };
        } else {
          result[k] = v;
        }
      } else {
        result[k] = v;
      }
    } else if (typeof v === "object" && v !== null && !Array.isArray(v)) {
      const nestedType = fieldTypes[k];
      result[k] = nestedType ? _wrapSecretStrings(v, nestedType) : v;
    } else {
      result[k] = v;
    }
  }
  return result;
}

// ── Dispatch helper ───────────────────────────────────────────────────────────

function callGrpc(ffi: GrpcFfi, config: GrpcConfig, method: string, req: any, ReqType: any, ResType: any): unknown {
  const configBuf = Buffer.from(JSON.stringify(config));
  const normalized = _wrapSecretStrings(req, (ReqType as any).name as string);
  const reqBuf = Buffer.from(ReqType.encode(ReqType.create(normalized)).finish());
  const outLen = [0];

  const ptr = ffi.call(method, configBuf, configBuf.length, reqBuf, reqBuf.length, outLen);
  const len = outLen[0];
  const rawBytes = Buffer.from(koffi.decode(ptr, "uint8", len) as Uint8Array);
  ffi.free(ptr, len);

  if (rawBytes[0] === 1) {
    throw new Error(`gRPC error (${method}): ${rawBytes.slice(1).toString("utf-8")}`);
  }

  return ResType.decode(rawBytes.slice(1));
}

// ── Sub-clients (one per proto service) ──────────────────────────────────────

{% for service in services %}
// {{ service }}
export class {{ service_to_grpc_struct(service) }} {
  constructor(private ffi: GrpcFfi, private config: GrpcConfig) {}

{% for f in groups[service] %}
  /** {{ f.service }}.{{ f.rpc }} — {{ f.description }} */
  async {{ to_camel(f.name) }}(req: unknown): Promise<unknown> {
    return callGrpc(this.ffi, this.config, "{{ grpc_method_path(f.service, f.name) }}",
      req, types.{{ f.request }}, types.{{ f.response }});
  }
{% endfor %}
}

{% endfor %}
// ── Top-level GrpcClient ──────────────────────────────────────────────────────

export class GrpcClient {
{% for service in services %}
  public {{ service_to_grpc_js_field(service) }}: {{ service_to_grpc_struct(service) }};
{% endfor %}

  constructor(config: GrpcConfig, libPath?: string) {
    const ffi = loadGrpcFfi(libPath);
{% for service in services %}
    this.{{ service_to_grpc_js_field(service) }} = new {{ service_to_grpc_struct(service) }}(ffi, config);
{% endfor %}
  }
}
