package com.iqser.red.service.persistence.management.v1.processor.service.redactionlog;

import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;

import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
import com.iqser.red.service.persistence.management.v1.processor.utils.ColorUtils;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AnnotationStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.Comment;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.IdRemoval;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualForceRedaction;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualImageRecategorization;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualLegalBasisChange;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualRedactionEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualResizeRedaction;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.Colors;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.Type;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Change;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.ChangeType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Engine;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.ManualChange;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.ManualRedactionType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Point;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLogComment;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.SectionGrid;

import io.micrometer.core.annotation.Timed;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor
public class RedactionLogMergeService {

    private static final String DELETED_TYPE_COLOR = "#9398a0";
    private final SectionTextService sectionTextService;
    private final FileManagementStorageService fileManagementStorageService;


    @Timed("redactmanager_getMergedRedactionLog")
    public RedactionLog provideRedactionLog(RedactionRequest redactionRequest) {

        log.debug("Requested preview for: {}", redactionRequest);

        var redactionLog = fileManagementStorageService.getRedactionLog(redactionRequest.getDossierId(), redactionRequest.getFileId());

        if (redactionLog == null) {
            throw new NotFoundException("RedactionLog not present");
        }

        log.debug("Loaded redaction log with computationalVersion: {}", redactionLog.getAnalysisVersion());
        var merged = mergeRedactionLogData(redactionLog,
                redactionRequest.getManualRedactions(),
                redactionRequest.getExcludedPages(),
                redactionRequest.getTypes(),
                redactionRequest.getColors());

        merged.getRedactionLogEntry().removeIf(e -> e.isFalsePositive() && !redactionRequest.isIncludeFalsePositives());

        return merged;
    }


    private RedactionLog mergeRedactionLogData(RedactionLog redactionLog,
                                               ManualRedactions manualRedactions,
                                               Set<Integer> excludedPages,
                                               List<Type> types,
                                               Colors colors) {

        var skippedImportedRedactions = new HashSet<>();
        log.info("Merging Redaction log with manual redactions");
        if (manualRedactions != null) {

            var manualRedactionWrappers = createManualRedactionWrappers(manualRedactions);

            for (RedactionLogEntry entry : redactionLog.getRedactionLogEntry()) {

                processRedactionLogEntry(manualRedactionWrappers.stream().filter(mr -> entry.getId().equals(mr.getId())).collect(Collectors.toList()), entry, types, colors);

                if (entry.isImported() && !entry.isRedacted()) {
                    skippedImportedRedactions.add(entry.getId());
                }

                entry.setComments(convert(manualRedactions.getComments().get(entry.getId())));

                if (excludedPages != null && !excludedPages.isEmpty()) {
                    entry.getPositions().forEach(pos -> {
                        if (!isLocalManualRedaction(entry) && excludedPages.contains(pos.getPage())) {
                            entry.setExcluded(true);
                        }
                    });
                }
            }

        }

        Set<String> processedIds = new HashSet<>();
        redactionLog.getRedactionLogEntry().removeIf(entry -> {

            if (entry.getImportedRedactionIntersections() != null) {
                entry.getImportedRedactionIntersections().removeAll(skippedImportedRedactions);
                if (!entry.getImportedRedactionIntersections().isEmpty() && (!entry.isImage() || entry.isImage() && !(entry.getType().equals("image") || entry.getType()
                        .equals("ocr")))) {
                    return true;
                }
            }
            if (processedIds.contains(entry.getId())) {
                log.info("Duplicate annotation found with id {}", entry.getId());
                return true;
            }
            processedIds.add(entry.getId());
            return false;
        });
        return redactionLog;
    }


    private boolean isLocalManualRedaction(RedactionLogEntry entry) {

        return entry.getManualChanges() != null && entry.getManualChanges()
                .stream()
                .anyMatch(mc -> mc.getManualRedactionType() == ManualRedactionType.ADD_LOCALLY || mc.getManualRedactionType() == ManualRedactionType.RESIZE && mc.getAnnotationStatus() == AnnotationStatus.APPROVED && entry.getEngines()
                        .contains(Engine.RULE) && !entry.getEngines().contains(Engine.DICTIONARY));

    }


    private List<ManualRedactionWrapper> createManualRedactionWrappers(ManualRedactions manualRedactions) {

        List<ManualRedactionWrapper> manualRedactionWrappers = new ArrayList<>();

        manualRedactions.getImageRecategorization().forEach(item -> {
            if (item.getSoftDeletedTime() == null) {
                manualRedactionWrappers.add(new ManualRedactionWrapper(item.getAnnotationId(), item.getRequestDate(), item));
            }
        });

        manualRedactions.getIdsToRemove().forEach(item -> {
            if (item.getSoftDeletedTime() == null) {
                manualRedactionWrappers.add(new ManualRedactionWrapper(item.getAnnotationId(), item.getRequestDate(), item));
            }
        });

        manualRedactions.getForceRedactions().forEach(item -> {
            if (item.getSoftDeletedTime() == null) {
                manualRedactionWrappers.add(new ManualRedactionWrapper(item.getAnnotationId(), item.getRequestDate(), item));
            }
        });

        manualRedactions.getLegalBasisChanges().forEach(item -> {
            if (item.getSoftDeletedTime() == null) {
                manualRedactionWrappers.add(new ManualRedactionWrapper(item.getAnnotationId(), item.getRequestDate(), item));
            }
        });

        manualRedactions.getResizeRedactions().forEach(item -> {
            if (item.getSoftDeletedTime() == null) {
                manualRedactionWrappers.add(new ManualRedactionWrapper(item.getAnnotationId(), item.getRequestDate(), item));
            }
        });

        Collections.sort(manualRedactionWrappers);

        return manualRedactionWrappers;
    }


    private void processRedactionLogEntry(List<ManualRedactionWrapper> manualRedactionWrappers, RedactionLogEntry redactionLogEntry, List<Type> types, Colors colors) {

        manualRedactionWrappers.forEach(mrw -> {

            Object item = mrw.getItem();
            if (item instanceof ManualImageRecategorization) {
                var imageRecategorization = (ManualImageRecategorization) item;
                processManualImageRecategorization(redactionLogEntry, types, colors, imageRecategorization);
            }

            if (item instanceof IdRemoval) {
                var manualRemoval = (IdRemoval) item;
                processIdRemoval(redactionLogEntry, types, colors, manualRemoval);
            }

            if (item instanceof ManualForceRedaction) {
                var manualForceRedact = (ManualForceRedaction) item;
                processManualForceRedaction(redactionLogEntry, types, colors, manualForceRedact);
            }

            if (item instanceof ManualLegalBasisChange) {
                var manualLegalBasisChange = (ManualLegalBasisChange) item;
                processManualLegalBasisChange(redactionLogEntry, types, colors, manualLegalBasisChange);
            }

            if (item instanceof ManualResizeRedaction) {
                var manualResizeRedact = (ManualResizeRedaction) item;
                processManualResizeRedaction(redactionLogEntry, types, colors, manualResizeRedact);
            }

        });

    }


    private void processManualImageRecategorization(RedactionLogEntry redactionLogEntry, List<Type> types, Colors colors, ManualImageRecategorization imageRecategorization) {

        String manualOverrideReason = null;
        if (imageRecategorization.getStatus().equals(AnnotationStatus.APPROVED)) {

            redactionLogEntry.setType(imageRecategorization.getType());
            redactionLogEntry.setSection("Image:" + redactionLogEntry.getType());

            if (isHint(types, imageRecategorization.getType())) {
                redactionLogEntry.setRedacted(false);
                redactionLogEntry.setHint(true);
            } else {
                redactionLogEntry.setHint(false);
            }

            manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", recategorized by manual override");
        } else if (imageRecategorization.getStatus().equals(AnnotationStatus.REQUESTED)) {
            manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", requested to recategorize");
            redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, false, redactionLogEntry.isRedacted(), false, types));
        }

        if (manualOverrideReason != null) {
            redactionLogEntry.setReason(manualOverrideReason);
        }

        redactionLogEntry.getManualChanges()
                .add(ManualChange.from(imageRecategorization).withManualRedactionType(ManualRedactionType.RECATEGORIZE).withChange("type", imageRecategorization.getType()));
    }


    private String mergeReasonIfNecessary(String currentReason, String addition) {

        if (currentReason != null) {
            if (!currentReason.contains(addition)) {
                return currentReason + addition;
            }
            return currentReason;
        } else {
            return "";
        }
    }


    private void processIdRemoval(RedactionLogEntry redactionLogEntry, List<Type> types, Colors colors, IdRemoval manualRemoval) {

        boolean isApprovedRedaction = manualRemoval.getStatus().equals(AnnotationStatus.APPROVED);
        if (isApprovedRedaction && manualRemoval.isRemoveFromDictionary() && isBasedOnDictionaryOnly(redactionLogEntry)) {
            log.debug("Skipping merge for dictionary-modifying entry");
        } else {
            String redactionLogEntryType = redactionLogEntry.getType();
            String manualOverrideReason = null;
            if (isApprovedRedaction) {
                redactionLogEntry.setRedacted(false);
                manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", removed by manual override");
                redactionLogEntry.setColor(getColor(redactionLogEntryType, colors, false, redactionLogEntry.isRedacted(), true, types));
                redactionLogEntry.setHint(false);
            } else if (manualRemoval.getStatus().equals(AnnotationStatus.REQUESTED)) {
                manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", requested to remove");
                redactionLogEntry.setColor(getColor(redactionLogEntryType, colors, true, redactionLogEntry.isRedacted(), false, types));
            }

            if (manualOverrideReason != null) {
                redactionLogEntry.setReason(manualOverrideReason);
            }
        }

        redactionLogEntry.getManualChanges()
                .add(ManualChange.from(manualRemoval)
                        .withManualRedactionType(manualRemoval.isRemoveFromDictionary() ? ManualRedactionType.REMOVE_FROM_DICTIONARY : ManualRedactionType.REMOVE_LOCALLY));
    }


    private boolean isBasedOnDictionaryOnly(RedactionLogEntry redactionLogEntry) {

        return redactionLogEntry.getEngines().contains(Engine.DICTIONARY) && redactionLogEntry.getEngines().size() == 1;
    }


    private void processManualForceRedaction(RedactionLogEntry redactionLogEntry, List<Type> types, Colors colors, ManualForceRedaction manualForceRedact) {

        String manualOverrideReason = null;
        var dictionaryIsHint = isHint(types, redactionLogEntry.getType());
        if (manualForceRedact.getStatus().equals(AnnotationStatus.APPROVED)) {
            // Forcing a skipped hint should result in a hint
            if (dictionaryIsHint) {
                redactionLogEntry.setHint(true);
            } else {
                redactionLogEntry.setRedacted(true);
            }
            redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, false, redactionLogEntry.isRedacted(), false, types));
            manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", forced by manual override");
            redactionLogEntry.setLegalBasis(manualForceRedact.getLegalBasis());
        } else if (manualForceRedact.getStatus().equals(AnnotationStatus.REQUESTED)) {
            manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", requested to force " + (dictionaryIsHint ? "hint" : "redact"));
            redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, true, redactionLogEntry.isRedacted(), false, types));
            redactionLogEntry.setLegalBasis(manualForceRedact.getLegalBasis());
        }

        if (manualOverrideReason != null) {
            redactionLogEntry.setReason(manualOverrideReason);
        }

        var manualChange = ManualChange.from(manualForceRedact).withManualRedactionType(dictionaryIsHint ? ManualRedactionType.FORCE_HINT : ManualRedactionType.FORCE_REDACT);

        redactionLogEntry.getManualChanges().add(manualChange);
    }


    private void processManualLegalBasisChange(RedactionLogEntry redactionLogEntry, List<Type> types, Colors colors, ManualLegalBasisChange manualLegalBasisChange) {

        String manualOverrideReason = null;
        if (manualLegalBasisChange.getStatus().equals(AnnotationStatus.APPROVED)) {
            manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", legal basis was manually changed");
            redactionLogEntry.setLegalBasis(manualLegalBasisChange.getLegalBasis());
            redactionLogEntry.setRedacted(true);
            if (manualLegalBasisChange.getSection() != null) {
                redactionLogEntry.setSection(manualLegalBasisChange.getSection());
            }
            if (redactionLogEntry.isRectangle() && manualLegalBasisChange.getValue() != null) {
                redactionLogEntry.setValue(manualLegalBasisChange.getValue());
            }
        } else if (manualLegalBasisChange.getStatus().equals(AnnotationStatus.REQUESTED)) {
            manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", legal basis change requested");
            redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, true, redactionLogEntry.isRedacted(), false, types));
        }

        if (manualOverrideReason != null) {
            redactionLogEntry.setReason(manualOverrideReason);
        }

        var manualChange = ManualChange.from(manualLegalBasisChange).withManualRedactionType(ManualRedactionType.LEGAL_BASIS_CHANGE);
        manualChange.withChange("legalBasis", manualLegalBasisChange.getLegalBasis());
        if (manualLegalBasisChange.getSection() != null) {
            manualChange.withChange("section", manualLegalBasisChange.getSection());
        }
        if (redactionLogEntry.isRectangle() && manualLegalBasisChange.getValue() != null) {
            manualChange.withChange("value", manualLegalBasisChange.getValue());
        }
        redactionLogEntry.getManualChanges().add(manualChange);
    }


    private void processManualResizeRedaction(RedactionLogEntry redactionLogEntry, List<Type> types, Colors colors, ManualResizeRedaction manualResizeRedact) {

        String manualOverrideReason = null;
        if (manualResizeRedact.getStatus().equals(AnnotationStatus.APPROVED)) {
            redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, false, redactionLogEntry.isRedacted(), false, types));
            redactionLogEntry.setPositions(convertPositions(manualResizeRedact.getPositions()));
            if (!"signature".equalsIgnoreCase(redactionLogEntry.getType()) && !"logo".equalsIgnoreCase(redactionLogEntry.getType())) {
                redactionLogEntry.setValue(manualResizeRedact.getValue());
            }
            // This is for backwards compatibility, now the text after/before is calculated during reanalysis because we need to find dict entries on positions where entries are resized to smaller.
            if (manualResizeRedact.getTextBefore() != null || manualResizeRedact.getTextAfter() != null) {
                redactionLogEntry.setTextBefore(manualResizeRedact.getTextBefore());
                redactionLogEntry.setTextAfter(manualResizeRedact.getTextAfter());
            }
            manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", resized by manual override");
        } else if (manualResizeRedact.getStatus().equals(AnnotationStatus.REQUESTED)) {
            manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", requested to resize redact");
            redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, true, redactionLogEntry.isRedacted(), false, types));
            redactionLogEntry.setPositions(convertPositions(manualResizeRedact.getPositions()));

            // This is for backwards compatibility, now the text after/before is calculated during reanalysis because we need to find dict entries on positions where entries are resized to smaller.
            if (manualResizeRedact.getTextBefore() != null || manualResizeRedact.getTextAfter() != null) {
                redactionLogEntry.setTextBefore(manualResizeRedact.getTextBefore());
                redactionLogEntry.setTextAfter(manualResizeRedact.getTextAfter());
            }
        }

        redactionLogEntry.setReason(manualOverrideReason);
        redactionLogEntry.getManualChanges()
                .add(ManualChange.from(manualResizeRedact).withManualRedactionType(ManualRedactionType.RESIZE).withChange("value", manualResizeRedact.getValue()));
    }


    public List<RedactionLogEntry> addManualAddEntries(SectionGrid sectionGrid,
                                                       Set<ManualRedactionEntry> manualAdds,
                                                       Map<String, List<Comment>> comments,
                                                       Colors colors,
                                                       List<Type> types,
                                                       int analysisNumber) {

        List<RedactionLogEntry> redactionLogEntries = new ArrayList<>();

        for (ManualRedactionEntry manualRedactionEntry : manualAdds) {

            if (shouldCreateManualEntry(manualRedactionEntry)) {
                RedactionLogEntry redactionLogEntry = createRedactionLogEntry(manualRedactionEntry, manualRedactionEntry.getAnnotationId(), colors, types, analysisNumber);
                redactionLogEntry.setPositions(convertPositions(manualRedactionEntry.getPositions()));
                redactionLogEntry.setComments(convert(comments.get(manualRedactionEntry.getAnnotationId())));
                redactionLogEntry.setTextBefore(manualRedactionEntry.getTextBefore());
                redactionLogEntry.setTextAfter(manualRedactionEntry.getTextAfter());

                sectionTextService.handleSectionText(sectionGrid, redactionLogEntry);

                redactionLogEntries.add(redactionLogEntry);
            }
        }

        return redactionLogEntries;
    }


    private List<RedactionLogComment> convert(List<Comment> comments) {

        return comments == null ? null : comments.stream()
                .map(c -> new RedactionLogComment(c.getId(), c.getUser(), c.getText(), c.getAnnotationId(), c.getFileId(), c.getDate(), c.getSoftDeletedTime()))
                .collect(Collectors.toList());
    }


    private List<Rectangle> convertPositions(List<com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.Rectangle> positions) {

        return positions.stream()
                .map(pos -> new Rectangle(new Point(pos.getTopLeftX(), pos.getTopLeftY()), pos.getWidth(), pos.getHeight(), pos.getPage()))
                .collect(Collectors.toList());
    }


    @SuppressWarnings("PMD.UselessParentheses")
    private boolean shouldCreateManualEntry(ManualRedactionEntry manualRedactionEntry) {

        return (!manualRedactionEntry.isAddToDictionary() && !manualRedactionEntry.isAddToDossierDictionary()) || ((manualRedactionEntry.isAddToDictionary() || manualRedactionEntry.isAddToDossierDictionary()) && manualRedactionEntry.getProcessedDate() == null);
    }


    private RedactionLogEntry createRedactionLogEntry(ManualRedactionEntry manualRedactionEntry, String id, Colors colors, List<Type> types, int analysisNumber) {

        var addToDictionary = manualRedactionEntry.isAddToDictionary() || manualRedactionEntry.isAddToDossierDictionary();

        var change = ManualChange.from(manualRedactionEntry).withManualRedactionType(addToDictionary ? ManualRedactionType.ADD_TO_DICTIONARY : ManualRedactionType.ADD_LOCALLY);
        List<ManualChange> changeList = new ArrayList<>();
        changeList.add(change);

        return RedactionLogEntry.builder()
                .id(id)
                .color(getColorForManualAdd(manualRedactionEntry.getType(), colors, manualRedactionEntry.getStatus(), types))
                .reason(manualRedactionEntry.getReason())
                .isDictionaryEntry(manualRedactionEntry.isAddToDictionary())
                .isDossierDictionaryEntry(manualRedactionEntry.isAddToDossierDictionary())
                .legalBasis(manualRedactionEntry.getLegalBasis())
                .value(manualRedactionEntry.getValue())
                .sourceId(manualRedactionEntry.getSourceId())
                .section(manualRedactionEntry.getSection())
                .type(manualRedactionEntry.getType())
                .redacted(true)
                .isHint(false)
                .sectionNumber(-1)
                .rectangle(manualRedactionEntry.isRectangle())
                .manualChanges(changeList)
                .changes(List.of(new Change(analysisNumber + 1, ChangeType.ADDED, manualRedactionEntry.getRequestDate())))
                .build();
    }


    private float[] getColor(String type, Colors colors, boolean requested, boolean isRedaction, boolean skipped, List<Type> types) {

        if (requested) {
            return ColorUtils.convertColor(colors.getRequestRemoveColor());
        }
        if (skipped || !isRedaction && !isHint(types, type)) {
            return ColorUtils.convertColor(colors.getSkippedColor());
        }
        return getColor(types, type);
    }


    private float[] getColorForManualAdd(String type, Colors colors, AnnotationStatus status, List<Type> types) {

        if (status.equals(AnnotationStatus.REQUESTED)) {
            return ColorUtils.convertColor(colors.getRequestAddColor());
        } else if (status.equals(AnnotationStatus.DECLINED)) {
            return ColorUtils.convertColor(colors.getSkippedColor());
        }
        return getColor(types, type);
    }


    private float[] getColor(List<Type> types, String type) {

        var matchingTypes = getMatchingTypes(types, type);
        Optional<Type> foundAndNotDeletedType = matchingTypes.stream().filter(t -> !isDeletedType(t)).findFirst();
        if (foundAndNotDeletedType.isPresent()) {
            return ColorUtils.convertColor(foundAndNotDeletedType.get().getHexColor());
        }
        Optional<Type> firstDeletedType = matchingTypes.stream().findFirst();
        return firstDeletedType.map(value -> ColorUtils.convertColor(value.getHexColor())).orElseGet(() -> ColorUtils.convertColor(DELETED_TYPE_COLOR));
    }


    boolean isHint(List<Type> types, String type) {

        var matchingTypes = getMatchingTypes(types, type);
        Optional<Type> foundAndNotDeletedType = matchingTypes.stream().filter(t -> !isDeletedType(t)).findFirst();
        if (foundAndNotDeletedType.isPresent()) {
            return foundAndNotDeletedType.get().isHint();
        }
        Optional<Type> firstDeletedType = matchingTypes.stream().findFirst();
        return firstDeletedType.map(Type::isHint).orElse(false);
    }


    private List<Type> getMatchingTypes(List<Type> types, String type) {

        return types.stream().filter(t -> t.getType().equals(type)).collect(Collectors.toList());
    }


    private boolean isDeletedType(Type type) {

        return type.getSoftDeletedTime() != null;
    }


    @Data
    @AllArgsConstructor
    private static class ManualRedactionWrapper implements Comparable<ManualRedactionWrapper> {

        private String id;
        private OffsetDateTime date;
        private Object item;


        @Override
        public int compareTo(ManualRedactionWrapper o) {

            return this.date.compareTo(o.date);
        }

    }

}

