import type { PrismaService } from "./prisma.service.js";

/**
 * Typed facade for Prisma model delegates.
 *
 * Uses `object` for findMany/count args to avoid `exactOptionalPropertyTypes`
 * incompatibility with Prisma's complex generic parameter types.
 */
interface PrismaDelegate<TEntity, TCreateInput, TUpdateInput, TWhereUnique, TWhere> {
  findUnique(args: { where: TWhereUnique }): Promise<TEntity | null>;
  findFirst(args: { where: TWhere }): Promise<TEntity | null>;
  findMany(args: object): Promise<TEntity[]>;
  create(args: { data: TCreateInput }): Promise<TEntity>;
  update(args: { where: TWhereUnique; data: TUpdateInput }): Promise<TEntity>;
  delete(args: { where: TWhereUnique }): Promise<TEntity>;
  count(args?: object): Promise<number>;
}

/**
 * Generic base repository for Prisma entities.
 *
 * Concrete repositories override `delegate` to return their Prisma model delegate.
 * The delegate is typed as `unknown` to avoid type incompatibilities with Prisma's
 * generated delegate types under `exactOptionalPropertyTypes`, then narrowed via
 * a typed facade in the private `d` getter.
 *
 * @typeParam TEntity - The entity type returned by queries
 * @typeParam TCreateInput - The input type for create operations
 * @typeParam TUpdateInput - The input type for update operations
 * @typeParam TWhereUnique - The unique identifier filter type
 * @typeParam TWhere - The general where filter type
 */
export abstract class BaseRepository<TEntity, TCreateInput, TUpdateInput, TWhereUnique, TWhere> {
  constructor(protected readonly prisma: PrismaService) {}

  protected abstract get delegate(): unknown;

  private get d(): PrismaDelegate<TEntity, TCreateInput, TUpdateInput, TWhereUnique, TWhere> {
    return this.delegate as PrismaDelegate<
      TEntity,
      TCreateInput,
      TUpdateInput,
      TWhereUnique,
      TWhere
    >;
  }

  async findById(where: TWhereUnique): Promise<TEntity | null> {
    return this.d.findUnique({ where });
  }

  async findOne(where: TWhere): Promise<TEntity | null> {
    return this.d.findFirst({ where });
  }

  async findMany(
    where?: TWhere,
    options?: { skip?: number; take?: number; orderBy?: Record<string, "asc" | "desc"> }
  ): Promise<TEntity[]> {
    return this.d.findMany({
      ...(where !== undefined && { where }),
      ...options,
    });
  }

  async create(data: TCreateInput): Promise<TEntity> {
    return this.d.create({ data });
  }

  async update(where: TWhereUnique, data: TUpdateInput): Promise<TEntity> {
    return this.d.update({ where, data });
  }

  async delete(where: TWhereUnique): Promise<TEntity> {
    return this.d.delete({ where });
  }

  async count(where?: TWhere): Promise<number> {
    if (where !== undefined) {
      return this.d.count({ where });
    }
    return this.d.count();
  }
}
