ViewerResponseHelper.java

/*
 * Copyright (C) 2025 B3Partners B.V.
 *
 * SPDX-License-Identifier: MIT
 */

package org.tailormap.api.persistence.helper;

import static java.util.Objects.requireNonNullElse;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.tailormap.api.persistence.Application;
import org.tailormap.api.persistence.json.AppI18nSettings;
import org.tailormap.api.persistence.json.AppUiSettings;
import org.tailormap.api.persistence.json.AttributeSettings;
import org.tailormap.api.persistence.json.Filter;
import org.tailormap.api.persistence.json.FilterGroup;
import org.tailormap.api.security.AuthorisationService;
import org.tailormap.api.viewer.model.ViewerResponse;

@Service
public class ViewerResponseHelper {
  private final AuthorisationService authorisationService;
  private final ViewerHelper viewerHelper;

  public ViewerResponseHelper(AuthorisationService authorisationService, ViewerHelper viewerHelper) {
    this.authorisationService = authorisationService;
    this.viewerHelper = viewerHelper;
  }

  @Transactional
  public ViewerResponse getViewerResponse(Application application) {
    return new ViewerResponse()
        .kind(ViewerResponse.KindEnum.APP)
        .name(application.getName())
        .title(application.getTitle())
        .styling(application.getStyling())
        .components(application.getComponents())
        .i18nSettings(requireNonNullElse(
            application.getSettings().getI18nSettings(), new AppI18nSettings().hideLanguageSwitcher(false)))
        .uiSettings(requireNonNullElse(
            application.getSettings().getUiSettings(), new AppUiSettings().hideLoginButton(false)))
        .projections(List.of(application.getCrs()))
        .filterGroups(verifyFilterGroups(application));
  }

  private List<FilterGroup> verifyFilterGroups(Application application) {

    List<FilterGroup> filterGroups = application.getSettings().getFilterGroups();

    Map<String, ViewerHelper.AppLayerFullContext> appLayerFullContextMap = viewerHelper.getAppLayerFullContextMap(
        application,
        filterGroups.stream()
            .map(FilterGroup::getLayerIds)
            .flatMap(List::stream)
            .toList());

    removeUnauthorizedLayersFromFilterGroups(appLayerFullContextMap, filterGroups);

    removeLayersFromFilterGroupsWithoutFeatureType(appLayerFullContextMap, filterGroups);

    removeFiltersWithoutAttributeInAllLayers(appLayerFullContextMap, filterGroups);

    removeEmptyFilterGroups(filterGroups);

    setAttributeAliasInFilters(appLayerFullContextMap, filterGroups);

    return filterGroups;
  }

  private void removeUnauthorizedLayersFromFilterGroups(
      Map<String, ViewerHelper.AppLayerFullContext> appLayerFullContextMap, List<FilterGroup> filterGroups) {
    filterGroups.forEach(fg ->
        fg.getLayerIds().removeIf(appLayerId -> !isAppLayerAuthorized(appLayerFullContextMap.get(appLayerId))));
  }

  private boolean isAppLayerAuthorized(ViewerHelper.AppLayerFullContext appLayerContext) {
    return appLayerContext != null
        && authorisationService.userAllowedToViewGeoService(appLayerContext.geoService())
        && authorisationService.userAllowedToViewGeoServiceLayer(
            appLayerContext.geoService(), appLayerContext.geoServiceLayer());
  }

  private void removeLayersFromFilterGroupsWithoutFeatureType(
      Map<String, ViewerHelper.AppLayerFullContext> appLayerFullContextMap, List<FilterGroup> filterGroups) {
    // Layers without a feature type are not in the full context map
    filterGroups.forEach(
        fg -> fg.getLayerIds().removeIf(appLayerId -> !appLayerFullContextMap.containsKey(appLayerId)));
  }

  private void removeFiltersWithoutAttributeInAllLayers(
      Map<String, ViewerHelper.AppLayerFullContext> appLayerFullContextMap, List<FilterGroup> filterGroups) {

    for (FilterGroup filterGroup : filterGroups) {
      Set<String> attributesCommonToAllLayers = filterGroup.getLayerIds().stream()
          .map(appLayerFullContextMap::get)
          .map(appLayerContext -> TMFeatureTypeHelper.getNonHiddenAttributeNames(
              appLayerContext.featureType(), appLayerContext.appLayerSettings()))
          .reduce((a, b) -> {
            Set<String> intersection = new HashSet<>(a);
            intersection.retainAll(b);
            return intersection;
          })
          .orElse(new HashSet<>());

      filterGroup.getFilters().removeIf(f -> !attributesCommonToAllLayers.contains(f.getAttribute()));
    }
  }

  private void removeEmptyFilterGroups(List<FilterGroup> filterGroups) {
    filterGroups.removeIf(
        fg -> fg.getLayerIds().isEmpty() || fg.getFilters().isEmpty());
  }

  private void setAttributeAliasInFilters(
      Map<String, ViewerHelper.AppLayerFullContext> appLayerFullContextMap, List<FilterGroup> filterGroups) {
    for (FilterGroup filterGroup : filterGroups) {
      for (Filter filter : filterGroup.getFilters()) {
        filterGroup.getLayerIds().stream()
            .map(appLayerFullContextMap::get)
            .map(appLayerContext -> appLayerContext
                .featureType()
                .getSettings()
                .getAttributeSettings()
                .get(filter.getAttribute()))
            .filter(Objects::nonNull)
            .map(AttributeSettings::getTitle)
            .filter(Objects::nonNull)
            .findFirst()
            .ifPresent(filter::setAttributeAlias);
      }
    }
  }
}