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

import org.junit.jupiter.api.Test;
import p.packer.messages.AssetReference;
import p.packer.messages.assets.AssetFamilyCatalog;
import p.packer.messages.assets.OutputCodecCatalog;
import p.packer.messages.assets.OutputFormatCatalog;
import p.studio.workspaces.assets.messages.*;

import java.nio.file.Path;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

final class AssetDetailsPaletteOverhaulingCoordinatorTest {
    @Test
    void defaultsToFirstTilePaletteWhenNoSelectedFilesExist() {
        final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
        coordinator.replaceDetails(tileDetails(List.of(), files("tile", 3)));

        assertTrue(coordinator.ready());
        assertEquals(0, coordinator.viewModel().selectedFiles().size());
        assertNull(coordinator.viewModel().previewTileFile());
        assertNull(coordinator.viewModel().previewPaletteFile());
    }

    @Test
    void clickingSelectedPaletteChangesOnlyPreviewPalette() {
        final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
        coordinator.replaceDetails(tileDetails(List.of(files("tile", 3).get(0), files("tile", 3).get(1)), List.of(files("tile", 3).get(2))));
        coordinator.beginEdit();

        final AssetWorkspaceBankCompositionFile first = coordinator.viewModel().availableFiles().get(0);
        coordinator.moveToSelected(List.of(first));
        coordinator.selectPreviewPalette(first);

        assertEquals(first.path(), coordinator.viewModel().previewPaletteFile().path());
        assertEquals(1, coordinator.viewModel().selectedFiles().size());
    }

    @Test
    void removingPreviewedPaletteFallsBackToFirstRemainingSelectedPalette() {
        final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
        final List<AssetWorkspaceBankCompositionFile> generated = files("tile", 3);
        coordinator.replaceDetails(tileDetails(List.of(generated.get(0), generated.get(1)), List.of(generated.get(2))));
        coordinator.beginEdit();
        final AssetWorkspaceBankCompositionFile first = coordinator.viewModel().availableFiles().get(0);
        coordinator.moveToSelected(List.of(first));
        coordinator.selectPreviewPalette(first);

        coordinator.moveToAvailable(List.of(first));

        assertEquals(1, coordinator.viewModel().selectedFiles().size());
        assertEquals(first.path(), coordinator.viewModel().previewPaletteFile().path());
    }

    @Test
    void deduplicatesPalettesByColorHash() {
        final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
        final AssetWorkspaceBankCompositionFile first = file("tile-a.png", List.of(0xFFFF0000, 0xFF00FF00));
        final AssetWorkspaceBankCompositionFile duplicate = file("tile-b.png", List.of(0xFFFF0000, 0xFF00FF00));
        final AssetWorkspaceBankCompositionFile unique = file("tile-c.png", List.of(0xFF0000FF));

        coordinator.replaceDetails(tileDetails(List.of(first), List.of(duplicate, unique)));

        assertEquals(2, coordinator.viewModel().allFiles().size());
        assertEquals(10, coordinator.viewModel().allFiles().getFirst().displayName().length());
        assertEquals(coordinator.viewModel().allFiles().stream().map(AssetWorkspaceBankCompositionFile::displayName).distinct().count(),
                coordinator.viewModel().allFiles().size());
    }

    private AssetWorkspaceAssetDetails tileDetails(
            List<AssetWorkspaceBankCompositionFile> selectedFiles,
            List<AssetWorkspaceBankCompositionFile> availableFiles) {
        return new AssetWorkspaceAssetDetails(
                new AssetWorkspaceAssetSummary(
                        AssetReference.forAssetId(1),
                        "bank",
                        AssetWorkspaceAssetState.REGISTERED,
                        AssetWorkspaceBuildParticipation.INCLUDED,
                        1,
                        AssetFamilyCatalog.TILE_BANK,
                        Path.of("/tmp/bank"),
                        false,
                        false),
                List.of(),
                OutputFormatCatalog.TILES_INDEXED_V1,
                OutputCodecCatalog.NONE,
                List.of(OutputCodecCatalog.NONE),
                Map.of(OutputCodecCatalog.NONE, List.of()),
                List.of(),
                Map.of(),
                new AssetWorkspaceBankCompositionDetails(availableFiles, selectedFiles, 0L),
                selectedFiles.stream().map(file -> (Map<String, Object>) file.metadata().get("palette")).toList(),
                List.of());
    }

    private List<AssetWorkspaceBankCompositionFile> files(String prefix, int count) {
        return java.util.stream.IntStream.range(0, count)
                .mapToObj(index -> file(
                        prefix + "-" + index + ".png",
                        List.of(
                                0xFF000000 | ((index + 1) << 16),
                                0xFF000000 | ((index + 1) << 8),
                                0xFF000000 | (index + 1))))
                .toList();
    }

    private AssetWorkspaceBankCompositionFile file(String path, List<Integer> colors) {
        return new AssetWorkspaceBankCompositionFile(
                path,
                path,
                256L,
                1L,
                null,
                Map.of(
                        "tile", Map.of(
                                "width", 2,
                                "height", 2,
                                "paletteIndices", List.of(0, 1, 2, 0)),
                        "palette", Map.of(
                                "originalArgb8888", colors,
                                "convertedRgb565", colors.stream().map(this::toRgb565).toList())));
    }

    private int toRgb565(int argb8888) {
        final int red = (argb8888 >> 16) & 0xFF;
        final int green = (argb8888 >> 8) & 0xFF;
        final int blue = argb8888 & 0xFF;
        final int r5 = (red >> 3) & 0x1F;
        final int g6 = (green >> 2) & 0x3F;
        final int b5 = (blue >> 3) & 0x1F;
        return (r5 << 11) | (g6 << 5) | b5;
    }
}
