import { AbstractDto } from "../api/dtos/AbstractDto.js";
import { AbstractEntity } from "../entities/AbstractEntity.js";

export abstract class AbstractDtoEntityMapper<
  E extends AbstractEntity,
  D extends AbstractDto
> {
  /**
   * Map base entity fields (id, createdAt, updatedAt) to DTO.
   */
  protected mapBaseEntityToDto(entity: E, dto: D): D {
    dto.id = entity.id;
    dto.createdAt = entity.createDate;
    dto.updatedAt = entity.updateDate;
    return dto;
  }

  /**
   * Map base DTO fields to entity.
   */
  protected mapBaseDtoToEntity(dto: D, entity: E): E {
    entity.id = dto.id;
    entity.createDate = dto.createdAt;
    entity.updateDate = dto.updatedAt;
    return entity;
  }

  /**
   * Merge entity list  with changes contained in DTO list
   * @param dtos List of dtos
   * @param entities List of entities
   * @returns Merged list
   * 
   * elements no longer contained in the dto list will be removed from the entity list
   * new elements will be mapped to entity and added to the entity list
   * existing elements will be updated
   */
  mergeDtoListIntoEntityList(dtos: D[], entities: E[]) : E[]{
    const updatedEntities: E[] = [];
    const existingMap = new Map(entities?.map(e => [e.id, e]) ?? []);

    for (const dto of dtos) {
      if (dto.id && existingMap.has(dto.id)) {
        // update existing
        const entity = existingMap.get(dto.id)!;
        updatedEntities.push(this.mergeDtoIntoEntity(dto, entity));
      } else {
        // create new
        const newEntity = this.createNewEntity();
        updatedEntities.push(this.mergeDtoIntoEntity(dto, newEntity));
      }
    }

    return updatedEntities;
  }

  // Abstract methods to be implemented by subclasses
  /**
   * Maps an entity to DTO
   * @param entity Entity that is mapped to DTO
   */
  abstract toDto(entity: E): D;
  /**
   * Maps a DTO to entity
   * @param dto DTO to map to entity
   */
  abstract toEntity(dto: D): E;
  /**
   * Merge changes in DTO into entity
   * @param dto Dto containing changes
   * @param entity existing entity 
   * 
   * Used for merging user changes (DTO) into the existing entity (database).
   */
  abstract mergeDtoIntoEntity(dto: D, entity: E): E;
  /**
   * Defines how to create a new entity. Required by mergeDtoListIntoEntityList
   * to add new elements to the list
   */
  abstract createNewEntity() : E;
}
