/**
 * Convex HTTP Client — Server-side access to Convex functions
 *
 * Uses string-based function paths (e.g. "auth/queries:getUserByEmail")
 * because Next.js bundling breaks the `anyApi` Proxy objects from
 * convex/_generated/api.js (they serialize to `{}` instead of paths).
 *
 * Usage:
 *   import { convexQuery, convexMutation } from '@/lib/convex-client';
 *
 *   const user = await convexQuery("auth/queries:getUserByEmail", { email });
 *   await convexMutation("auth/mutations:updateUserNoAuth", { email, ... });
 */

import { ConvexHttpClient } from "convex/browser";

let _client: ConvexHttpClient | null = null;
let _clientUrl: string | null = null;

/**
 * Lazy client getter — reads env var at call time, not module load time.
 */
function getClient(): ConvexHttpClient | null {
  const url = process.env.NEXT_PUBLIC_CONVEX_URL || process.env.CONVEX_URL;
  if (!url) return null;
  if (!_client || _clientUrl !== url) {
    _client = new ConvexHttpClient(url);
    _clientUrl = url;
  }
  return _client;
}

export const isConvexConfigured = () =>
  Boolean(process.env.NEXT_PUBLIC_CONVEX_URL || process.env.CONVEX_URL);

/**
 * Normalize a function reference to a string path.
 * Handles both string paths ("auth/queries:getUserByEmail") and
 * anyApi proxy objects that may have been bundled into empty objects.
 */
function toFunctionPath(ref: unknown): string {
  if (typeof ref === "string") return ref;
  // anyApi proxy or FunctionReference — try to extract the path
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const r = ref as any;
  if (typeof r?._name === "string") return r._name;
  if (typeof r?.functionName === "string") return r.functionName;
  // Safety check: if we got here the ref is likely a webpack-broken Proxy
  // that serializes to {} instead of a string path.
  throw new Error(
    `[convex-client] toFunctionPath received a non-string reference that could not be resolved. ` +
      `This usually means an \`api.X.Y.Z\` proxy object was passed instead of a string path. ` +
      `Use string paths like "module/submodule:functionName" instead. Received: ${JSON.stringify(ref)}`,
  );
}

/**
 * Call a Convex query function server-side.
 * Accepts a string path like "auth/queries:getUserByEmail".
 * Returns null if Convex is not configured.
 */
export async function convexQuery<T = unknown>(
  functionRef: string | unknown,
  args: Record<string, unknown> = {},
): Promise<T | null> {
  const client = getClient();
  if (!client) return null;
  try {
    const path = toFunctionPath(functionRef);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    return (await client.query(path as any, args)) as T;
  } catch (err) {
    console.error("[convexQuery] Error:", err);
    return null;
  }
}

/**
 * Call a Convex mutation function server-side.
 * Accepts a string path like "auth/mutations:createUserNoAuth".
 * Returns null if Convex is not configured.
 */
export async function convexMutation<T = unknown>(
  functionRef: string | unknown,
  args: Record<string, unknown> = {},
): Promise<T | null> {
  const client = getClient();
  if (!client) return null;
  try {
    const path = toFunctionPath(functionRef);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    return (await client.mutation(path as any, args)) as T;
  } catch (err) {
    console.error("[convexMutation] Error:", err);
    return null;
  }
}

/**
 * Call a Convex query that throws on errors instead of returning null.
 * Returns null ONLY when the query itself returns null (e.g. "not found").
 * Throws if Convex is not configured or the query fails.
 */
export async function convexQueryStrict<T = unknown>(
  functionRef: string | unknown,
  args: Record<string, unknown> = {},
): Promise<T | null> {
  const client = getClient();
  if (!client) throw new Error("Convex is not configured");
  const path = toFunctionPath(functionRef);
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  return (await client.query(path as any, args)) as T;
}

/**
 * Call a Convex action function server-side.
 * Accepts a string path like "billing/actions:createCheckout".
 * Returns null if Convex is not configured.
 */
export async function convexAction<T = unknown>(
  functionRef: string | unknown,
  args: Record<string, unknown> = {},
): Promise<T | null> {
  const client = getClient();
  if (!client) return null;
  try {
    const path = toFunctionPath(functionRef);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    return (await client.action(path as any, args)) as T;
  } catch (err) {
    console.error("[convexAction] Error:", err);
    return null;
  }
}

export { getClient as getConvexClient };
