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

import p.packer.messages.assets.AssetFamilyCatalog;
import p.studio.controls.forms.StudioFormMode;
import p.studio.controls.forms.StudioFormSession;
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetDetails;
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile;

import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public final class AssetDetailsPaletteOverhaulingCoordinator {
    private StudioFormSession<AssetDetailsPaletteOverhaulingDraft> formSession;

    public void replaceDetails(AssetWorkspaceAssetDetails details) {
        if (details == null || details.summary().assetFamily() != AssetFamilyCatalog.TILE_BANK) {
            formSession = null;
            return;
        }

        final List<AssetWorkspaceBankCompositionFile> allFiles = paletteFiles(details);
        final List<AssetWorkspaceBankCompositionFile> previewTileFiles = details.bankComposition().selectedFiles().stream()
                .filter(AssetDetailsPaletteOverhaulingCoordinator::supportsPalettePreview)
                .toList();
        final List<AssetWorkspaceBankCompositionFile> initialSelected = initialSelectedPaletteFiles(details, allFiles);

        final List<AssetWorkspaceBankCompositionFile> available = new ArrayList<>(allFiles);
        available.removeAll(initialSelected);
        final AssetDetailsPaletteOverhaulingDraft source = new AssetDetailsPaletteOverhaulingDraft(
                previewTileFiles,
                allFiles,
                available,
                initialSelected,
                previewTileFiles.isEmpty() ? null : previewTileFiles.getFirst().path(),
                null);
        if (formSession == null) {
            formSession = new StudioFormSession<>(source);
            return;
        }
        if (!Objects.equals(formSession.source(), source)) {
            formSession.replaceSource(source);
        }
    }

    public boolean ready() {
        return formSession != null;
    }

    public void beginEdit() {
        if (ready()) {
            formSession.beginEdit();
        }
    }

    public void apply() {
        if (ready()) {
            formSession.apply();
        }
    }

    public void reset() {
        if (ready()) {
            formSession.resetDraft();
        }
    }

    public void cancel() {
        if (ready()) {
            formSession.cancelEdit();
        }
    }

    public void moveToSelected(List<AssetWorkspaceBankCompositionFile> files) {
        if (ready() && formSession.mode() == StudioFormMode.EDITING) {
            formSession.updateDraft(current -> current.moveToSelected(files));
        }
    }

    public void moveToAvailable(List<AssetWorkspaceBankCompositionFile> files) {
        if (ready() && formSession.mode() == StudioFormMode.EDITING) {
            formSession.updateDraft(current -> current.moveToAvailable(files));
        }
    }

    public void selectPreviewTile(AssetWorkspaceBankCompositionFile file) {
        if (ready() && file != null) {
            formSession.updateDraft(current -> current.selectPreviewTile(file.path()));
        }
    }

    public void selectPreviewPalette(AssetWorkspaceBankCompositionFile file) {
        if (ready() && file != null) {
            formSession.updateDraft(current -> current.selectPreviewPalette(file.path()));
        }
    }

    public AssetDetailsPaletteOverhaulingViewModel viewModel() {
        if (!ready()) {
            return new AssetDetailsPaletteOverhaulingViewModel(false, false, List.of(), List.of(), List.of(), List.of(), null, null, 0);
        }

        final AssetDetailsPaletteOverhaulingDraft draft = formSession.draft();
        return new AssetDetailsPaletteOverhaulingViewModel(
                formSession.mode() == StudioFormMode.EDITING,
                formSession.isDirty(),
                draft.previewTileFiles(),
                draft.allFiles(),
                draft.availableFiles(),
                draft.selectedFiles(),
                fileByPath(draft.previewTileFiles(), draft.previewTilePath()),
                fileByPath(draft.allFiles(), draft.previewPalettePath()),
                draft.allFiles().size());
    }

    static boolean supportsPalettePreview(AssetWorkspaceBankCompositionFile file) {
        if (file == null) {
            return false;
        }
        return nestedMap(file.metadata(), "tile") != null && nestedMap(file.metadata(), "palette") != null;
    }

    @SuppressWarnings("unchecked")
    private static Map<String, Object> nestedMap(Map<String, Object> metadata, String key) {
        final Object value = metadata.get(key);
        if (value instanceof Map<?, ?> nested) {
            return (Map<String, Object>) nested;
        }
        return null;
    }

    private static List<AssetWorkspaceBankCompositionFile> paletteFiles(AssetWorkspaceAssetDetails details) {
        final Map<String, AssetWorkspaceBankCompositionFile> ordered = new LinkedHashMap<>();
        // `bankComposition` files are already projected by the packer read model from the current cache/walk state.
        for (AssetWorkspaceBankCompositionFile file : details.bankComposition().availableFiles()) {
            if (supportsPalettePreview(file)) {
                ordered.putIfAbsent(paletteHash(file), canonicalPaletteFile(file));
            }
        }
        for (AssetWorkspaceBankCompositionFile file : details.bankComposition().selectedFiles()) {
            if (supportsPalettePreview(file)) {
                ordered.putIfAbsent(paletteHash(file), canonicalPaletteFile(file));
            }
        }
        return List.copyOf(ordered.values());
    }

    @SuppressWarnings("unchecked")
    private static AssetWorkspaceBankCompositionFile canonicalPaletteFile(AssetWorkspaceBankCompositionFile file) {
        final String hash = paletteHash(file);
        final Map<String, Object> metadata = new LinkedHashMap<>(file.metadata());
        metadata.put("paletteHash", hash);
        return new AssetWorkspaceBankCompositionFile(
                hash,
                hash,
                file.size(),
                file.lastModified(),
                file.fingerprint(),
                metadata);
    }

    private static List<AssetWorkspaceBankCompositionFile> initialSelectedPaletteFiles(
            AssetWorkspaceAssetDetails details,
            List<AssetWorkspaceBankCompositionFile> allFiles) {
        final List<AssetWorkspaceBankCompositionFile> selected = new ArrayList<>();
        for (Map<String, Object> palette : details.pipelinePalettes()) {
            for (AssetWorkspaceBankCompositionFile file : allFiles) {
                if (!selected.contains(file) && paletteEquals(palette, nestedMap(file.metadata(), "palette"))) {
                    selected.add(file);
                    break;
                }
            }
        }
        return List.copyOf(selected);
    }

    private static boolean paletteEquals(Map<String, Object> left, Map<String, Object> right) {
        if (left == null || right == null) {
            return false;
        }
        return Objects.equals(left.get("originalArgb8888"), right.get("originalArgb8888"))
                && Objects.equals(left.get("convertedRgb565"), right.get("convertedRgb565"));
    }

    @SuppressWarnings("unchecked")
    private static String paletteHash(AssetWorkspaceBankCompositionFile file) {
        final Map<String, Object> palette = nestedMap(file.metadata(), "palette");
        final Object convertedValue = palette == null ? null : palette.get("convertedRgb565");
        final List<Integer> colors = convertedValue instanceof List<?> converted
                ? (List<Integer>) converted
                : List.of();
        try {
            final MessageDigest digest = MessageDigest.getInstance("SHA-256");
            for (int index = 0; index < 15; index++) {
                final int color = index < colors.size() && colors.get(index) != null ? colors.get(index) : 0;
                digest.update(ByteBuffer.allocate(4).putInt(color).array());
            }
            long value = 0L;
            final byte[] bytes = digest.digest();
            for (int index = 0; index < 8; index++) {
                value = (value << 8) | Byte.toUnsignedLong(bytes[index]);
            }
            value &= 0x0FFFFFFFFFFFFFFFL;
            final String base36 = Long.toUnsignedString(value, 36).toUpperCase();
            return base36.length() >= 10
                    ? base36.substring(0, 10)
                    : "0".repeat(10 - base36.length()) + base36;
        } catch (Exception exception) {
            throw new IllegalStateException("Unable to hash palette colors", exception);
        }
    }

    private static AssetWorkspaceBankCompositionFile fileByPath(List<AssetWorkspaceBankCompositionFile> files, String path) {
        if (path == null) {
            return null;
        }
        return files.stream()
                .filter(file -> path.equals(file.path()))
                .findFirst()
                .orElse(null);
    }
}
