import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;

public final class TypeBoundaryDemo {
    private TypeBoundaryDemo() {
    }

    sealed interface PricingRule permits FixedDiscount, RateDiscount {
        long apply(long originalCents);
    }

    record FixedDiscount(long cents) implements PricingRule {
        FixedDiscount {
            if (cents < 0) {
                throw new IllegalArgumentException("cents must not be negative");
            }
        }

        @Override
        public long apply(long originalCents) {
            requireNonNegativePrice(originalCents);
            return Math.max(0, originalCents - cents);
        }
    }

    record RateDiscount(int basisPoints) implements PricingRule {
        RateDiscount {
            if (basisPoints < 0 || basisPoints > 10_000) {
                throw new IllegalArgumentException(
                        "basisPoints must be in [0, 10000]");
            }
        }

        @Override
        public long apply(long originalCents) {
            requireNonNegativePrice(originalCents);
            long multiplier = 10_000L - basisPoints;
            return Math.multiplyExact(originalCents, multiplier) / 10_000L;
        }
    }

    record DiscountRequest(String kind, Integer amount) {
    }

    record OrderTags(List<String> values) {
        OrderTags {
            values = List.copyOf(Objects.requireNonNull(values, "values"));
        }
    }

    record OrderId(String value) {
        OrderId {
            if (value == null || value.isBlank()) {
                throw new IllegalArgumentException("order id must not be blank");
            }
        }
    }

    enum OrderStatus {
        CREATED,
        PAID
    }

    static final class Order {
        private final OrderId id;
        private OrderStatus status = OrderStatus.CREATED;

        Order(OrderId id) {
            this.id = Objects.requireNonNull(id, "id");
        }

        void markPaid() {
            status = OrderStatus.PAID;
        }

        OrderStatus status() {
            return status;
        }

        @Override
        public boolean equals(Object other) {
            return this == other || other instanceof Order order && id.equals(order.id);
        }

        @Override
        public int hashCode() {
            return id.hashCode();
        }
    }

    static final class MutableOrderKey {
        private int identityPart;

        MutableOrderKey(int identityPart) {
            this.identityPart = identityPart;
        }

        void rename(int newIdentityPart) {
            identityPart = newIdentityPart;
        }

        @Override
        public boolean equals(Object other) {
            return other instanceof MutableOrderKey key
                    && identityPart == key.identityPart;
        }

        @Override
        public int hashCode() {
            return identityPart;
        }
    }

    static PricingRule toDomain(DiscountRequest request) {
        Objects.requireNonNull(request, "request");
        String kind = Objects.requireNonNull(request.kind(), "kind")
                .toUpperCase(Locale.ROOT);
        int amount = Objects.requireNonNull(request.amount(), "amount");
        return switch (kind) {
            case "FIXED" -> new FixedDiscount(amount);
            case "RATE" -> new RateDiscount(amount);
            default -> throw new IllegalArgumentException("unknown discount kind: " + kind);
        };
    }

    static String describe(PricingRule ignored) {
        return "rule";
    }

    static String describe(FixedDiscount ignored) {
        return "fixed";
    }

    public static void main(String[] args) {
        if (args.length > 1) {
            throw new IllegalArgumentException("expected zero or one mode");
        }
        String mode = args.length == 0 ? "normal" : args[0];
        switch (mode) {
            case "normal" -> runNormal();
            case "invalid-input" -> toDomain(new DiscountRequest("RATE", -10));
            case "mutable-key" -> runMutableKey();
            default -> throw new IllegalArgumentException("unknown mode: " + mode);
        }
    }

    private static void runNormal() {
        PricingRule declaredAsRule = toDomain(new DiscountRequest("FIXED", 1_000));
        System.out.println("declared-overload=" + describe(declaredAsRule));
        System.out.println("runtime-dispatch=" + declaredAsRule.apply(10_000));
        PricingRule validRate = toDomain(new DiscountRequest("RATE", 1_000));
        System.out.println("valid-rate=" + validRate.apply(10_000));
        System.out.println(
                "value-equality=" + new FixedDiscount(1_000).equals(declaredAsRule));

        List<String> sourceTags = new ArrayList<>(List.of("vip"));
        OrderTags tags = new OrderTags(sourceTags);
        sourceTags.add("mutated-outside");
        System.out.println("defensive-copy=" + tags.values().equals(List.of("vip")));

        Order order = new Order(new OrderId("order-17"));
        Order sameEntity = new Order(new OrderId("order-17"));
        Set<Order> orders = new HashSet<>();
        orders.add(order);
        int hashBefore = order.hashCode();
        order.markPaid();
        System.out.println("entity-status=" + order.status());
        System.out.println("same-entity-equality=" + order.equals(sameEntity));
        System.out.println(
                "stable-id-hash="
                        + (hashBefore == order.hashCode()
                        && order.hashCode() == sameEntity.hashCode()));
        System.out.println("stable-id-lookup=" + orders.contains(sameEntity));
    }

    private static void runMutableKey() {
        MutableOrderKey key = new MutableOrderKey(7);
        Set<MutableOrderKey> keys = new HashSet<>();
        keys.add(key);
        int hashBefore = key.hashCode();
        boolean lookupBefore = keys.contains(key);
        key.rename(8);
        boolean hashStable = hashBefore == key.hashCode();

        System.err.println("mutable-lookup-before=" + lookupBefore);
        System.err.println("mutable-hash-stable=" + hashStable);
        System.err.println("mutable-lookup-after=" + keys.contains(key));
        System.err.println(
                "diagnosis=key hash changed while stored; Set behavior is unspecified");
        if (!lookupBefore || hashStable) {
            throw new AssertionError("the negative fixture did not reach its target state");
        }
        System.exit(2);
    }

    private static void requireNonNegativePrice(long originalCents) {
        if (originalCents < 0) {
            throw new IllegalArgumentException("originalCents must not be negative");
        }
    }
}
