import { RecipeDto } from "../api/dtos/RecipeDto.js";
import { RecipeIngredientGroupDto } from "../api/dtos/RecipeIngredientGroupDto.js";
import { RecipeInstructionStepDto } from "../api/dtos/RecipeInstructionStepDto.js";
import { RecipeEntity } from "../entities/RecipeEntity.js";
import { AbstractDtoEntityMapper } from "./AbstractDtoEntityMapper.js";
import { RecipeIngredientGroupDtoEntityMapper } from "./RecipeIngredientGroupDtoEntityMapper.js";
import { RecipeInstructionStepDtoEntityMapper } from "./RecipeInstructionStepDtoEntityMapper.js";
import { TagDtoEntityMapper } from "./TagDtoEntityMapper.js";

export class RecipeDtoEntityMapper extends AbstractDtoEntityMapper<RecipeEntity, RecipeDto> {
    constructor(
        private instructionStepMapper: RecipeInstructionStepDtoEntityMapper,
        private ingredientGroupMapper: RecipeIngredientGroupDtoEntityMapper,
        private tagMapper: TagDtoEntityMapper
    ) {
        super();
    }

    toDto(entity: RecipeEntity): RecipeDto {
        const dto = new RecipeDto();
        this.mapBaseEntityToDto(entity, dto);

        dto.title = entity.title;
        dto.amount = entity.amount;
        dto.amountDescription = entity.amountDescription;

        // map instructions
        dto.instructions = entity.instructionSteps.map((stepEntity) => {
            const instructionStep: RecipeInstructionStepDto = this.instructionStepMapper.toDto(stepEntity);
            instructionStep.recipeId = entity.id;
            return instructionStep;
        });

        // map ingredient groups
        dto.ingredientGroups = entity.ingredientGroups.map((groupEntity) => {
            const ingredientGroup: RecipeIngredientGroupDto = this.ingredientGroupMapper.toDto(groupEntity);
            ingredientGroup.recipeId = entity.id;
            return ingredientGroup;
        });

        // map tags
        dto.tagList = (entity.tagList ?? []).map((tagEntity) =>
            this.tagMapper.toDto(tagEntity)
        );

        return dto;
    }

    toEntity(dto: RecipeDto): RecipeEntity {
        const entity = new RecipeEntity();
        this.mapBaseDtoToEntity(dto, entity);

        entity.title = dto.title;
        entity.amount = dto.amount;
        entity.amountDescription = dto.amountDescription;

        // map instructions
        entity.instructionSteps = dto.instructions.map((stepDto) => {
            const stepEntity = this.instructionStepMapper.toEntity(stepDto);

            // Set the relation if the entity already exists in DB
            if (entity.hasValidId()) {
                stepEntity.recipe = entity;
            }

            // If it's a new step (no id from client), let DB generate a new UUID
            if (!stepDto.id) {
                delete (stepEntity as any).id;
            }

            return stepEntity;
        });

        // map ingredient groups
        entity.ingredientGroups = dto.ingredientGroups.map((groupDto) => {
            const groupEntity = this.ingredientGroupMapper.toEntity(groupDto);

            // Set the relation if the entity already exists in DB
            if (entity.hasValidId()) {
                groupEntity.recipe = entity;
            }

            // If it's a new group (no id from client), let DB generate a new UUID
            if (!groupDto.id) {
                delete (groupEntity as any).id;
            }

            return groupEntity;
        });

        // map tags
        entity.tagList = (dto.tagList ?? []).map((tagDto) =>
            this.tagMapper.toEntity(tagDto)
        );

        return entity;
    }

    mergeDtoIntoEntity(dto: RecipeDto, entity: RecipeEntity): RecipeEntity {
        entity.title = dto.title;
        entity.amount = dto.amount;
        entity.amountDescription = dto.amountDescription;

        // --- Instruction Steps ---
        entity.instructionSteps = this.instructionStepMapper.mergeDtoListIntoEntityList(
            dto.instructions,
            entity.instructionSteps
        );

        // --- Ingredient Groups ---
        entity.ingredientGroups = this.ingredientGroupMapper.mergeDtoListIntoEntityList(
            dto.ingredientGroups,
            entity.ingredientGroups
        );

        // --- Tags ---
        // Tags are looked up by ID and replaced wholesale: the recipe holds
        // references to existing tag entities, so we map each DTO to its entity
        // form and let TypeORM sync the join table on save.
        entity.tagList = (dto.tagList ?? []).map((tagDto) =>
            this.tagMapper.toEntity(tagDto)
        );

        return entity;
    }

    createNewEntity(): RecipeEntity {
        return new RecipeEntity();
    }
}