package p.packer.services;

import com.fasterxml.jackson.databind.JsonNode;
import p.packer.dtos.*;
import p.packer.messages.assets.OutputCodecCatalog;
import p.packer.models.*;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public final class PackerReadMessageMapper {
    private PackerReadMessageMapper() {
    }

    public static PackerAssetSummaryDTO toAssetSummaryDTO(PackerAssetSummary summary) {
        return new PackerAssetSummaryDTO(
                summary.assetReference(),
                toAssetIdentityDTO(summary.identity()),
                summary.state(),
                summary.buildParticipation(),
                summary.assetFamily(),
                summary.preloadEnabled(),
                summary.hasDiagnostics());
    }

    public static PackerAssetDetailsDTO toAssetDetailsDTO(PackerAssetDetails details) {
        return new PackerAssetDetailsDTO(
                toAssetSummaryDTO(details.summary()),
                details.outputFormat(),
                details.outputCodec(),
                details.availableOutputCodecs(),
                toCodecConfigurationFieldsByCodecDTO(details.codecConfigurationFieldsByCodec()),
                toCodecConfigurationFieldDTOs(details.metadataFields()),
                normalizeMetadata(details.outputPipeline()),
                toBankCompositionDetailsDTO(details.bankComposition()),
                details.pipelinePalettes().stream().map(PackerReadMessageMapper::normalizeMetadata).toList(),
                toDiagnosticDTOs(details.diagnostics()));
    }

    public static Map<String, Object> normalizeMetadata(Map<String, ?> metadata) {
        return metadata.entrySet().stream().collect(Collectors.toMap(
                Map.Entry::getKey,
                entry -> normalizeMetadataValue(entry.getValue()),
                (left, right) -> left,
                java.util.LinkedHashMap::new));
    }

    private static PackerBankCompositionDetailsDTO toBankCompositionDetailsDTO(PackerBankCompositionDetails details) {
        return new PackerBankCompositionDetailsDTO(
                details.availableFiles().stream().map(PackerReadMessageMapper::toBankCompositionFileDTO).toList(),
                details.selectedFiles().stream().map(PackerReadMessageMapper::toBankCompositionFileDTO).toList(),
                details.measuredBankSizeBytes());
    }

    private static PackerBankCompositionFileDTO toBankCompositionFileDTO(PackerBankCompositionFile file) {
        return new PackerBankCompositionFileDTO(
                file.path(),
                file.displayName(),
                file.size(),
                file.lastModified(),
                file.fingerprint(),
                normalizeMetadata(file.metadata()));
    }

    private static Object normalizeMetadataValue(Object value) {
        if (value == null
                || value instanceof String
                || value instanceof Number
                || value instanceof Boolean) {
            return value;
        }
        if (value instanceof JsonNode jsonNode) {
            if (jsonNode.isObject()) {
                final java.util.LinkedHashMap<String, Object> normalized = new java.util.LinkedHashMap<>();
                jsonNode.fields().forEachRemaining(entry -> normalized.put(entry.getKey(), normalizeMetadataValue(entry.getValue())));
                return Map.copyOf(normalized);
            }
            if (jsonNode.isArray()) {
                final java.util.List<Object> normalized = new java.util.ArrayList<>();
                jsonNode.forEach(item -> normalized.add(normalizeMetadataValue(item)));
                return List.copyOf(normalized);
            }
            if (jsonNode.isTextual()) {
                return jsonNode.asText();
            }
            if (jsonNode.isIntegralNumber()) {
                return jsonNode.longValue();
            }
            if (jsonNode.isFloatingPointNumber()) {
                return jsonNode.doubleValue();
            }
            if (jsonNode.isBoolean()) {
                return jsonNode.booleanValue();
            }
            if (jsonNode.isNull()) {
                return null;
            }
            return jsonNode.toString();
        }
        if (value instanceof PackerTileIndexedV1 tile) {
            final List<Integer> paletteIndices = new java.util.ArrayList<>(tile.paletteIndices().length);
            for (byte paletteIndex : tile.paletteIndices()) {
                paletteIndices.add(Byte.toUnsignedInt(paletteIndex));
            }
            return Map.of(
                    "width", tile.width(),
                    "height", tile.height(),
                    "paletteIndices", List.copyOf(paletteIndices));
        }
        if (value instanceof PackerPaletteV1 palette) {
            final List<Integer> originalArgb8888 = new java.util.ArrayList<>(palette.originalArgb8888().length);
            for (int argb : palette.originalArgb8888()) {
                originalArgb8888.add(argb);
            }
            final List<Integer> convertedRgb565 = new java.util.ArrayList<>(palette.convertedRgb565().length);
            for (short rgb565 : palette.convertedRgb565()) {
                convertedRgb565.add(Short.toUnsignedInt(rgb565));
            }
            return Map.of(
                    "originalArgb8888", List.copyOf(originalArgb8888),
                    "convertedRgb565", List.copyOf(convertedRgb565));
        }
        if (value instanceof Map<?, ?> nestedMap) {
            final java.util.LinkedHashMap<String, Object> normalized = new java.util.LinkedHashMap<>();
            nestedMap.forEach((key, nestedValue) -> normalized.put(String.valueOf(key), normalizeMetadataValue(nestedValue)));
            return Map.copyOf(normalized);
        }
        if (value instanceof Iterable<?> iterable) {
            final java.util.List<Object> normalized = new java.util.ArrayList<>();
            iterable.forEach(item -> normalized.add(normalizeMetadataValue(item)));
            return List.copyOf(normalized);
        }
        if (value.getClass().isArray()) {
            if (value instanceof int[] intArray) {
                final java.util.List<Integer> normalized = new java.util.ArrayList<>(intArray.length);
                for (int item : intArray) {
                    normalized.add(item);
                }
                return List.copyOf(normalized);
            }
            if (value instanceof short[] shortArray) {
                final java.util.List<Integer> normalized = new java.util.ArrayList<>(shortArray.length);
                for (short item : shortArray) {
                    normalized.add(Short.toUnsignedInt(item));
                }
                return List.copyOf(normalized);
            }
            if (value instanceof byte[] byteArray) {
                final java.util.List<Integer> normalized = new java.util.ArrayList<>(byteArray.length);
                for (byte item : byteArray) {
                    normalized.add(Byte.toUnsignedInt(item));
                }
                return List.copyOf(normalized);
            }
            if (value instanceof Object[] objectArray) {
                final java.util.List<Object> normalized = new java.util.ArrayList<>(objectArray.length);
                for (Object item : objectArray) {
                    normalized.add(normalizeMetadataValue(item));
                }
                return List.copyOf(normalized);
            }
        }
        return String.valueOf(value);
    }

    public static List<PackerAssetSummaryDTO> toAssetSummaryDTOs(List<PackerAssetSummary> summaries) {
        return summaries.stream().map(PackerReadMessageMapper::toAssetSummaryDTO).toList();
    }

    public static List<PackerDiagnosticDTO> toDiagnosticDTOs(List<PackerDiagnostic> diagnostics) {
        return diagnostics.stream().map(PackerReadMessageMapper::toDiagnosticDTO).toList();
    }

    public static List<PackerAssetActionAvailabilityDTO> toAssetActionAvailabilityDTOs(
            List<PackerAssetActionAvailability> actions) {
        return actions.stream().map(PackerReadMessageMapper::toAssetActionAvailabilityDTO).toList();
    }

    private static PackerAssetIdentityDTO toAssetIdentityDTO(PackerAssetIdentity identity) {
        return new PackerAssetIdentityDTO(
                identity.assetId(),
                identity.assetUuid(),
                identity.assetName(),
                identity.assetRoot());
    }

    private static Map<OutputCodecCatalog, List<PackerCodecConfigurationFieldDTO>> toCodecConfigurationFieldsByCodecDTO(
            Map<OutputCodecCatalog, List<PackerCodecConfigurationField>> fieldsByCodec) {
        return fieldsByCodec.entrySet().stream().collect(Collectors.toMap(
                Map.Entry::getKey,
                entry -> entry.getValue().stream().map(PackerReadMessageMapper::toCodecConfigurationFieldDTO).toList(),
                (left, right) -> left,
                java.util.LinkedHashMap::new));
    }

    private static PackerCodecConfigurationFieldDTO toCodecConfigurationFieldDTO(PackerCodecConfigurationField field) {
        return new PackerCodecConfigurationFieldDTO(
                field.key(),
                field.label(),
                field.fieldType(),
                field.value(),
                field.required(),
                field.options());
    }

    private static List<PackerCodecConfigurationFieldDTO> toCodecConfigurationFieldDTOs(
            List<PackerCodecConfigurationField> fields) {
        return fields.stream().map(PackerReadMessageMapper::toCodecConfigurationFieldDTO).toList();
    }

    private static PackerDiagnosticDTO toDiagnosticDTO(PackerDiagnostic diagnostic) {
        return new PackerDiagnosticDTO(
                diagnostic.severity(),
                diagnostic.category(),
                diagnostic.message(),
                diagnostic.evidencePath(),
                diagnostic.blocking());
    }

    private static PackerAssetActionAvailabilityDTO toAssetActionAvailabilityDTO(
            PackerAssetActionAvailability action) {
        return new PackerAssetActionAvailabilityDTO(
                action.action(),
                action.enabled(),
                action.visible(),
                action.reason());
    }
}
