CategoryMapper.java

package com.albertsilva.dev.asjcatalog.mapper.category;

import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;

import com.albertsilva.dev.asjcatalog.domain.catalog.Category;
import com.albertsilva.dev.asjcatalog.dto.category.request.CategoryCreateRequest;
import com.albertsilva.dev.asjcatalog.dto.category.request.CategoryUpdateRequest;
import com.albertsilva.dev.asjcatalog.dto.category.response.CategoryDetailsResponse;
import com.albertsilva.dev.asjcatalog.dto.category.response.CategoryResponse;

/**
 * Conversor manual (sem MapStruct) entre os DTOs de categoria e a entidade
 * {@link Category}. Componente sem estado, usado apenas por
 * {@code CategoryService}.
 *
 * <p>
 * <b>Direções:</b> {@link CategoryCreateRequest} → entidade
 * ({@link #toEntity}); {@link CategoryUpdateRequest} → entidade existente
 * ({@link #updateEntity}); entidade → {@link CategoryResponse} /
 * {@link CategoryDetailsResponse}; e página de entidades → página de
 * {@link CategoryResponse}.
 * </p>
 *
 * <p>
 * <b>Características:</b> nunca lê nem grava {@code active} nem as datas; não
 * acessa {@code Category.products}; os métodos de conversão devolvem
 * {@code null} para entrada {@code null}; não normaliza valores (nome e
 * descrição são copiados como recebidos).
 * </p>
 */
@Component
public class CategoryMapper {

  /**
   * Cria uma nova {@link Category} (ainda sem id) a partir do request de criação.
   *
   * <p>
   * Copia apenas {@code name} e {@code description}. O indicador {@code active}
   * <b>não é definido aqui</b>: a instância fica com o valor padrão do campo
   * ({@code false}) até que {@code CategoryService.create} o defina como
   * {@code true}. As datas são preenchidas depois pelos callbacks JPA.
   * </p>
   *
   * @param request dados de criação
   * @return nova entidade (não persistida) ou {@code null} se {@code request} for
   *         {@code null}
   */
  public Category toEntity(CategoryCreateRequest request) {
    if (request == null) {
      return null;
    }

    Category entity = new Category();
    entity.setName(request.name());
    entity.setDescription(request.description());

    return entity;
  }

  /**
   * Aplica o request de atualização a uma categoria existente, <b>somente nos
   * campos diferentes de {@code null}</b>.
   *
   * <p>
   * {@code name} e {@code description} são tratados de forma independente: cada
   * um só é sobrescrito se o valor recebido não for {@code null}. Portanto,
   * {@code null} significa "manter o valor atual" (não é possível limpar a
   * descrição com {@code null}), enquanto {@code ""} substitui a descrição por
   * texto vazio. Se {@code request} ou {@code entity} for {@code null}, nada é
   * feito. {@code active} e as datas não são alteradas.
   * </p>
   *
   * <p>
   * Ao acessar os setters, força a inicialização de uma referência preguiçosa
   * ({@code getReferenceById}), como faz {@code CategoryService.update}.
   * </p>
   *
   * @param request dados de atualização
   * @param entity  categoria a ser modificada
   */
  public void updateEntity(CategoryUpdateRequest request, Category entity) {
    if (request == null || entity == null) {
      return;
    }

    if (request.name() != null) {
      entity.setName(request.name());
    }

    if (request.description() != null) {
      entity.setDescription(request.description());
    }
  }

  /**
   * Converte a entidade em resposta <b>resumida</b>: somente {@code id} e
   * {@code name}. Descrição, {@code active}, datas e produtos não são expostos.
   *
   * @param entity categoria
   * @return resposta ou {@code null} se {@code entity} for {@code null}
   */
  public CategoryResponse toResponse(Category entity) {
    if (entity == null) {
      return null;
    }

    return new CategoryResponse(entity.getId(), entity.getName());
  }

  /**
   * Converte a entidade em resposta <b>detalhada</b>: {@code id}, {@code name},
   * {@code description} e {@code active}. Datas e produtos não são expostos.
   *
   * @param entity categoria
   * @return resposta ou {@code null} se {@code entity} for {@code null}
   */
  public CategoryDetailsResponse toDetailsResponse(Category entity) {
    if (entity == null) {
      return null;
    }

    return new CategoryDetailsResponse(entity.getId(), entity.getName(), entity.getDescription(), entity.isActive());
  }

  /**
   * Converte uma página de entidades {@link Category} em uma página de
   * {@link CategoryResponse}.
   *
   * <p>
   * <b>Comportamento:</b>
   * </p>
   * <ul>
   * <li>Utiliza a função {@link #toResponse(Category)} para mapear cada
   * elemento</li>
   * <li>Mantém as informações de paginação (total, páginas, etc.)</li>
   * </ul>
   *
   * @param entities página de entidades
   * @return página de DTOs de resposta
   */
  public Page<CategoryResponse> toResponsePage(Page<Category> entities) {
    return entities.map(this::toResponse);
  }
}