import java.lang.reflect.Constructor;
import java.lang.reflect.RecordComponent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class PengikatQuery {

    // DTO bentuk record: kontrak data permintaan pencarian.
    record Pencarian(String q, int halaman, int ukuran, List<String> tag) { }

    public static void main(String[] args) {
        Map<String, List<String>> parameter = pecah("q=kopi+gayo&halaman=2&ukuran=5&tag=arabika&tag=toraja");
        System.out.println("== parameter hasil parsing query string ==");
        parameter.forEach((k, v) -> System.out.println(k + " = " + v));

        Pencarian penuh = ikat(Pencarian.class, parameter);
        System.out.println();
        System.out.println("Binding penuh : " + penuh);

        Map<String, List<String>> sebagian = pecah("q=teh");
        Pencarian defaultBawaan = ikat(Pencarian.class, sebagian);
        System.out.println("Binding sebagian (tanpa default): " + defaultBawaan);
    }

    // Pecah query string menjadi map kunci -> daftar nilai.
    static Map<String, List<String>> pecah(String queryString) {
        Map<String, List<String>> hasil = new LinkedHashMap<>();
        for (String pasangan : queryString.split("&")) {
            int idx = pasangan.indexOf('=');
            String kunci = idx < 0 ? pasangan : pasangan.substring(0, idx);
            String nilai = idx < 0 ? "" : pasangan.substring(idx + 1).replace('+', ' ');
            hasil.computeIfAbsent(kunci, k -> new ArrayList<>()).add(nilai);
        }
        return hasil;
    }

    // Ikat map parameter ke record lewat RecordComponent: mirror
    // @RequestParam + @PathVariable pada method controller.
    @SuppressWarnings("unchecked")
    static <T extends Record> T ikat(Class<T> tipe, Map<String, List<String>> parameter) {
        RecordComponent[] komponen = tipe.getRecordComponents();
        Object[] argumen = new Object[komponen.length];
        for (int i = 0; i < komponen.length; i++) {
            String nama = komponen[i].getName();
            Class<?> tipeNilai = komponen[i].getType();
            List<String> nilai = parameter.getOrDefault(nama, List.of());
            if (tipeNilai == int.class) {
                argumen[i] = nilai.isEmpty() ? 0 : Integer.parseInt(nilai.get(0));
            } else if (tipeNilai == List.class) {
                argumen[i] = nilai;
            } else {
                argumen[i] = nilai.isEmpty() ? null : nilai.get(0);
            }
        }
        Class<?>[] tipeParam = Arrays.stream(komponen)
                .map(RecordComponent::getType)
                .toArray(Class[]::new);
        try {
            Constructor<T> konstruktor = tipe.getDeclaredConstructor(tipeParam);
            return konstruktor.newInstance(argumen);
        } catch (ReflectiveOperationException e) {
            throw new IllegalStateException("Binding gagal untuk " + tipe.getSimpleName());
        }
    }
}
