/* eslint-disable @typescript-eslint/no-explicit-any */
import at from "lodash/at.js";
import pick from "lodash/pick.js";

export interface EntityType<
  TUnsaved = any,
  TSaved = any,
  TIds extends object = any
> {
  _saved: TSaved;
  _unsaved: TUnsaved;
  _idKeys: TIds;
  idOf: (rec: TSaved) => TIds;
}

export type UnsavedR<T extends { _unsaved: any }> = T["_unsaved"];
export type SavedR<T extends { _saved: any }> = T["_saved"];
export type KeyType<R extends EntityType> = R["_idKeys"];

export interface PgRecordInfo<
  Unsaved = any,
  Saved extends Unsaved & IdType = any,
  IdType extends object = any
> extends EntityType<Unsaved, Saved, IdType> {
  tableName: string;
  schemaName: string;
  idKeys: (keyof Saved)[];
}

/**
 * Extract the runtime key name from a recordInfo
 */
export function idKeysOf<RI extends PgRecordInfo>(
  recordInfoWithIdKey: RI
): string[] {
  return recordInfoWithIdKey.idKeys as any;
}

/**
 * Turns a record type with possibly more fields than "id" into an array
 */
export function collectIdValues<RecordT extends PgRecordInfo>(
  idObj: KeyType<RecordT>,
  knexRecordType: RecordT
): string[] {
  return at(idObj, idKeysOf(knexRecordType));
}

function castToRecordInfo(
  runtimeData: Omit<PgRecordInfo, "_idKeys" | "_saved" | "_unsaved">
): PgRecordInfo {
  return runtimeData as PgRecordInfo;
}

/**
 *
 * Creates a record descriptor that captures the table name, primary key name,
 * unsaved type, and saved type of a database record type. Assumes "id" as the
 * primary key name
 *
 */
export function recordInfo<Unsaved, Saved extends Unsaved & { id: any }>(
  schemaName: string,
  tableName: string
): PgRecordInfo<Unsaved, Saved, Pick<Saved, "id">>;

export function recordInfo<Type extends { id: string }>(
  schemaName: string,
  tableName: string
): PgRecordInfo<Type, Type, Pick<Type, "id">>;

/**
 *
 * Creates a record descriptor that captures the table name, primary key name,
 * unsaved type, and saved type of a database record type.
 *
 */
export function recordInfo<
  Unsaved,
  Saved extends Unsaved,
  Id extends keyof Saved
>(
  schemaName: string,
  tableName: string,
  idKey: Id[]
): PgRecordInfo<Unsaved, Saved, Pick<Saved, Id>>;

/**
 *
 * Don't use this signature  be sure to provide unsaved and saved types.
 *
 */
export function recordInfo(
  schemaName: string,
  tableName: string,
  idKeys?: string[]
) {
  idKeys = idKeys || ["id"];
  return castToRecordInfo({
    schemaName,
    tableName,
    idKeys,
    idOf: (rec) => pick(rec, idKeys as any),
  });
}

/**
 *
 * Creates a record descriptor for records with composite primary keys
 *
 */
export function compositeRecordType<
  TUnsaved,
  TSaved extends TUnsaved = TUnsaved
>(
  schemaName: string,
  tableName: string
): {
  withCompositeKeys<TKeys extends keyof TSaved>(
    keys: TKeys[]
  ): PgRecordInfo<TUnsaved, TSaved, Pick<TSaved, TKeys>>;
} {
  return {
    withCompositeKeys(keys) {
      return castToRecordInfo({
        schemaName,
        tableName,
        idKeys: keys,
        idOf: (rec) => pick(rec, keys),
      });
    },
  };
}
