import { stripRuntimeFunctionModifiers, type PageAst } from "@wrnexus/syntax";

export interface RpcManifestEntry {
  id: string;
  component: string;
  function: string;
  parameters: Array<{ name: string; type: string; optional: boolean }>;
  returnType: string;
}

function stableId(value: string): string {
  let hash = 0x811c9dc5;
  for (let index = 0; index < value.length; index++) {
    hash ^= value.charCodeAt(index);
    hash = Math.imul(hash, 0x01000193);
  }
  return `wrn_${(hash >>> 0).toString(36)}`;
}

/**
 * Remote exposure is reference based in v0.6. A server function is included in
 * the RPC manifest only when browser-capable code calls `server.<name>(...)`.
 * Server functions remain available to SSR/server modules without becoming
 * remotely callable by default.
 */
export function remotelyReferencedServerFunctions(ast: PageAst): Set<string> {
  const browserSources = ast.runtimeFunctions
    .filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime))
    .map((fn) => fn.body);
  for (const [hook, body] of Object.entries(ast.storeLifecycle)) {
    if (hook !== "serverInit" && body) browserSources.push(body);
  }
  const names = new Set<string>();
  const call = /\bserver\.([A-Za-z_$][\w$]*)\s*\(/g;
  for (const source of browserSources) {
    for (const match of source.matchAll(call)) names.add(match[1]!);
  }
  return names;
}

export function rpcManifest(ast: PageAst): RpcManifestEntry[] {
  const exposed = remotelyReferencedServerFunctions(ast);
  return ast.runtimeFunctions
    .filter((fn) => fn.runtime === "server" && exposed.has(fn.name))
    .map((fn) => ({
      id: stableId(`${ast.name}:${fn.name}`),
      component: ast.name,
      function: fn.name,
      parameters: fn.parameters.map((param) => ({
        name: param.name,
        type: param.valueType ?? "unknown",
        optional: param.optional,
      })),
      returnType: fn.returnType ?? (fn.async ? "Promise<unknown>" : "unknown"),
    }));
}

export function generateServerFunctionsModule(ast: PageAst): string {
  const source = ast.functions
    .map((body) => stripRuntimeFunctionModifiers(body, ["legacy", "server", "shared"]))
    .filter(Boolean)
    .join("\n\n");
  const names = ast.runtimeFunctions
    .filter((fn) => ["legacy", "server", "shared"].includes(fn.runtime))
    .map((fn) => fn.name);
  const manifest = rpcManifest(ast);
  return `// generated WRNexusJS server module for ${ast.name}\n${source}\n\nexport const __wrnexusServerFunctions = { ${[...new Set(names)].join(", ")} };\nexport const __wrnexusRpcManifest = ${JSON.stringify(manifest, null, 2)};\n`;
}
