/* eslint-disable @typescript-eslint/no-explicit-any */
/**
 * Prisma-to-Convex Compatibility Adapter
 *
 * Transitional shim that exposes a Prisma Client-like API backed by Convex.
 * This lets ~176 existing API routes continue using `prisma.users.findUnique(...)`
 * etc. while the real backend is Convex.
 *
 * IMPORTANT: This is a migration artifact. It does NOT implement full Prisma
 * semantics (nested writes, raw SQL, complex relations). It covers the ~30 most
 * common table+method combinations actually used in the codebase.
 *
 * Type safety: Uses `any` throughout because the proxy must match Prisma Client's
 * dynamic API surface. Routes already compile against Prisma types; this adapter
 * replaces the runtime, not the type system.
 */

import { convexQuery, convexMutation } from './convex-client';
import type { ConvexDoc } from './adapters/base';

// ─── Helpers ──────────────────────────────────────────────────────────

/** Strip `_id` / `_creationTime` and add Prisma-style `id` field */
function convexToPrisma(doc: any): any {
  if (!doc) return doc;
  if (Array.isArray(doc)) return doc.map(convexToPrisma);
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const { _id, _creationTime: _ct, ...rest } = doc;
  return { id: _id, ...rest };
}

/** Build a simple equality-filter predicate from a Prisma `where` clause */
function matchesWhere(doc: Record<string, unknown>, where: Record<string, unknown>): boolean {
  for (const [key, val] of Object.entries(where)) {
    if (val === undefined) continue;

    // Prisma logical combinators: OR (any) / AND (all) / NOT (none)
    if (key === 'OR' && Array.isArray(val)) {
      const anyMatch = (val as Record<string, unknown>[]).some((cond) =>
        matchesWhere(doc, cond),
      );
      if (!anyMatch) return false;
      continue;
    }
    if (key === 'AND' && Array.isArray(val)) {
      const allMatch = (val as Record<string, unknown>[]).every((cond) =>
        matchesWhere(doc, cond),
      );
      if (!allMatch) return false;
      continue;
    }
    if (key === 'NOT' && Array.isArray(val)) {
      const noneMatch = (val as Record<string, unknown>[]).every(
        (cond) => !matchesWhere(doc, cond),
      );
      if (!noneMatch) return false;
      continue;
    }

    const docVal = doc[key];

    // Handle Prisma operators: { in: [...] }, { contains: '...' }, { not: ... }, etc.
    if (val !== null && typeof val === 'object' && !Array.isArray(val)) {
      const op = val as Record<string, unknown>;
      if ('in' in op) {
        if (!(op.in as unknown[]).includes(docVal)) return false;
        continue;
      }
      if ('notIn' in op) {
        if ((op.notIn as unknown[]).includes(docVal)) return false;
        continue;
      }
      if ('contains' in op) {
        const mode = op.mode === 'insensitive' ? 'i' : '';
        const re = new RegExp(String(op.contains), mode);
        if (!re.test(String(docVal ?? ''))) return false;
        continue;
      }
      if ('startsWith' in op) {
        if (!String(docVal ?? '').startsWith(String(op.startsWith))) return false;
        continue;
      }
      if ('endsWith' in op) {
        if (!String(docVal ?? '').endsWith(String(op.endsWith))) return false;
        continue;
      }
      if ('not' in op) {
        if (docVal === op.not) return false;
        continue;
      }
      if ('gt' in op) {
        if (!((docVal as number) > (op.gt as number))) return false;
        continue;
      }
      if ('gte' in op) {
        if (!((docVal as number) >= (op.gte as number))) return false;
        continue;
      }
      if ('lt' in op) {
        if (!((docVal as number) < (op.lt as number))) return false;
        continue;
      }
      if ('lte' in op) {
        if (!((docVal as number) <= (op.lte as number))) return false;
        continue;
      }
      if ('equals' in op) {
        if (docVal !== op.equals) return false;
        continue;
      }
    }

    // Direct equality
    if (docVal !== val) return false;
  }
  return true;
}

/** Apply Prisma-style orderBy to an array */
function applyOrderBy(
  arr: Record<string, unknown>[],
  orderBy: Record<string, string> | Record<string, string>[],
): Record<string, unknown>[] {
  if (!orderBy) return arr;
  const entries = Array.isArray(orderBy) ? orderBy : [orderBy];
  return [...arr].sort((a, b) => {
    for (const entry of entries) {
      for (const [key, dir] of Object.entries(entry)) {
        const direction = dir === 'desc' ? -1 : 1;
        if ((a[key] as string) < (b[key] as string)) return -1 * direction;
        if ((a[key] as string) > (b[key] as string)) return 1 * direction;
      }
    }
    return 0;
  });
}

/** Apply Prisma `select` to a document (pick specified fields) */
function applySelect(
  doc: Record<string, unknown> | null,
  select: Record<string, boolean>,
): Record<string, unknown> | null {
  if (!select || !doc) return doc;
  const result: Record<string, unknown> = {};
  for (const [key, val] of Object.entries(select)) {
    if (val) result[key] = doc[key];
  }
  // Always include id
  if (doc.id !== undefined) result.id = doc.id;
  return result;
}

// ─── Generic CRUD adapter for any Convex table ──────────────────────

/**
 * Configuration for mapping a Prisma table to Convex functions.
 *
 * `listFn` — query function that returns all/filtered rows
 * `getByIdFn` — query function that fetches by Convex document ID
 * `createFn` — mutation to insert a row
 * `updateFn` — mutation to patch a row (expects `{ id, ...fields }`)
 * `deleteFn` — mutation to remove a row (expects `{ id }`)
 * `lookupFns` — map of field name to specific Convex query that looks up by that field
 */
interface TableConfig {
  tableName: string;
  listFn?: any;
  getByIdFn?: any;
  createFn?: any;
  updateFn?: any;
  deleteFn?: any;
  lookupFns?: Record<string, any>;
}

function createTableProxy(config: TableConfig) {
  const {
    tableName,
    listFn,
    getByIdFn,
    createFn,
    updateFn,
    deleteFn,
    lookupFns = {},
  } = config;

  /**
   * Resolve a record from a Prisma `where` clause.
   * Tries indexed lookups first, falls back to scanning the list.
   */
  async function resolveByWhere(where: Record<string, unknown>): Promise<Record<string, unknown> | null> {
    if (!where) return null;

    // 1. By Convex _id (Prisma `id` field)
    if (where.id && getByIdFn) {
      try {
        const doc = await convexQuery(getByIdFn, { id: where.id });
        return doc ? convexToPrisma(doc as Record<string, unknown>) : null;
      } catch {
        // ID format mismatch — fall through
      }
    }

    // 2. By dedicated lookup function (e.g. getUserByEmail)
    for (const [field, fn] of Object.entries(lookupFns)) {
      if (where[field] !== undefined) {
        try {
          const doc = await convexQuery(fn, { [field]: where[field] });
          return doc ? convexToPrisma(doc as Record<string, unknown>) : null;
        } catch {
          // fallthrough
        }
      }
    }

    // 3. Scan via list
    if (listFn) {
      const all = await fetchAll(where);
      const match = all.find((doc) => matchesWhere(doc, where));
      return match || null;
    }

    return null;
  }

  /**
   * Fetch all records, optionally filtering via `where` fields that match
   * Convex query arguments (e.g. userId, organizationId, status).
   */
  async function fetchAll(where?: Record<string, unknown>): Promise<Record<string, unknown>[]> {
    if (!listFn) return [];
    try {
      // Build Convex args from the where clause — pass only recognized simple fields
      const args: Record<string, unknown> = {};
      if (where) {
        for (const [key, val] of Object.entries(where)) {
          if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
            args[key] = val;
          }
        }
      }
      const result = await convexQuery<Record<string, unknown> | Record<string, unknown>[]>(listFn, args);
      if (!result) return [];
      // Some queries return { items: [...], total } or { customers: [...], total }
      let rows: unknown[];
      if (Array.isArray(result)) {
        rows = result;
      } else {
        rows = (
          (result as Record<string, unknown>).items ||
          (result as Record<string, unknown>).customers ||
          (result as Record<string, unknown>).bookings ||
          (result as Record<string, unknown>).leads ||
          []
        ) as unknown[];
      }
      return rows.map((r) => convexToPrisma(r as Record<string, unknown>));
    } catch {
      return [];
    }
  }

  /** Strip the `id` key from a data object (Convex generates _id).
   *  If `id` was present, preserve it as `prismaId` so it can be looked up later. */
  function stripId(data: Record<string, unknown>): Record<string, unknown> {
    const rest = { ...data };
    const legacyId = rest.id;
    delete rest.id;
    // Preserve legacy Prisma ID as prismaId for cross-system lookups
    if (legacyId && !rest.prismaId) {
      rest.prismaId = legacyId;
    }
    // Convert Date objects to epoch ms (Convex requires float64, not Date/string)
    for (const [key, value] of Object.entries(rest)) {
      if (value instanceof Date) {
        rest[key] = (value as Date).getTime();
      }
    }
    return rest;
  }

  return {
    /**
     * findUnique — Find a single record by unique constraint
     */
    findUnique: async (args: {
      where: Record<string, unknown>;
      select?: Record<string, boolean>;
      include?: Record<string, unknown>;
    }): Promise<Record<string, unknown> | null> => {
      const doc = await resolveByWhere(args.where);
      return args.select ? applySelect(doc, args.select) : doc;
    },

    /**
     * findFirst — Find the first matching record
     */
    findFirst: async (args?: {
      where?: Record<string, unknown>;
      orderBy?: Record<string, string> | Record<string, string>[];
      select?: Record<string, boolean>;
      include?: Record<string, unknown>;
    }): Promise<Record<string, unknown> | null> => {
      if (!args?.where) {
        const all = await fetchAll();
        return all[0] ?? null;
      }

      // Try direct lookup first (faster)
      const direct = await resolveByWhere(args.where);
      if (direct) {
        return args.select ? applySelect(direct, args.select) : direct;
      }

      // Fall back to filtered scan
      const all = await fetchAll(args.where);
      let filtered = all.filter((doc) => matchesWhere(doc, args.where ?? {}));
      if (args.orderBy) filtered = applyOrderBy(filtered, args.orderBy);
      const doc = filtered[0] ?? null;
      return args.select ? applySelect(doc, args.select) : doc;
    },

    /**
     * findMany — Fetch multiple records with filtering/ordering/pagination
     */
    findMany: async (args?: {
      where?: Record<string, unknown>;
      take?: number;
      skip?: number;
      orderBy?: Record<string, string> | Record<string, string>[];
      select?: Record<string, boolean>;
      include?: Record<string, unknown>;
      cursor?: Record<string, unknown>;
      distinct?: string[];
    }): Promise<Record<string, unknown>[]> => {
      let all = await fetchAll(args?.where);

      // Client-side where filter for fields not handled by the Convex query
      if (args?.where) {
        all = all.filter((doc) => matchesWhere(doc, args.where!));
      }

      if (args?.orderBy) all = applyOrderBy(all, args.orderBy);
      if (args?.skip) all = all.slice(args.skip);
      if (args?.take) all = all.slice(0, args.take);
      if (args?.select) all = all.map((doc) => applySelect(doc, args.select!) as Record<string, unknown>);

      return all;
    },

    /**
     * create — Insert a new record
     */
    create: async (args: {
      data: Record<string, unknown>;
      select?: Record<string, boolean>;
    }): Promise<Record<string, unknown>> => {
      if (!createFn) {
        throw new Error(`[prisma-convex-adapter] No create mutation for ${tableName}`);
      }
      const data = stripId(args.data);
      const result = await convexMutation(createFn, data);
      // Convex mutations return the new document ID; fetch the full doc
      if (typeof result === 'string') {
        if (getByIdFn) {
          try {
            const doc = await convexQuery(getByIdFn, { id: result });
            return doc ? convexToPrisma(doc as Record<string, unknown>) : { id: result, ...data };
          } catch {
            return { id: result, ...data };
          }
        }
        return { id: result, ...data };
      }
      return result ? convexToPrisma(result as Record<string, unknown>) : { ...data };
    },

    /**
     * update — Patch an existing record
     */
    update: async (args: {
      where: Record<string, unknown>;
      data: Record<string, unknown>;
      select?: Record<string, boolean>;
    }): Promise<Record<string, unknown>> => {
      if (!updateFn) {
        throw new Error(`[prisma-convex-adapter] No update mutation for ${tableName}`);
      }
      const existing = await resolveByWhere(args.where);
      if (!existing) {
        throw new Error(`[prisma-convex-adapter] Record not found in ${tableName}`);
      }
      const data = stripId(args.data);
      // Resolve Prisma atomic operators (e.g. { increment: 1 }) to plain values
      for (const [key, val] of Object.entries(data)) {
        if (val && typeof val === 'object' && !Array.isArray(val) && !(val instanceof Date)) {
          const obj = val as Record<string, unknown>;
          if ('increment' in obj && typeof obj.increment === 'number') {
            data[key] = ((existing[key] as number) || 0) + obj.increment;
          } else if ('decrement' in obj && typeof obj.decrement === 'number') {
            data[key] = ((existing[key] as number) || 0) - obj.decrement;
          } else if ('set' in obj) {
            data[key] = obj.set;
          }
        }
      }
      const result = await convexMutation(updateFn, { id: existing.id, ...data });
      // Re-fetch after update
      if (getByIdFn) {
        try {
          const updated = await convexQuery(getByIdFn, { id: existing.id });
          return updated ? convexToPrisma(updated as Record<string, unknown>) : { ...existing, ...data };
        } catch {
          return { ...existing, ...data };
        }
      }
      return result ? convexToPrisma(result as Record<string, unknown>) : { ...existing, ...data };
    },

    /**
     * upsert — Create or update
     */
    upsert: async (args: {
      where: Record<string, unknown>;
      create: Record<string, unknown>;
      update: Record<string, unknown>;
      select?: Record<string, boolean>;
    }): Promise<Record<string, unknown>> => {
      const existing = await resolveByWhere(args.where);
      if (existing) {
        if (!updateFn) return existing;
        const data = stripId(args.update);
        await convexMutation(updateFn, { id: existing.id, ...data });
        if (getByIdFn) {
          try {
            const updated = await convexQuery(getByIdFn, { id: existing.id });
            return updated ? convexToPrisma(updated as Record<string, unknown>) : { ...existing, ...data };
          } catch {
            return { ...existing, ...data };
          }
        }
        return { ...existing, ...data };
      }
      // Create
      if (!createFn) {
        throw new Error(`[prisma-convex-adapter] No create mutation for ${tableName}`);
      }
      const createData = stripId(args.create);
      const result = await convexMutation(createFn, createData);
      if (typeof result === 'string' && getByIdFn) {
        try {
          const doc = await convexQuery(getByIdFn, { id: result });
          return doc ? convexToPrisma(doc as Record<string, unknown>) : { id: result, ...createData };
        } catch {
          return { id: result, ...createData };
        }
      }
      return result ? convexToPrisma(result as Record<string, unknown>) : { ...createData };
    },

    /**
     * delete — Remove a record
     */
    delete: async (args: {
      where: Record<string, unknown>;
    }): Promise<Record<string, unknown>> => {
      if (!deleteFn) {
        throw new Error(`[prisma-convex-adapter] No delete mutation for ${tableName}`);
      }
      const existing = await resolveByWhere(args.where);
      if (!existing) {
        throw new Error(`[prisma-convex-adapter] Record not found in ${tableName}`);
      }
      await convexMutation(deleteFn, { id: existing.id });
      return existing;
    },

    /**
     * count — Count matching records
     */
    count: async (args?: {
      where?: Record<string, unknown>;
    }): Promise<number> => {
      let all = await fetchAll(args?.where);
      if (args?.where) {
        all = all.filter((doc) => matchesWhere(doc, args.where!));
      }
      return all.length;
    },

    /**
     * createMany — Batch insert (sequential for now)
     */
    createMany: async (args: {
      data: Record<string, unknown>[];
      skipDuplicates?: boolean;
    }): Promise<{ count: number }> => {
      if (!createFn) {
        throw new Error(`[prisma-convex-adapter] No create mutation for ${tableName}`);
      }
      let count = 0;
      for (const row of args.data) {
        try {
          const data = stripId(row);
          await convexMutation(createFn, data);
          count++;
        } catch (err) {
          if (!args.skipDuplicates) throw err;
        }
      }
      return { count };
    },

    /**
     * updateMany — Batch update
     */
    updateMany: async (args: {
      where: Record<string, unknown>;
      data: Record<string, unknown>;
    }): Promise<{ count: number }> => {
      if (!updateFn) return { count: 0 };
      let all = await fetchAll(args.where);
      all = all.filter((doc) => matchesWhere(doc, args.where));
      let count = 0;
      for (const doc of all) {
        try {
          const data = stripId(args.data);
          await convexMutation(updateFn, { id: doc.id, ...data });
          count++;
        } catch {
          // skip failures
        }
      }
      return { count };
    },

    /**
     * deleteMany — Batch delete
     */
    deleteMany: async (args?: {
      where?: Record<string, unknown>;
    }): Promise<{ count: number }> => {
      if (!deleteFn) return { count: 0 };
      let all = await fetchAll(args?.where);
      if (args?.where) {
        all = all.filter((doc) => matchesWhere(doc, args.where!));
      }
      let count = 0;
      for (const doc of all) {
        try {
          await convexMutation(deleteFn, { id: doc.id });
          count++;
        } catch {
          // skip
        }
      }
      return { count };
    },

    /**
     * aggregate — Minimal stub (few routes use this)
     */
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    aggregate: async (_args: Record<string, unknown>): Promise<Record<string, unknown>> => {
      return { _count: { _all: 0 }, _sum: {}, _avg: {}, _min: {}, _max: {} };
    },

    /**
     * groupBy — Minimal stub (only user_sessions / login_history use this)
     */
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    groupBy: async (_args: Record<string, unknown>): Promise<Record<string, unknown>[]> => {
      return [];
    },
  };
}

// ─── Read-only stub for tables with no mutations ────────────────────

type TableProxy = ReturnType<typeof createTableProxy>;

function createReadOnlyProxy(
  tableName: string,
  listFn?: any,
  getByIdFn?: any,
): TableProxy {
  const proxy = createTableProxy({ tableName, listFn, getByIdFn });
  // Override write methods to no-op gracefully
  proxy.create = async () => {
    console.warn(`[prisma-convex-adapter] ${tableName}.create — no mutation available, returning stub`);
    return {};
  };
  proxy.update = async () => {
    console.warn(`[prisma-convex-adapter] ${tableName}.update — no mutation available, returning stub`);
    return {};
  };
  proxy.delete = async () => {
    console.warn(`[prisma-convex-adapter] ${tableName}.delete — no mutation available, returning stub`);
    return {};
  };
  return proxy;
}

// ─── Table Registry ─────────────────────────────────────────────────
// Maps every Prisma model name used in the codebase to a Convex-backed proxy.

export function buildConvexPrismaProxy(): any {
  // ── Auth domain ──────────────────────────────────────────────────
  const users = createTableProxy({
    tableName: 'users',
    listFn: null, // No generic list — routes use findUnique/findFirst
    getByIdFn: "auth/queries:getUser",
    createFn: "auth/mutations:createUser",
    updateFn: "auth/mutations:updateUser",
    deleteFn: "auth/mutations:deleteUser",
    lookupFns: {
      email: "auth/queries:getUserByEmail",
      clerkId: "auth/queries:getUserByClerkId",
      googleId: "auth/queries:getUserByGoogleId",
    },
  });

  const user_sessions = createTableProxy({
    tableName: 'user_sessions',
    listFn: "auth/queries:listUserSessions",
    getByIdFn: null,
    createFn: "auth/mutations:createSession",
    updateFn: null,
    deleteFn: "auth/mutations:deleteSession",
  });

  const login_history = createTableProxy({
    tableName: 'login_history',
    listFn: "auth/queries:getLoginHistory",
    getByIdFn: null,
    createFn: "auth/mutations:createLoginHistory",
  });

  const user_preferences = createTableProxy({
    tableName: 'user_preferences',
    listFn: null,
    getByIdFn: null,
    createFn: "auth/mutations:upsertUserPreferences",
    updateFn: "auth/mutations:upsertUserPreferences",
    lookupFns: {
      userId: "auth/queries:getUserPreferences",
    },
  });

  const user_links = createTableProxy({
    tableName: 'user_links',
    listFn: "auth/queries:getUserLinks",
    getByIdFn: null,
  });

  const audit_logs = createTableProxy({
    tableName: 'audit_logs',
    listFn: "auth/queries:getAuditLogs",
    getByIdFn: null,
    createFn: "auth/mutations:createAuditLog",
  });

  const onboarding_progress = createTableProxy({
    tableName: 'onboarding_progress',
    listFn: null,
    getByIdFn: null,
    createFn: "auth/mutations:createOnboardingProgress",
    updateFn: "auth/mutations:updateOnboardingProgress",
    lookupFns: {
      userId: "auth/queries:getOnboardingProgress",
    },
  });

  // ── Organizations domain ─────────────────────────────────────────
  const organizations = createTableProxy({
    tableName: 'organizations',
    listFn: null,
    getByIdFn: "organizations/queries:getOrganization",
    createFn: "organizations/mutations:createOrganization",
    updateFn: null, // No generic update mutation — specific routes handle this
    lookupFns: {
      ownerId: "organizations/queries:getByOwnerId",
    },
  });

  const organization_members = createTableProxy({
    tableName: 'organization_members',
    listFn: "organizations/queries:getMembers",
    getByIdFn: null,
    createFn: "organizations/mutations:addMember",
    deleteFn: "organizations/mutations:removeMember",
    lookupFns: {
      userId: "auth/queries:getUserOrganizationMembership",
    },
  });

  const organization_invitations = createTableProxy({
    tableName: 'organization_invitations',
    listFn: "organizations/queries:getInvitations",
    getByIdFn: null,
  });

  // ── Billing domain ───────────────────────────────────────────────
  const subscriptions = createTableProxy({
    tableName: 'subscriptions',
    listFn: null,
    getByIdFn: null,
    createFn: "billing/mutations:createSubscription",
    updateFn: "billing/mutations:updateSubscription",
    lookupFns: {
      userId: "billing/queries:getSubscription",
      stripeSubscriptionId: "billing/queries:getByStripeSubscriptionId",
      stripeCustomerId: "billing/queries:getByStripeCustomerId",
    },
  });

  const billing_history = createTableProxy({
    tableName: 'billing_history',
    listFn: "billing/queries:getBillingHistory",
    getByIdFn: null,
    createFn: "billing/mutations:recordPayment",
  });

  const payments = createTableProxy({
    tableName: 'payments',
    listFn: "billing/queries:getPayments",
    getByIdFn: null,
    lookupFns: {
      stripePaymentIntentId: "billing/queries:getByStripePaymentIntentId",
    },
  });

  const refunds = createTableProxy({
    tableName: 'refunds',
    listFn: null,
    getByIdFn: null,
    createFn: "billing/mutations:processRefund",
    updateFn: "billing/mutations:updateRefund",
    lookupFns: {
      stripeRefundId: "billing/queries:getByStripeRefundId",
      stripeChargeId: "billing/queries:getByStripeChargeId",
    },
  });

  // ── Bookings domain ──────────────────────────────────────────────
  const bookings = createTableProxy({
    tableName: 'bookings',
    listFn: "bookings/queries:getBookings",
    getByIdFn: "bookings/queries:getById",
    createFn: "bookings/mutations:create",
    updateFn: "bookings/mutations:updateDepositInfo",
    deleteFn: "bookings/mutations:cancel",
    lookupFns: {
      id: "bookings/queries:getByPrismaId",
    },
  });

  const demo_bookings = createTableProxy({
    tableName: 'demo_bookings',
    listFn: "bookings/queries:getDemoBookings",
    getByIdFn: null,
    createFn: "bookings/mutations:createDemoBooking",
    updateFn: "bookings/mutations:updateDemoBookingCalendarData",
  });

  const booking_notes = createTableProxy({
    tableName: 'booking_notes',
    listFn: null,
    getByIdFn: null,
    createFn: "bookings/mutations:addNote",
  });

  // ── Customers domain ─────────────────────────────────────────────
  const customers = {
    ...createTableProxy({
      tableName: 'customers',
      listFn: "customers/queries:getCustomers",
      getByIdFn: "customers/queries:getCustomerById",
      createFn: "customers/mutations:createCustomer",
      updateFn: "customers/mutations:updateCustomer",
      deleteFn: "customers/mutations:deleteCustomer",
    }),
    // Override findFirst to handle compound userId+phone lookup
    findFirst: async (args?: { where?: Record<string, unknown> }) => {
      const where = args?.where ?? {};
      if (where.phone && where.userId) {
        const doc = await convexQuery<ConvexDoc | null>(
          "customers/queries:getByPhone",
          { userId: where.userId as string, phone: where.phone as string }
        );
        return doc ? convexToPrisma(doc) : null;
      }
      if (where.phone) {
        const doc = await convexQuery<ConvexDoc | null>(
          "customers/queries:getByPhone",
          { userId: '', phone: where.phone as string }
        );
        return doc ? convexToPrisma(doc) : null;
      }
      return null;
    },
  };

  // ── Notifications domain ─────────────────────────────────────────
  const notifications = createTableProxy({
    tableName: 'notifications',
    listFn: "notifications/queries:getNotifications",
    getByIdFn: null,
    createFn: "notifications/mutations:create",
    updateFn: "notifications/mutations:markRead",
    deleteFn: "notifications/mutations:deleteNotification",
  });

  const admin_notifications = createTableProxy({
    tableName: 'admin_notifications',
    listFn: "notifications/queries:getAdminNotifications",
    getByIdFn: null,
  });

  // ── Support domain ───────────────────────────────────────────────
  const support_tickets = createTableProxy({
    tableName: 'support_tickets',
    listFn: "support/queries:getTickets",
    getByIdFn: "support/queries:getTicketById",
    createFn: "support/mutations:createTicket",
    updateFn: "support/mutations:updateTicket",
  });

  const ticket_messages = createTableProxy({
    tableName: 'ticket_messages',
    listFn: "support/queries:getMessages",
    getByIdFn: null,
    createFn: "support/mutations:addMessage",
  });

  const support_faqs = createTableProxy({
    tableName: 'support_faqs',
    listFn: "support/queries:getFAQs",
    getByIdFn: null,
  });

  const contact_submissions = createTableProxy({
    tableName: 'contact_submissions',
    listFn: null,
    getByIdFn: null,
    createFn: "support/mutations:createContactSubmission",
  });

  // ── Content domain ───────────────────────────────────────────────
  const events = createTableProxy({
    tableName: 'events',
    listFn: "content/queries:getHeroSections",
    getByIdFn: null,
    createFn: "content/mutations:createContent",
    updateFn: "content/mutations:updateContent",
  });

  const announcements = createReadOnlyProxy(
    'announcements',
    "content/queries:getAnnouncements",
  );

  const media_assets = createReadOnlyProxy('media_assets');

  // ── Leads domain ─────────────────────────────────────────────────
  const leads = createTableProxy({
    tableName: 'leads',
    listFn: "leads/queries:getLeads",
    getByIdFn: "leads/queries:getById",
    createFn: "leads/mutations:createLead",
    updateFn: "leads/mutations:updateLead",
    deleteFn: "leads/mutations:deleteLead",
  });

  // ── Quotations domain ────────────────────────────────────────────
  const quotations = createTableProxy({
    tableName: 'quotations',
    listFn: "quotations/queries:getQuotations",
    getByIdFn: "quotations/queries:getById",
    createFn: "quotations/mutations:createQuotation",
    updateFn: "quotations/mutations:updateQuotation",
  });

  const quotation_items = createTableProxy({
    tableName: 'quotation_items',
    listFn: "quotations/queries:getItems",
    getByIdFn: null,
    createFn: "quotations/mutations:addItem",
    deleteFn: "quotations/mutations:removeItem",
  });

  const quotation_payments = createTableProxy({
    tableName: 'quotation_payments',
    listFn: "quotations/queries:getPayments",
    getByIdFn: null,
    createFn: "quotations/mutations:recordPayment",
  });

  const quotation_recurring_templates = createReadOnlyProxy('quotation_recurring_templates');
  const quotation_reminders = createReadOnlyProxy('quotation_reminders');
  const quotation_settings = createReadOnlyProxy('quotation_settings');

  // ── Reviews domain ───────────────────────────────────────────────
  const reviews = createTableProxy({
    tableName: 'reviews',
    listFn: "reviews/queries:getReviews",
    getByIdFn: null,
    createFn: "reviews/mutations:createReview",
  });

  const review_requests = createTableProxy({
    tableName: 'review_requests',
    listFn: "reviews/queries:getReviewRequests",
    getByIdFn: null,
    createFn: "reviews/mutations:createReviewRequest",
  });

  const review_question_templates = createReadOnlyProxy('review_question_templates');

  const review_widget_settings = createTableProxy({
    tableName: 'review_widget_settings',
    listFn: null,
    getByIdFn: null,
    createFn: "reviews/mutations:updateWidgetSettings",
    updateFn: "reviews/mutations:updateWidgetSettings",
    lookupFns: {
      userId: "reviews/queries:getWidgetSettings",
    },
  });

  // ── Expenses domain ──────────────────────────────────────────────
  const expenses = createTableProxy({
    tableName: 'expenses',
    listFn: "expenses/queries:getExpenses",
    getByIdFn: null,
    createFn: "expenses/mutations:createExpense",
    updateFn: "expenses/mutations:updateExpense",
    deleteFn: "expenses/mutations:deleteExpense",
  });

  // ── Social domain ────────────────────────────────────────────────
  const social_accounts = createTableProxy({
    tableName: 'social_accounts',
    listFn: "social/queries:getAccounts",
    getByIdFn: null,
    createFn: "social/mutations:connectAccount",
    deleteFn: "social/mutations:disconnectAccount",
  });

  const social_posts = createTableProxy({
    tableName: 'social_posts',
    listFn: null,
    getByIdFn: null,
    createFn: "social/mutations:createPost",
    updateFn: "social/mutations:updatePostStatus",
  });

  const social_content_queue = createTableProxy({
    tableName: 'social_content_queue',
    listFn: "social/queries:getContentQueue",
    getByIdFn: null,
    createFn: "social/mutations:schedulePost",
  });

  const social_messages = createTableProxy({
    tableName: 'social_messages',
    listFn: "social/queries:getMessages",
    getByIdFn: null,
    updateFn: "social/mutations:markMessageRead",
  });

  const social_trends = createReadOnlyProxy(
    'social_trends',
    "social/queries:getTrends",
  );

  // ── Careers domain ───────────────────────────────────────────────
  const jobs = createTableProxy({
    tableName: 'jobs',
    listFn: "careers/queries:getJobs",
    getByIdFn: null,
    createFn: "careers/mutations:createJob",
    updateFn: "careers/mutations:updateJobStatus",
  });

  const job_applications = createTableProxy({
    tableName: 'job_applications',
    listFn: "careers/queries:getApplications",
    getByIdFn: null,
    createFn: "careers/mutations:createApplication",
  });

  const interview_bookings = createTableProxy({
    tableName: 'interview_bookings',
    listFn: "careers/queries:getInterviewBookings",
    getByIdFn: null,
  });

  const interview_booking_tokens = createReadOnlyProxy('interview_booking_tokens');

  // ── WhatsApp domain ──────────────────────────────────────────────
  const whatsapp_conversations = createTableProxy({
    tableName: 'whatsapp_conversations',
    listFn: "whatsapp/queries:getConversations",
    getByIdFn: null,
    createFn: "whatsapp/mutations:createConversation",
  });

  const org_whatsapp_conversations = {
    ...createTableProxy({
      tableName: 'org_whatsapp_conversations',
      listFn: "whatsapp/queries:getOrgConversations",
      getByIdFn: "whatsapp/queries:getOrgConversationById",
      createFn: "whatsapp/mutations:createOrgConversation",
      updateFn: "whatsapp/mutations:updateOrgConversation",
    }),
    // Override findFirst to support organizationId+userPhone compound lookup
    findFirst: async (args?: { where?: Record<string, unknown> }) => {
      const where = args?.where ?? {};
      if (where.organizationId && where.userPhone) {
        const docs = await convexQuery<ConvexDoc[]>(
          "whatsapp/queries:getOrgConversations",
          { organizationId: where.organizationId as string, userPhone: where.userPhone as string },
        );
        const doc = docs?.[0] ?? null;
        return doc ? convexToPrisma(doc) : null;
      }
      if (where.organizationId) {
        const docs = await convexQuery<ConvexDoc[]>(
          "whatsapp/queries:getOrgConversations",
          { organizationId: where.organizationId as string },
        );
        return docs?.[0] ? convexToPrisma(docs[0]) : null;
      }
      return null;
    },
  };

  const org_whatsapp_messages = createTableProxy({
    tableName: 'org_whatsapp_messages',
    listFn: "whatsapp/queries:getOrgMessages",
    getByIdFn: null,
    createFn: "whatsapp/mutations:sendOrgMessage",
  });

  // Alias: some routes use `whatsAppConversation` (camelCase Prisma model)
  const whatsAppConversation = whatsapp_conversations;

  // `conversationLog` maps to whatsapp queries too
  const conversationLog = createTableProxy({
    tableName: 'conversation_logs',
    listFn: "whatsapp/queries:getConversations",
    getByIdFn: null,
    createFn: "whatsapp/mutations:createConversation",
    updateFn: "whatsapp/mutations:updateConversationStatus",
  });

  const conversation_messages = createTableProxy({
    tableName: 'conversation_messages',
    listFn: "whatsapp/queries:getMessages",
    getByIdFn: null,
    createFn: "whatsapp/mutations:sendMessage",
  });

  // ── Real Estate domain ───────────────────────────────────────────
  const property = createTableProxy({
    tableName: 'property',
    listFn: "real_estate/queries:getProperties",
    getByIdFn: "real_estate/queries:getPropertyById",
    createFn: "real_estate/mutations:createProperty",
    updateFn: "real_estate/mutations:updateProperty",
  });

  const propertyViewing = createTableProxy({
    tableName: 'property_viewing',
    listFn: "real_estate/queries:getViewings",
    getByIdFn: null,
    createFn: "real_estate/mutations:scheduleViewing",
    updateFn: "real_estate/mutations:updateViewingStatus",
  });

  const broker = createTableProxy({
    tableName: 'broker',
    listFn: "real_estate/queries:getBrokers",
    getByIdFn: null,
  });

  const realEstateTransaction = createReadOnlyProxy('real_estate_transaction');
  const clientRequirements = createReadOnlyProxy('client_requirements');
  const propertyImage = createReadOnlyProxy('property_image');
  const propertyMatch = createReadOnlyProxy('property_match');
  const brokerAvailability = createReadOnlyProxy('broker_availability');
  const viewingFeedback = createReadOnlyProxy('viewing_feedback');
  const viewingTour = createReadOnlyProxy('viewing_tour');

  // ── Voice Agents domain ──────────────────────────────────────────
  const voiceAgentPhoneConfig = createTableProxy({
    tableName: 'voice_agent_phone_config',
    listFn: null,
    getByIdFn: null,
    createFn: "voice_agents/mutations:createPhoneConfig",
    updateFn: "voice_agents/mutations:updatePhoneConfig",
    lookupFns: {
      organizationId: "voice_agents/queries:getPhoneConfig",
    },
  });

  const knowledgeBaseDocument = createTableProxy({
    tableName: 'knowledge_base_documents',
    listFn: "voice_agents/queries:getKBDocuments",
    getByIdFn: "voice_agents/queries:getKBDocumentById",
    createFn: "voice_agents/mutations:createKBDocument",
    updateFn: "voice_agents/mutations:updateKBDocument",
    deleteFn: "voice_agents/mutations:deleteKBDocument",
  });

  const knowledgeBaseSettings = createTableProxy({
    tableName: 'knowledge_base_settings',
    listFn: null,
    getByIdFn: null,
    createFn: "voice_agents/mutations:createKBSettings",
    updateFn: "voice_agents/mutations:updateKBSettings",
    lookupFns: {
      userId: "voice_agents/queries:getKBSettingsByUserId",
    },
  });

  const knowledgeBaseChunk = createTableProxy({
    tableName: 'knowledge_base_chunks',
    listFn: null,
    getByIdFn: null,
    createFn: "voice_agents/mutations:createKBChunk",
  });

  const knowledgeBaseFAQ = createTableProxy({
    tableName: 'knowledge_base_faqs',
    listFn: null,
    getByIdFn: null,
    createFn: "voice_agents/mutations:createKBFAQ",
    updateFn: "voice_agents/mutations:updateKBFAQ",
    deleteFn: "voice_agents/mutations:deleteKBFAQ",
  });
  // company_knowledge_base is backed by a real Convex query so the WhatsApp
  // AI engine can retrieve FAQ/knowledge entries for an organization. Seeding
  // uses whatsapp/mutations:seedCompanyKnowledgeBase (see seed script /
  // /api/_dev/seed-kb). We do NOT expose create/update/delete through this
  // proxy — seeding is an administrative action that goes directly to Convex.
  const company_knowledge_base = createTableProxy({
    tableName: 'company_knowledge_base',
    listFn: "whatsapp/queries:getKnowledgeBaseEntries",
    getByIdFn: null,
  });

  // ── Analytics domain ─────────────────────────────────────────────
  const site_visits = createTableProxy({
    tableName: 'site_visits',
    listFn: "analytics/queries:getSiteVisits",
    getByIdFn: null,
    createFn: "analytics/mutations:recordVisit",
  });

  const workflowAnalytics = createTableProxy({
    tableName: 'workflow_analytics',
    listFn: "analytics/queries:getWorkflowAnalytics",
    getByIdFn: null,
    createFn: "analytics/mutations:updateMetrics",
    updateFn: "analytics/mutations:updateMetrics",
  });

  const workflowError = createReadOnlyProxy('workflow_errors');

  // ── Misc tables (stubs) ──────────────────────────────────────────
  const integrations = createReadOnlyProxy('integrations');
  const waitlists = createReadOnlyProxy('waitlists');
  const waitlist_signups = createReadOnlyProxy('waitlist_signups');
  const system_settings = createReadOnlyProxy('system_settings');
  const system_messages = createReadOnlyProxy('system_messages');
  const system_message_views = createReadOnlyProxy('system_message_views');
  const campaigns = createReadOnlyProxy('campaigns');
  const referrals = createReadOnlyProxy('referrals');
  const staff_alerts = createReadOnlyProxy('staff_alerts');
  const billing_emails = createReadOnlyProxy('billing_emails');
  const preview_tokens = createReadOnlyProxy('preview_tokens');
  const content_versions = createReadOnlyProxy('content_versions');
  const otps = createReadOnlyProxy('otps');
  const ai_content_generations = createReadOnlyProxy('ai_content_generations');
  const admin_notes = createReadOnlyProxy('admin_notes');
  const userWorkflow = createReadOnlyProxy('user_workflows');
  const userAPIKey = createReadOnlyProxy('user_api_keys');
  const notification = notifications; // alias used in some routes

  // ── Build the proxy object ───────────────────────────────────────

  const proxyObj: Record<string, unknown> = {
    // Auth
    users,
    user_sessions,
    login_history,
    user_preferences,
    user_links,
    audit_logs,
    onboarding_progress,

    // Organizations
    organizations,
    organization_members,
    organization_invitations,

    // Billing
    subscriptions,
    billing_history,
    payments,
    refunds,
    billing_emails,

    // Bookings
    bookings,
    demo_bookings,
    booking_notes,

    // Customers
    customers,

    // Notifications
    notifications,
    notification,
    admin_notifications,

    // Support
    support_tickets,
    ticket_messages,
    support_faqs,
    contact_submissions,

    // Content
    events,
    announcements,
    media_assets,
    preview_tokens,
    content_versions,
    ai_content_generations,

    // Leads
    leads,

    // Quotations
    quotations,
    quotation_items,
    quotation_payments,
    quotation_recurring_templates,
    quotation_reminders,
    quotation_settings,

    // Reviews
    reviews,
    review_requests,
    review_question_templates,
    review_widget_settings,

    // Expenses
    expenses,

    // Social
    social_accounts,
    social_posts,
    social_content_queue,
    social_messages,
    social_trends,

    // Careers
    jobs,
    job_applications,
    interview_bookings,
    interview_booking_tokens,

    // WhatsApp
    whatsapp_conversations,
    whatsAppConversation,
    org_whatsapp_conversations,
    org_whatsapp_messages,
    user_language_preferences: {
      ...createTableProxy({
        tableName: 'user_language_preferences',
        listFn: null,
        getByIdFn: null,
        createFn: "whatsapp/mutations:upsertLanguagePreference",
        updateFn: "whatsapp/mutations:upsertLanguagePreference",
      }),
      findUnique: async (args: { where: Record<string, unknown> }) => {
        const w = args.where as Record<string, unknown>;
        const compound = w.organizationId_userPhone as Record<string, string> | undefined;
        const orgId = compound?.organizationId ?? w.organizationId as string;
        const phone = compound?.userPhone ?? w.userPhone as string;
        if (!orgId || !phone) return null;
        const doc = await convexQuery<ConvexDoc | null>(
          "whatsapp/queries:getLanguagePreference",
          { organizationId: orgId, userPhone: phone },
        );
        return doc ? convexToPrisma(doc) : null;
      },
      upsert: async (args: { where: Record<string, unknown>; create: Record<string, unknown>; update: Record<string, unknown> }) => {
        const data = { ...args.create, ...args.update };
        const w = args.where as Record<string, unknown>;
        const compound = w.organizationId_userPhone as Record<string, string> | undefined;
        await convexMutation("whatsapp/mutations:upsertLanguagePreference", {
          organizationId: compound?.organizationId ?? data.organizationId as string,
          userPhone: compound?.userPhone ?? data.userPhone as string,
          language: data.language as string,
        });
        return data;
      },
    },
    conversationLog,
    conversation_messages,

    // Real Estate
    property,
    propertyViewing,
    propertyImage,
    propertyMatch,
    broker,
    brokerAvailability,
    realEstateTransaction,
    clientRequirements,
    viewingFeedback,
    viewingTour,

    // Voice Agents
    voiceAgentPhoneConfig,
    knowledgeBaseDocument,
    knowledgeBaseSettings,
    knowledgeBaseChunk,
    knowledgeBaseFAQ,
    company_knowledge_base,

    // Analytics
    site_visits,
    workflowAnalytics,
    workflowError,

    // Misc
    integrations,
    waitlists,
    waitlist_signups,
    system_settings,
    system_messages,
    system_message_views,
    campaigns,
    referrals,
    staff_alerts,
    otps,
    admin_notes,
    userWorkflow,
    userAPIKey,

    // ── Prisma Client meta-methods ─────────────────────────────────
    $transaction: async (operations: unknown) => {
      // If passed an array of promises, resolve them in parallel
      if (Array.isArray(operations)) {
        return Promise.all(operations);
      }
      // If passed a function (interactive transaction), call it with the proxy
      if (typeof operations === 'function') {
        return (operations as (p: Record<string, unknown>) => unknown)(proxyObj);
      }
      return operations;
    },
    $connect: async () => {},
    $disconnect: async () => {},
    $queryRaw: async () => [],
    $queryRawUnsafe: async () => [],
    $executeRaw: async () => 0,
    $executeRawUnsafe: async () => 0,
    $on: () => {},
    $use: () => {},
    $extends: () => proxyObj,
  };

  // Return a Proxy that logs access to unknown tables (debugging aid)
  return new Proxy(proxyObj, {
    get(target, prop, receiver) {
      if (typeof prop === 'symbol' || prop in target) {
        return Reflect.get(target, prop, receiver);
      }
      // Unknown table — return a stub that logs warnings
      const stubTableName = String(prop);
      console.warn(
        `[prisma-convex-adapter] Access to unmapped table "${stubTableName}" — returning empty stub`,
      );
      return createReadOnlyProxy(stubTableName);
    },
  });
}
