import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Serializer dan parser JSON mini untuk inti telanjang aplikasi web:
 * objek datar dengan nilai teks, angka, boolean, atau null. Objek dan array
 * bersarang sengaja tidak didukung agar kelas ini tetap ringkas; pustaka
 * seperti Jackson menangani seluruh kasus pada aplikasi Spring Boot asli.
 */
public final class JsonWeb {

    private JsonWeb() {
        // Kelas utilitas: seluruh anggota static.
    }

    /** Meloloskan karakter yang wajib di-escape di dalam string JSON. */
    public static String escape(String teks) {
        StringBuilder hasil = new StringBuilder();
        for (int i = 0; i < teks.length(); i++) {
            char c = teks.charAt(i);
            switch (c) {
                case '"' -> hasil.append("\\\"");
                case '\\' -> hasil.append("\\\\");
                case '\n' -> hasil.append("\\n");
                case '\t' -> hasil.append("\\t");
                case '\r' -> hasil.append("\\r");
                default -> {
                    if (c < 0x20) {
                        hasil.append(String.format("\\u%04x", (int) c));
                    } else {
                        hasil.append(c);
                    }
                }
            }
        }
        return hasil.toString();
    }

    /** Membungkus teks menjadi string JSON sah (kutip plus escape). */
    public static String teks(String nilai) {
        return "\"" + escape(nilai) + "\"";
    }

    /** Satu tugas menjadi objek JSON dengan urutan field tetap. */
    public static String tugas(TugasWeb t) {
        return "{\"id\":" + t.id()
                + ",\"judul\":" + teks(t.judul())
                + ",\"pemilik\":" + teks(t.pemilik())
                + ",\"status\":\"" + t.status() + "\""
                + ",\"tenggat\":\"" + t.tenggat() + "\"}";
    }

    /** Daftar tugas menjadi array JSON. */
    public static String daftar(List<TugasWeb> semua) {
        StringBuilder hasil = new StringBuilder("[");
        for (TugasWeb t : semua) {
            if (hasil.length() > 1) {
                hasil.append(",");
            }
            hasil.append(tugas(t));
        }
        return hasil.append("]").toString();
    }

    /** Amplop galat tunggal. */
    public static String galat(int status, String pesan) {
        return "{\"status\":" + status + ",\"pesan\":" + teks(pesan) + "}";
    }

    /** Amplop galat validasi dengan rincian per field. */
    public static String galat(int status, String pesan, List<String> rincian) {
        if (rincian == null || rincian.isEmpty()) {
            return galat(status, pesan);
        }
        StringBuilder hasil = new StringBuilder("{\"status\":").append(status)
                .append(",\"pesan\":").append(teks(pesan)).append(",\"galat\":[");
        boolean pertama = true;
        for (String g : rincian) {
            if (!pertama) {
                hasil.append(",");
            }
            pertama = false;
            hasil.append(teks(g));
        }
        return hasil.append("]}").toString();
    }

    /**
     * Mem-parsing objek JSON datar menjadi Map dengan urutan field terjaga.
     * Nilai berupa String, Long, Double, Boolean, atau null; nilai bersarang
     * ditolak. Melempar IllegalArgumentException bila teks tidak sah.
     */
    public static Map<String, Object> parseObjek(String json) {
        Pembaca baca = new Pembaca(json);
        Map<String, Object> hasil = baca.objek();
        baca.spasi();
        if (!baca.habis()) {
            throw baca.galat("konten tambahan setelah objek");
        }
        return hasil;
    }

    /** Pembaca JSON karakter demi karakter dengan posisi terlacak. */
    private static final class Pembaca {

        private final String teks;
        private int pos = 0;

        Pembaca(String teks) {
            this.teks = teks;
        }

        /** Membaca '{' sampai '}' menjadi LinkedHashMap. */
        Map<String, Object> objek() {
            spasi();
            harap('{');
            Map<String, Object> hasil = new LinkedHashMap<>();
            spasi();
            if (lihat() == '}') {
                pos++;
                return hasil;
            }
            while (true) {
                spasi();
                String kunci = string();
                spasi();
                harap(':');
                spasi();
                hasil.put(kunci, nilai());
                spasi();
                if (lihat() == ',') {
                    pos++;
                    continue;
                }
                harap('}');
                return hasil;
            }
        }

        /** Satu nilai: string, true, false, null, atau angka datar. */
        Object nilai() {
            char c = lihat();
            if (c == '"') {
                return string();
            }
            if (c == '{' || c == '[') {
                throw galat("objek/array bersarang tidak didukung parser mini");
            }
            if (c == 't') {
                harapKata("true");
                return Boolean.TRUE;
            }
            if (c == 'f') {
                harapKata("false");
                return Boolean.FALSE;
            }
            if (c == 'n') {
                harapKata("null");
                return null;
            }
            return angka();
        }

        /** Membaca string berikut kutip pembuka dan penutupnya. */
        String string() {
            harap('"');
            StringBuilder hasil = new StringBuilder();
            while (pos < teks.length()) {
                char c = teks.charAt(pos++);
                if (c == '"') {
                    return hasil.toString();
                }
                if (c == '\\') {
                    hasil.append(escapeBerikutnya());
                } else {
                    hasil.append(c);
                }
            }
            throw galat("string tidak ditutup kutip");
        }

        /** Membaca satu karakter hasil escape, misalnya \n atau \". */
        char escapeBerikutnya() {
            if (pos >= teks.length()) {
                throw galat("escape berada di ujung string");
            }
            char c = teks.charAt(pos++);
            return switch (c) {
                case '"' -> '"';
                case '\\' -> '\\';
                case '/' -> '/';
                case 'n' -> '\n';
                case 't' -> '\t';
                case 'r' -> '\r';
                case 'b' -> '\b';
                case 'f' -> '\f';
                default -> throw galat("escape tidak dikenal: \\" + c);
            };
        }

        /** Membaca angka bulat atau pecahan tanpa notasi ilmiah. */
        Object angka() {
            int awal = pos;
            if (lihat() == '-') {
                pos++;
            }
            while (pos < teks.length() && "0123456789.".indexOf(teks.charAt(pos)) >= 0) {
                pos++;
            }
            String teksAngka = teks.substring(awal, pos);
            if (teksAngka.isEmpty() || teksAngka.equals("-")) {
                throw galat("nilai tidak dikenal mulai di posisi " + awal);
            }
            try {
                if (teksAngka.indexOf('.') >= 0) {
                    return Double.valueOf(teksAngka);
                }
                return Long.valueOf(teksAngka);
            } catch (NumberFormatException e) {
                throw galat("angka tidak sah: " + teksAngka);
            }
        }

        /** Memastikan kata kunci (true/false/null) ada di posisi sekarang. */
        void harapKata(String kata) {
            if (!teks.startsWith(kata, pos)) {
                throw galat("kata kunci '" + kata + "' tidak ditemukan");
            }
            pos += kata.length();
        }

        /** Melewati spasi dan whitespace lain. */
        void spasi() {
            while (pos < teks.length() && Character.isWhitespace(teks.charAt(pos))) {
                pos++;
            }
        }

        /** Melihat karakter pada posisi sekarang tanpa memajukannya. */
        char lihat() {
            if (pos >= teks.length()) {
                throw galat("teks berakhir sebelum waktunya");
            }
            return teks.charAt(pos);
        }

        /** Memastikan karakter yang diharapkan ada, lalu memajukan posisi. */
        void harap(char diharapkan) {
            char c = lihat();
            if (c != diharapkan) {
                throw galat("diharapkan '" + diharapkan + "', ditemukan '" + c + "'");
            }
            pos++;
        }

        boolean habis() {
            return pos >= teks.length();
        }

        IllegalArgumentException galat(String pesan) {
            return new IllegalArgumentException(pesan + " (posisi " + pos + ")");
        }
    }
}
