import {
  Op,
  type Model,
  type ModelStatic,
  type WhereOptions,
} from 'sequelize';
import Utils from '@/db/utils';
import type { DbApiOptions } from '@/db/api/types';

/**
 * Shared generic-repository helpers for the `db/api` layer. They cover the
 * methods that are byte-identical across every entity (`remove`, `deleteByIds`)
 * and the single-field autocomplete, parameterized by the model. Entity-specific
 * methods (`create`/`update`/`bulkImport`/`findBy`/`findAll`) stay in each
 * repository.
 */

/** Finds a record by id and soft-deletes it (returns null when absent). */
export async function removeRecord<M extends Model>(
  model: ModelStatic<M>,
  id: string,
  options?: DbApiOptions,
): Promise<M | null> {
  const transaction = options?.transaction;
  const record = await model.findByPk(id, { transaction });

  if (!record) {
    return null;
  }

  await record.destroy({ transaction });
  return record;
}

/** Deletes every record whose id is in `ids` (within the caller's transaction). */
export async function deleteRecordsByIds<M extends Model>(
  model: ModelStatic<M>,
  ids: string[],
  options?: DbApiOptions,
): Promise<M[]> {
  const transaction = options?.transaction;
  const where: WhereOptions = { id: { [Op.in]: ids } };

  const records = await model.findAll({ where, transaction });

  for (const record of records) {
    await record.destroy({ transaction });
  }

  return records;
}

/**
 * Autocomplete over a single label column: `{ id, label }` rows matching `query`
 * (by id or case-insensitive substring), scoped to the tenant unless global.
 */
export async function autocompleteByField<M extends Model>(
  model: ModelStatic<M>,
  field: string,
  query: string | undefined,
  limit: number | undefined,
  offset: number | undefined,
  globalAccess: boolean,
  organizationId: string | undefined,
): Promise<Array<{ id: string; label: string | null }>> {
  let where: WhereOptions = {};

  if (!globalAccess && organizationId) {
    where = { organizationId };
  }

  if (query) {
    where = {
      [Op.or]: [{ id: Utils.uuid(query) }, Utils.ilike(model.name, field, query)],
    };
  }

  const records = await model.findAll({
    attributes: ['id', field],
    where,
    limit: limit ? Number(limit) : undefined,
    offset: offset ? Number(offset) : undefined,
    order: [[field, 'ASC']],
  });

  return records.map((record) => {
    const id = record.get('id');
    const label = record.get(field);
    return {
      id: typeof id === 'string' ? id : String(id),
      label: typeof label === 'string' ? label : null,
    };
  });
}
