package p.studio.compiler.backend.irvm;

import java.util.HashMap;
import java.util.Map;
import java.util.OptionalInt;

final class IRVMIntrinsicRegistry {
    private static final Map<IntrinsicKey, Integer> FINAL_ID_BY_INTRINSIC = loadRegistry();

    private IRVMIntrinsicRegistry() {
    }

    static OptionalInt resolveFinalId(
            final String canonicalName,
            final long canonicalVersion) {
        if (canonicalName == null || canonicalName.isBlank()) {
            return OptionalInt.empty();
        }
        final var resolved = FINAL_ID_BY_INTRINSIC.get(new IntrinsicKey(canonicalName, canonicalVersion));
        if (resolved == null) {
            return OptionalInt.empty();
        }
        return OptionalInt.of(resolved);
    }

    static Map<String, Integer> snapshotByCanonicalIdentity() {
        final var snapshot = new HashMap<String, Integer>(FINAL_ID_BY_INTRINSIC.size());
        for (final var entry : FINAL_ID_BY_INTRINSIC.entrySet()) {
            snapshot.put(canonicalIdentity(entry.getKey().canonicalName(), entry.getKey().canonicalVersion()), entry.getValue());
        }
        return Map.copyOf(snapshot);
    }

    private static Map<IntrinsicKey, Integer> loadRegistry() {
        final var registry = new HashMap<IntrinsicKey, Integer>();
        final var identityByFinalId = new HashMap<Integer, String>();
        Integer previousFinalId = null;
        for (final var definition : IRVMIntrinsicDefinition.values()) {
            final var finalId = definition.finalId();
            if (previousFinalId != null && Integer.compareUnsigned(finalId, previousFinalId) <= 0) {
                throw new IllegalStateException(
                        "intrinsic final ids must be strictly increasing for deterministic evolution: "
                                + definition.canonicalIdentity());
            }
            register(registry, definition.canonicalName(), definition.canonicalVersion(), finalId);
            final var identity = definition.canonicalIdentity();
            final var previous = identityByFinalId.putIfAbsent(finalId, identity);
            if (previous != null && !previous.equals(identity)) {
                throw new IllegalStateException(
                        "duplicate intrinsic final id 0x%08X for %s and %s".formatted(finalId, previous, identity));
            }
            previousFinalId = finalId;
        }
        return Map.copyOf(registry);
    }

    private static String canonicalIdentity(
            final String canonicalName,
            final long canonicalVersion) {
        return canonicalName + "@" + canonicalVersion;
    }

    private static void register(
            final Map<IntrinsicKey, Integer> registry,
            final String canonicalName,
            final long canonicalVersion,
            final int finalId) {
        final var previous = registry.put(new IntrinsicKey(canonicalName, canonicalVersion), finalId);
        if (previous != null && previous != finalId) {
            throw new IllegalStateException("duplicate intrinsic registry identity: " + canonicalName + "@" + canonicalVersion);
        }
    }

    private record IntrinsicKey(
            String canonicalName,
            long canonicalVersion) {
    }
}
