package p.studio.workspaces.assets.details.bank;

import p.packer.messages.assets.AssetFamilyCatalog;
import p.studio.controls.banks.StudioAssetCapacitySeverity;

import java.util.Map;
import java.util.Objects;

public final class AssetBankCapacityService {
    private static final int TILE_ATLAS_SIZE_PX = 256;
    private static final long SOUND_BANK_MAX_BYTES = 1024L * 1024L;

    public AssetDetailsBankCompositionCapacityState evaluate(
            AssetFamilyCatalog assetFamily,
            int artifactCount,
            Map<String, String> outputMetadata,
            Map<String, Object> outputPipeline,
            long usedBytes) {
        final AssetFamilyCatalog safeFamily = Objects.requireNonNullElse(assetFamily, AssetFamilyCatalog.UNKNOWN);
        final Map<String, String> safeMetadata = Map.copyOf(Objects.requireNonNull(outputMetadata, "outputMetadata"));
        final Map<String, Object> safePipeline = Map.copyOf(Objects.requireNonNull(outputPipeline, "outputPipeline"));
        return switch (safeFamily) {
            case GLYPH_BANK -> evaluateGlyphBank(artifactCount, safeMetadata);
            case SCENE_BANK -> new AssetDetailsBankCompositionCapacityState(
                    0.0d,
                    StudioAssetCapacitySeverity.GREEN,
                    false,
                    artifactCount + " support files",
                    "");
            case SOUND_BANK -> evaluateSoundBank(resolveSoundBankUsedBytes(safePipeline, usedBytes));
            case UNKNOWN -> new AssetDetailsBankCompositionCapacityState(
                    0.0d,
                    StudioAssetCapacitySeverity.GREEN,
                    false,
                    artifactCount + " selected",
                    "");
        };
    }

    public int maxArtifactsForGlyphBank(Map<String, String> outputMetadata) {
        final String tileSizeValue = Objects.requireNonNull(outputMetadata, "outputMetadata")
                .getOrDefault("tile_size", "16x16");
        return switch (tileSizeValue) {
            case "8x8" -> slotsForTileSize(8);
            case "32x32" -> slotsForTileSize(32);
            default -> slotsForTileSize(16);
        };
    }

    private AssetDetailsBankCompositionCapacityState evaluateGlyphBank(
            int artifactCount,
            Map<String, String> outputMetadata) {
        final int maxSlots = maxArtifactsForGlyphBank(outputMetadata);
        final int safeArtifactCount = Math.max(0, artifactCount);
        final double progress = maxSlots <= 0 ? 0.0d : (double) safeArtifactCount / (double) maxSlots;
        final boolean blocked = safeArtifactCount >= maxSlots;
        return new AssetDetailsBankCompositionCapacityState(
                progress,
                severityFor(progress),
                blocked,
                safeArtifactCount + " / " + maxSlots,
                blocked ? "Glyph bank slot capacity reached." : "");
    }

    private AssetDetailsBankCompositionCapacityState evaluateSoundBank(long usedBytes) {
        final long safeUsedBytes = Math.max(0L, usedBytes);
        final double progress = (double) safeUsedBytes / (double) SOUND_BANK_MAX_BYTES;
        final boolean blocked = safeUsedBytes >= SOUND_BANK_MAX_BYTES;
        return new AssetDetailsBankCompositionCapacityState(
                progress,
                severityFor(progress),
                blocked,
                safeUsedBytes + " / " + SOUND_BANK_MAX_BYTES + " bytes",
                blocked ? "Sound bank byte capacity reached." : "");
    }

    private long resolveSoundBankUsedBytes(Map<String, Object> outputPipeline, long fallbackUsedBytes) {
        final Object samplesNode = outputPipeline.get("samples");
        if (!(samplesNode instanceof Map<?, ?> samples)) {
            return Math.max(0L, fallbackUsedBytes);
        }
        long maxEndExclusive = 0L;
        for (Object sampleValue : samples.values()) {
            if (!(sampleValue instanceof Map<?, ?> sampleMetadata)) {
                continue;
            }
            final long offset = longValue(sampleMetadata.get("offset"));
            final long length = Math.max(longValue(sampleMetadata.get("len")), longValue(sampleMetadata.get("length")));
            maxEndExclusive = Math.max(maxEndExclusive, Math.max(0L, offset) + Math.max(0L, length));
        }
        return Math.max(maxEndExclusive, Math.max(0L, fallbackUsedBytes));
    }

    private long longValue(Object value) {
        if (value instanceof Number number) {
            return number.longValue();
        }
        if (value instanceof String text) {
            try {
                return Long.parseLong(text.trim());
            } catch (NumberFormatException ignored) {
                return 0L;
            }
        }
        return 0L;
    }

    private static int slotsForTileSize(int tileSize) {
        final int tilesPerAxis = Math.max(1, TILE_ATLAS_SIZE_PX / tileSize);
        return tilesPerAxis * tilesPerAxis;
    }

    private StudioAssetCapacitySeverity severityFor(double progress) {
        if (progress >= 0.95d) {
            return StudioAssetCapacitySeverity.RED;
        }
        if (progress >= 0.65d) {
            return StudioAssetCapacitySeverity.ORANGE;
        }
        return StudioAssetCapacitySeverity.GREEN;
    }
}
