/**
 * Derive a tenant-unique short_code from a name-like string.
 *
 * Rules:
 *   - strip everything that isn't [A-Za-z0-9]
 *   - uppercase
 *   - take the first `maxLen` chars (default 6)
 *   - if the cleaned string is empty, use `fallbackPrefix`
 *   - probe `isCollision`; on collision append a numeric suffix
 *     starting at 1 ("DUBAI", "DUBAI1", "DUBAI2", …) until a free
 *     slot is found
 *
 * `isCollision` is the caller's repository-level uniqueness check —
 * normally `(code) => repo.findByShortCode(code).then(Boolean)`. It
 * runs inside the tenant-scoped repo, so uniqueness here is per tenant
 * (matches the partial unique index on every short_code column).
 *
 * Used by destination, hotel, activity, transport_service, and
 * trip_source so the input forms never expose `short_code` to ops.
 * Once derived, the code is treated as immutable — updates do NOT
 * regenerate it, to avoid breaking any external reference (Excel,
 * printable quotes, "search by code" boxes, etc.).
 *
 * The 999-iteration safety cap matches the original Hotel/TripSource
 * implementations — at that point the name is pathological and the
 * caller should surface a 409 rather than spin forever.
 */
export async function deriveUniqueShortCode(
  source: string | null | undefined,
  fallbackPrefix: string,
  isCollision: (candidate: string) => Promise<boolean>,
  maxLen = 6,
): Promise<string> {
  const cleaned = (source || '')
    .replace(/[^A-Za-z0-9]/g, '')
    .toUpperCase()
    .slice(0, maxLen);
  const baseCode = cleaned || fallbackPrefix;
  let candidate = baseCode;
  let suffix = 1;
  while (await isCollision(candidate)) {
    candidate = `${baseCode}${suffix}`;
    suffix++;
    if (suffix > 999) {
      throw new Error(
        `deriveUniqueShortCode: could not find a unique code derived from "${source}" within 999 attempts`,
      );
    }
  }
  return candidate;
}
