package p.studio.compiler.models;

import p.studio.compiler.source.Span;
import p.studio.utilities.structures.ReadOnlyList;

import java.util.Objects;

public record IRReservedMetadata(
        ReadOnlyList<HostMethodBinding> hostMethodBindings,
        ReadOnlyList<BuiltinTypeSurface> builtinTypeSurfaces,
        ReadOnlyList<BuiltinConstSurface> builtinConstSurfaces,
        ReadOnlyList<String> requiredCapabilities) {

    public IRReservedMetadata {
        hostMethodBindings = hostMethodBindings == null ? ReadOnlyList.empty() : hostMethodBindings;
        builtinTypeSurfaces = builtinTypeSurfaces == null ? ReadOnlyList.empty() : builtinTypeSurfaces;
        builtinConstSurfaces = builtinConstSurfaces == null ? ReadOnlyList.empty() : builtinConstSurfaces;
        requiredCapabilities = requiredCapabilities == null ? ReadOnlyList.empty() : requiredCapabilities;
    }

    public static IRReservedMetadata empty() {
        return new IRReservedMetadata(
                ReadOnlyList.empty(),
                ReadOnlyList.empty(),
                ReadOnlyList.empty(),
                ReadOnlyList.empty());
    }

    public record HostMethodBinding(
            String ownerName,
            String sourceMethodName,
            String abiModule,
            String abiMethod,
            long abiVersion,
            int retSlots,
            Integer assetLoweringParam,
            boolean capabilityDeclared,
            String requiredCapability,
            Span span) {
        public HostMethodBinding {
            ownerName = Objects.requireNonNull(ownerName, "ownerName");
            sourceMethodName = Objects.requireNonNull(sourceMethodName, "sourceMethodName");
            abiModule = Objects.requireNonNull(abiModule, "abiModule");
            abiMethod = Objects.requireNonNull(abiMethod, "abiMethod");
            if (retSlots < 0) {
                throw new IllegalArgumentException("host retSlots must be non-negative");
            }
            if (assetLoweringParam != null && assetLoweringParam < 0) {
                throw new IllegalArgumentException("assetLoweringParam must be non-negative");
            }
            requiredCapability = requiredCapability == null ? "" : requiredCapability;
            span = Objects.requireNonNull(span, "span");
        }
    }

    public record BuiltinTypeSurface(
            String sourceTypeName,
            String canonicalTypeName,
            long canonicalVersion,
            ReadOnlyList<BuiltinFieldSurface> fields,
            ReadOnlyList<IntrinsicSurface> intrinsics,
            Span span) {
        public BuiltinTypeSurface {
            sourceTypeName = Objects.requireNonNull(sourceTypeName, "sourceTypeName");
            canonicalTypeName = Objects.requireNonNull(canonicalTypeName, "canonicalTypeName");
            fields = fields == null ? ReadOnlyList.empty() : fields;
            intrinsics = intrinsics == null ? ReadOnlyList.empty() : intrinsics;
            span = Objects.requireNonNull(span, "span");
        }
    }

    public record BuiltinFieldSurface(
            String fieldName,
            String declaredTypeSurface,
            long slotStart,
            Span span) {
        public BuiltinFieldSurface {
            fieldName = Objects.requireNonNull(fieldName, "fieldName");
            declaredTypeSurface = Objects.requireNonNull(declaredTypeSurface, "declaredTypeSurface");
            span = Objects.requireNonNull(span, "span");
        }
    }

    public record IntrinsicSurface(
            String sourceMethodName,
            String canonicalName,
            long canonicalVersion,
            int argSlots,
            int retSlots,
            Span span) {
        public IntrinsicSurface {
            sourceMethodName = Objects.requireNonNull(sourceMethodName, "sourceMethodName");
            canonicalName = Objects.requireNonNull(canonicalName, "canonicalName");
            if (argSlots < 0 || retSlots < 0) {
                throw new IllegalArgumentException("intrinsic slot counts must be non-negative");
            }
            span = Objects.requireNonNull(span, "span");
        }
    }

    public record BuiltinConstSurface(
            String sourceConstName,
            String canonicalTarget,
            String canonicalName,
            long canonicalVersion,
            Span span) {
        public BuiltinConstSurface {
            sourceConstName = Objects.requireNonNull(sourceConstName, "sourceConstName");
            canonicalTarget = Objects.requireNonNull(canonicalTarget, "canonicalTarget");
            canonicalName = Objects.requireNonNull(canonicalName, "canonicalName");
            span = Objects.requireNonNull(span, "span");
        }
    }
}
