export interface RpcParameterContract {
  name: string;
  type: string;
  optional: boolean;
}
export interface RpcManifestContract {
  function: string;
  parameters?: RpcParameterContract[];
  returnType?: string;
}
export interface RpcRequestPayload {
  component: string;
  function: string;
  args: unknown[];
}
export interface RpcContext {
  request: Request;
  user?: unknown;
  traceId: string;
}
export interface RpcHandlerOptions {
  resolve(component: string): Promise<{
    functions: Record<string, (...args: any[]) => any>;
    manifest?: RpcManifestContract[];
  } | null>;
  authenticate?: (request: Request) => Promise<unknown> | unknown;
  authorize?: (context: RpcContext, payload: RpcRequestPayload) => Promise<boolean> | boolean;
  validateCsrf?: (request: Request) => Promise<boolean> | boolean;
  validateInput?: (
    payload: RpcRequestPayload,
    manifestEntry: RpcManifestContract,
  ) => Promise<unknown[]> | unknown[];
  validateOutput?: (
    value: unknown,
    manifestEntry: RpcManifestContract,
  ) => Promise<unknown> | unknown;
}

function json(body: unknown, status = 200): Response {
  return new Response(JSON.stringify(body), {
    status,
    headers: {
      "content-type": "application/json; charset=utf-8",
      "cache-control": "no-store",
    },
  });
}
function error(
  code: string,
  message: string,
  status: number,
  traceId: string,
  details?: unknown,
): Response {
  return json(
    { ok: false, error: { code, message, traceId, ...(details === undefined ? {} : { details }) } },
    status,
  );
}
function validName(value: unknown): value is string {
  return typeof value === "string" && /^[A-Za-z_$][\w$]*$/.test(value);
}

function removePromise(type: string): string {
  const match = /^Promise\s*<([\s\S]+)>$/.exec(type.trim());
  return match?.[1]?.trim() ?? type.trim();
}

function splitUnion(type: string): string[] {
  const parts: string[] = [];
  let depth = 0;
  let quote = "";
  let start = 0;
  for (let index = 0; index < type.length; index++) {
    const char = type[index]!;
    if (quote) {
      if (char === "\\") index++;
      else if (char === quote) quote = "";
      continue;
    }
    if (char === '"' || char === "'") quote = char;
    else if ("<([{".includes(char)) depth++;
    else if (">)]}".includes(char)) depth = Math.max(0, depth - 1);
    else if (char === "|" && depth === 0) {
      parts.push(type.slice(start, index).trim());
      start = index + 1;
    }
  }
  parts.push(type.slice(start).trim());
  return parts.filter(Boolean);
}

function matchesRuntimeType(value: unknown, rawType: string): boolean {
  const type = removePromise(rawType).trim();
  if (!type || ["unknown", "any", "never"].includes(type)) return true;
  const union = splitUnion(type);
  if (union.length > 1) return union.some((part) => matchesRuntimeType(value, part));
  if (type === "undefined" || type === "void") return value === undefined;
  if (type === "null") return value === null;
  if (/^"[\s\S]*"$|^'[\s\S]*'$/.test(type)) return value === type.slice(1, -1);
  if (/^-?\d+(?:\.\d+)?$/.test(type)) return value === Number(type);
  if (type === "true") return value === true;
  if (type === "false") return value === false;
  if (type === "string") return typeof value === "string";
  if (type === "number") return typeof value === "number" && Number.isFinite(value);
  if (type === "boolean") return typeof value === "boolean";
  if (type === "bigint") return typeof value === "bigint";
  if (type === "Date")
    return value instanceof Date || (typeof value === "string" && !Number.isNaN(Date.parse(value)));
  if (type.endsWith("[]"))
    return (
      Array.isArray(value) && value.every((item) => matchesRuntimeType(item, type.slice(0, -2)))
    );
  if (/^(?:Readonly)?Array\s*</.test(type)) return Array.isArray(value);
  if (/^(?:Record|Map|Set)\s*</.test(type)) return value !== null && typeof value === "object";
  if (/^\{[\s\S]*\}$/.test(type))
    return value !== null && typeof value === "object" && !Array.isArray(value);
  // Named interfaces/types are validated by generated application validators
  // when available. Their JSON transport shape must at least be object-like.
  if (/^[A-Za-z_$][\w$]*(?:<.*>)?$/.test(type)) return value !== null && typeof value === "object";
  return true;
}

function validateArguments(payload: RpcRequestPayload, manifest: RpcManifestContract): unknown[] {
  const parameters = manifest.parameters ?? [];
  const required = parameters.filter((parameter) => !parameter.optional).length;
  if (payload.args.length < required || payload.args.length > parameters.length) {
    throw new TypeError(
      `WRN-RPC-INPUT: ${manifest.function} expects ${required === parameters.length ? required : `${required}-${parameters.length}`} arguments, received ${payload.args.length}.`,
    );
  }
  parameters.forEach((parameter, index) => {
    const value = payload.args[index];
    if (value === undefined && parameter.optional) return;
    if (!matchesRuntimeType(value, parameter.type)) {
      throw new TypeError(
        `WRN-RPC-INPUT: argument '${parameter.name}' expected ${parameter.type}.`,
      );
    }
  });
  return payload.args;
}

function validateReturn(value: unknown, manifest: RpcManifestContract): unknown {
  const type = manifest.returnType ?? "unknown";
  if (!matchesRuntimeType(value, type)) {
    throw new TypeError(
      `WRN-RPC-OUTPUT: ${manifest.function} returned a value incompatible with ${type}.`,
    );
  }
  return value;
}

export function createRpcHandler(
  options: RpcHandlerOptions,
): (request: Request) => Promise<Response> {
  return async (request) => {
    const traceId = request.headers.get("x-request-id") || crypto.randomUUID();
    if (request.method !== "POST")
      return error("WRN-RPC-METHOD", "RPC requires POST", 405, traceId);
    if (options.validateCsrf && !(await options.validateCsrf(request))) {
      return error("WRN-RPC-CSRF", "CSRF validation failed", 403, traceId);
    }
    let payload: RpcRequestPayload;
    try {
      payload = (await request.json()) as RpcRequestPayload;
    } catch {
      return error("WRN-RPC-JSON", "Invalid JSON request", 400, traceId);
    }
    if (
      !validName(payload.component) ||
      !validName(payload.function) ||
      !Array.isArray(payload.args)
    ) {
      return error("WRN-RPC-PAYLOAD", "Invalid RPC payload", 400, traceId);
    }
    const resolved = await options.resolve(payload.component);
    if (!resolved)
      return error(
        "WRN-RPC-COMPONENT",
        `Unknown RPC component '${payload.component}'`,
        404,
        traceId,
      );
    const manifestEntry = resolved.manifest?.find((entry) => entry.function === payload.function);
    if (!manifestEntry) {
      return error(
        "WRN-RPC-FUNCTION",
        `Server function '${payload.function}' is not remotely exposed`,
        404,
        traceId,
      );
    }
    const fn = resolved.functions[payload.function];
    if (typeof fn !== "function") {
      return error(
        "WRN-RPC-FUNCTION",
        `Server function '${payload.function}' is not available`,
        404,
        traceId,
      );
    }
    const user = options.authenticate ? await options.authenticate(request) : undefined;
    const context: RpcContext = { request, user, traceId };
    if (options.authorize && !(await options.authorize(context, payload))) {
      return error("WRN-RPC-AUTHZ", "Not authorized", 403, traceId);
    }
    try {
      const args = options.validateInput
        ? await options.validateInput(payload, manifestEntry)
        : validateArguments(payload, manifestEntry);
      const value = await fn(...args, context);
      const validated = options.validateOutput
        ? await options.validateOutput(value, manifestEntry)
        : validateReturn(value, manifestEntry);
      return json({ ok: true, value: validated, traceId });
    } catch (cause) {
      const message = cause instanceof Error ? cause.message : String(cause);
      const code = message.startsWith("WRN-RPC-INPUT")
        ? "WRN-RPC-INPUT"
        : message.startsWith("WRN-RPC-OUTPUT")
          ? "WRN-RPC-OUTPUT"
          : "WRN-RPC-EXECUTION";
      return error(code, message, code === "WRN-RPC-EXECUTION" ? 500 : 400, traceId);
    }
  };
}
