import {
  DisplayAddresseeDTO,
  DisplayBranchDTO,
} from '@generated/swagger/oms-api';

/**
 * Internal model representing an entity with name and address for comparison.
 * This allows for type-safe comparison without casting.
 */
interface EntityWithNameAndAddress {
  readonly name: string;
  readonly address: unknown;
}

/**
 * Converts an addressee-like object to a comparable entity.
 */
function toComparableAddressee(
  addressee: Pick<DisplayAddresseeDTO, 'firstName' | 'lastName' | 'address'>,
): EntityWithNameAndAddress {
  const firstName = addressee.firstName ?? '';
  const lastName = addressee.lastName ?? '';
  const name = `${firstName}|${lastName}`.trim();
  return {
    name,
    address: addressee.address ?? null,
  };
}

/**
 * Converts a branch-like object to a comparable entity.
 */
function toComparableBranch(
  branch: Pick<DisplayBranchDTO, 'name' | 'address'>,
): EntityWithNameAndAddress {
  return {
    name: branch.name ?? '',
    address: branch.address ?? null,
  };
}

/**
 * Compares two entities for equality based on name and address.
 */
function areEntitiesEqual(
  entity1: EntityWithNameAndAddress,
  entity2: EntityWithNameAndAddress,
): boolean {
  if (entity1.name !== entity2.name) {
    return false;
  }

  const address1Str = JSON.stringify(entity1.address);
  const address2Str = JSON.stringify(entity2.address);

  return address1Str === address2Str;
}

/**
 * Compares two DisplayAddresseeDTO objects for equality based on name and address.
 * Two addressees are considered equal if they have the same firstName, lastName, and address.
 *
 * @param addressee1 - First addressee to compare
 * @param addressee2 - Second addressee to compare
 * @returns true if addressees are equal, false otherwise
 */
export function areAddresseesEqual(
  addressee1: DisplayAddresseeDTO | undefined,
  addressee2: DisplayAddresseeDTO | undefined,
): boolean {
  if (!addressee1 && !addressee2) {
    return true;
  }

  if (!addressee1 || !addressee2) {
    return false;
  }

  return areEntitiesEqual(
    toComparableAddressee(addressee1),
    toComparableAddressee(addressee2),
  );
}

/**
 * Compares two DisplayBranchDTO objects for equality based on name and address.
 * Two branches are considered equal if they have the same name and address.
 *
 * @param branch1 - First branch to compare
 * @param branch2 - Second branch to compare
 * @returns true if branches are equal, false otherwise
 */
export function areBranchesEqual(
  branch1: DisplayBranchDTO | undefined,
  branch2: DisplayBranchDTO | undefined,
): boolean {
  if (!branch1 && !branch2) {
    return true;
  }

  if (!branch1 || !branch2) {
    return false;
  }

  return areEntitiesEqual(
    toComparableBranch(branch1),
    toComparableBranch(branch2),
  );
}

/**
 * Type guard to check if an object can be compared as an addressee.
 */
function hasAddresseeShape(
  obj: unknown,
): obj is Pick<DisplayAddresseeDTO, 'firstName' | 'lastName' | 'address'> {
  return (
    obj !== null &&
    typeof obj === 'object' &&
    ('firstName' in obj || 'lastName' in obj || 'address' in obj)
  );
}

/**
 * Type guard to check if an object can be compared as a branch.
 */
function hasBranchShape(
  obj: unknown,
): obj is Pick<DisplayBranchDTO, 'name' | 'address'> {
  return (
    obj !== null &&
    typeof obj === 'object' &&
    ('name' in obj || 'address' in obj)
  );
}

/**
 * Removes duplicate addressees from an array, keeping only the first occurrence.
 * Two addressees are considered duplicates if they have the same firstName, lastName, and address.
 *
 * @param addressees - Array of addressees to deduplicate
 * @returns Deduplicated array with only first occurrence of each unique addressee
 */
export function deduplicateAddressees<
  T extends Pick<DisplayAddresseeDTO, 'firstName' | 'lastName' | 'address'>,
>(addressees: readonly (T | undefined)[]): T[] {
  const result: T[] = [];
  const seen: T[] = [];

  for (const addressee of addressees) {
    if (!addressee || !hasAddresseeShape(addressee)) {
      continue;
    }

    const isDuplicate = seen.some((seenAddressee) =>
      hasAddresseeShape(seenAddressee)
        ? areEntitiesEqual(
            toComparableAddressee(seenAddressee),
            toComparableAddressee(addressee),
          )
        : false,
    );

    if (!isDuplicate) {
      result.push(addressee);
      seen.push(addressee);
    }
  }

  return result;
}

/**
 * Removes duplicate branches from an array, keeping only the first occurrence.
 * Two branches are considered duplicates if they have the same name and address.
 *
 * @param branches - Array of branches to deduplicate
 * @returns Deduplicated array with only first occurrence of each unique branch
 */
export function deduplicateBranches<
  T extends Pick<DisplayBranchDTO, 'name' | 'address'>,
>(branches: readonly (T | undefined)[]): T[] {
  const result: T[] = [];
  const seen: T[] = [];

  for (const branch of branches) {
    if (!branch || !hasBranchShape(branch)) {
      continue;
    }

    const isDuplicate = seen.some((seenBranch) =>
      hasBranchShape(seenBranch)
        ? areEntitiesEqual(
            toComparableBranch(seenBranch),
            toComparableBranch(branch),
          )
        : false,
    );

    if (!isDuplicate) {
      result.push(branch);
      seen.push(branch);
    }
  }

  return result;
}
