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

import p.packer.messages.assets.AssetFamilyCatalog;
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetDetails;
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile;
import p.studio.workspaces.assets.metadata.AssetStudioGlyphMaterialArtifactBinding;
import p.studio.workspaces.assets.metadata.AssetStudioGlyphMaterialMetadata;
import p.studio.workspaces.assets.metadata.AssetStudioGlyphMaterialPalette;
import p.studio.workspaces.assets.metadata.AssetStudioGlyphMaterialSchema;
import p.studio.workspaces.assets.metadata.AssetStudioGlyphMaterialSchemaStatus;

import java.util.*;

public final class AssetDetailsColorSchemaCoordinator {
    private AssetWorkspaceAssetDetails details;
    private AssetStudioGlyphMaterialMetadata localMaterialMetadata;

    public void replaceDetails(AssetWorkspaceAssetDetails details) {
        this.details = details;
        this.localMaterialMetadata = details == null ? null : details.glyphMaterialMetadata();
    }

    public void replaceMaterialMetadata(AssetStudioGlyphMaterialMetadata metadata) {
        this.localMaterialMetadata = Objects.requireNonNull(metadata, "metadata");
    }

    public boolean ready() {
        return details != null && details.summary().assetFamily() == AssetFamilyCatalog.GLYPH_BANK;
    }

    public AssetStudioGlyphMaterialMetadata metadata() {
        if (!ready() || localMaterialMetadata == null) {
            return new AssetStudioGlyphMaterialMetadata(List.of(), List.of(), null);
        }
        return localMaterialMetadata;
    }

    public List<AssetStudioGlyphMaterialSchema> schemas() {
        return metadata().schemas();
    }

    public List<AssetWorkspaceBankCompositionFile> selectedTiles() {
        if (!ready()) {
            return List.of();
        }
        return details.bankComposition().selectedFiles().stream()
                .filter(AssetDetailsColorSchemaCoordinator::supportsSchemaAuthoring)
                .filter(file -> artifactId(file) != null)
                .toList();
    }

    public List<AssetWorkspaceBankCompositionFile> availableTilesForCreate() {
        final Set<String> reservedArtifactIds = new HashSet<>();
        for (AssetStudioGlyphMaterialSchema schema : schemas()) {
            schema.artifactBindings().forEach(binding -> reservedArtifactIds.add(binding.artifactId()));
        }
        return selectedTiles().stream()
                .filter(file -> !reservedArtifactIds.contains(artifactId(file)))
                .toList();
    }

    public List<AssetWorkspaceBankCompositionFile> availableTilesForEdit(String schemaId) {
        final AssetStudioGlyphMaterialSchema schema = findSchema(schemaId).orElse(null);
        if (schema == null) {
            return availableTilesForCreate();
        }
        final Set<String> current = schema.artifactBindings().stream()
                .map(AssetStudioGlyphMaterialArtifactBinding::artifactId)
                .collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new));
        final Set<String> reservedElsewhere = new HashSet<>();
        for (AssetStudioGlyphMaterialSchema candidate : schemas()) {
            if (candidate.schemaId().equals(schemaId)) {
                continue;
            }
            candidate.artifactBindings().forEach(binding -> reservedElsewhere.add(binding.artifactId()));
        }
        return selectedTiles().stream()
                .filter(file -> current.contains(artifactId(file)) || !reservedElsewhere.contains(artifactId(file)))
                .toList();
    }

    public boolean canRemove(String schemaId) {
        return metadata().palettes().stream().noneMatch(palette -> palette.schemaId().equals(schemaId));
    }

    public Optional<AssetStudioGlyphMaterialSchema> findSchema(String schemaId) {
        return schemas().stream().filter(schema -> schema.schemaId().equals(schemaId)).findFirst();
    }

    public AssetStudioGlyphMaterialMetadata upsertSchema(
            String schemaId,
            List<AssetWorkspaceBankCompositionFile> assignedTiles,
            List<String> orderedColors) {
        final AssetStudioGlyphMaterialMetadata current = metadata();
        final AssetStudioGlyphMaterialSchema existing = current.findSchema(schemaId).orElse(null);
        final AssetStudioGlyphMaterialSchema next = buildSchema(schemaId, existing, assignedTiles, orderedColors);
        final List<AssetStudioGlyphMaterialSchema> schemas = new ArrayList<>();
        boolean replaced = false;
        for (AssetStudioGlyphMaterialSchema schema : current.schemas()) {
            if (schema.schemaId().equals(schemaId)) {
                schemas.add(next);
                replaced = true;
            } else {
                schemas.add(schema);
            }
        }
        if (!replaced) {
            schemas.add(next);
        }
        schemas.sort(Comparator.comparing(AssetStudioGlyphMaterialSchema::schemaId, String.CASE_INSENSITIVE_ORDER));
        return new AssetStudioGlyphMaterialMetadata(schemas, current.palettes(), current.supportFile());
    }

    public AssetStudioGlyphMaterialMetadata removeSchema(String schemaId) {
        if (!canRemove(schemaId)) {
            throw new IllegalStateException("Schema is still referenced by one or more palettes.");
        }
        final AssetStudioGlyphMaterialMetadata current = metadata();
        final List<AssetStudioGlyphMaterialSchema> schemas = current.schemas().stream()
                .filter(schema -> !schema.schemaId().equals(schemaId))
                .toList();
        return new AssetStudioGlyphMaterialMetadata(schemas, current.palettes(), current.supportFile());
    }

    public String nextSchemaId() {
        int next = 1;
        final Set<String> ids = schemas().stream().map(AssetStudioGlyphMaterialSchema::schemaId).collect(java.util.stream.Collectors.toSet());
        while (ids.contains("schema-" + next)) {
            next += 1;
        }
        return "schema-" + next;
    }

    public int unassignedTileCount() {
        return availableTilesForCreate().size();
    }

    public static AssetStudioGlyphMaterialSchemaStatus effectiveStatus(
            AssetStudioGlyphMaterialSchema schema,
            List<AssetWorkspaceBankCompositionFile> selectedTiles) {
        if (schema.status() == AssetStudioGlyphMaterialSchemaStatus.INCONSISTENT) {
            return AssetStudioGlyphMaterialSchemaStatus.INCONSISTENT;
        }
        final Map<String, AssetWorkspaceBankCompositionFile> selectedByArtifactId = new HashMap<>();
        for (AssetWorkspaceBankCompositionFile tile : selectedTiles) {
            final String artifactId = artifactId(tile);
            if (artifactId != null) {
                selectedByArtifactId.put(artifactId, tile);
            }
        }
        for (AssetStudioGlyphMaterialArtifactBinding binding : schema.artifactBindings()) {
            final AssetWorkspaceBankCompositionFile selectedTile = selectedByArtifactId.get(binding.artifactId());
            if (selectedTile == null || !Objects.equals(binding.fingerprint(), fingerprint(selectedTile))) {
                return AssetStudioGlyphMaterialSchemaStatus.INCONSISTENT;
            }
        }
        return AssetStudioGlyphMaterialSchemaStatus.OK;
    }

    private AssetStudioGlyphMaterialSchema buildSchema(
            String schemaId,
            AssetStudioGlyphMaterialSchema existing,
            List<AssetWorkspaceBankCompositionFile> assignedTiles,
            List<String> orderedColors) {
        final Map<Integer, String> materialIndices = new LinkedHashMap<>();
        for (int index = 0; index < orderedColors.size(); index += 1) {
            materialIndices.put(index + 1, normalizeHtmlColor(orderedColors.get(index)));
        }
        final List<AssetStudioGlyphMaterialArtifactBinding> bindings = assignedTiles.stream()
                .map(file -> new AssetStudioGlyphMaterialArtifactBinding(artifactId(file), fingerprint(file)))
                .toList();
        final boolean unchanged = existing != null
                && existing.materialIndices().equals(materialIndices)
                && existing.artifactBindings().equals(bindings);
        final int version = existing == null
                ? 1
                : unchanged ? existing.version() : existing.version() + 1;
        final AssetStudioGlyphMaterialSchemaStatus status = existing == null || !unchanged
                ? AssetStudioGlyphMaterialSchemaStatus.OK
                : existing.status();
        return new AssetStudioGlyphMaterialSchema(schemaId, status, version, materialIndices, bindings);
    }

    static boolean supportsSchemaAuthoring(AssetWorkspaceBankCompositionFile file) {
        return file != null
                && artifactId(file) != null
                && fingerprint(file) != null;
    }

    static String artifactId(AssetWorkspaceBankCompositionFile file) {
        if (file == null) {
            return null;
        }
        final Object artifactId = file.metadata().get("artifactId");
        if (artifactId instanceof String value && !value.isBlank()) {
            return value.trim();
        }
        return null;
    }

    static String fingerprint(AssetWorkspaceBankCompositionFile file) {
        if (file == null) {
            return null;
        }
        if (file.fingerprint() != null && !file.fingerprint().isBlank()) {
            return file.fingerprint().trim();
        }
        final Object selectedFingerprint = file.metadata().get("selectedFingerprint");
        if (selectedFingerprint instanceof String value && !value.isBlank()) {
            return value.trim();
        }
        return null;
    }

    static List<String> tileColors(AssetWorkspaceBankCompositionFile file) {
        final Map<String, Object> palette = nestedMap(file.metadata(), "palette");
        if (palette == null) {
            return List.of();
        }
        final Object originalColors = palette.get("originalArgb8888");
        if (!(originalColors instanceof Iterable<?> iterable)) {
            return List.of();
        }
        final List<String> colors = new ArrayList<>();
        for (Object item : iterable) {
            if (!(item instanceof Number number)) {
                continue;
            }
            colors.add(String.format(
                    "#%02X%02X%02X",
                    (number.intValue() >> 16) & 0xFF,
                    (number.intValue() >> 8) & 0xFF,
                    number.intValue() & 0xFF));
        }
        return List.copyOf(colors);
    }

    @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 String normalizeHtmlColor(String color) {
        return Objects.requireNonNull(color, "color").trim().toUpperCase();
    }
}
