import json
import re
import sys
from pathlib import Path

import pypdf


def clean(value: str) -> str:
    return re.sub(r"\s+", " ", value).strip(" .")


def normalize_regency_name(regency_type: str, raw_name: str) -> str:
    # Cut off at double-spaces, "Undang", or the first standalone digit sequence
    # (statistical columns like kecamatan count / population leak into the name on
    # some pages – e.g. DKI Jakarta where column spacing is tight).
    name = clean(re.split(r"\s{2,}|\s+Undang\b", raw_name)[0])

    # Strip trailing tokens that are purely numeric (stats columns bleeding in)
    name = re.sub(r"(?:\s+\d[\d.,]*)+$", "", name).strip()

    words = name.split()

    if regency_type == "KOTA" and len(words) % 2 == 0:
        half = len(words) // 2
        if words[:half] == words[half:]:
            return " ".join(words[:half])

    return name


def parse_locations(pdf_path: Path) -> dict:
    reader = pypdf.PdfReader(str(pdf_path))

    provinces: dict[str, dict] = {}
    regencies: dict[str, dict] = {}
    districts: dict[str, dict] = {}
    villages: dict[str, dict] = {}
    skipped: list[dict] = []
    pending_regency: tuple[str, str] | None = None

    heading_re = re.compile(
        r"C\.[a-z]\.\d+\)\s+(Kabupaten|Kota)\s+(.+?)\s+Provinsi\s+(.+)",
        re.IGNORECASE,
    )
    province_re = re.compile(
        r"^(\d{2})\s+([A-Z][A-Za-z .\-]+?)(?:\s{2,}|\s+Undang|$)",
        re.IGNORECASE,
    )
    regency_code_only_re = re.compile(r"^(\d{2}\.\d{2})$")
    regency_re = re.compile(
        # Stop at: double-space, a bare digit (stats columns), or end-of-line
        r"^(\d{2}\.\d{2})\s+(Kabupaten|Kota)\s+(.+?)(?:\s{2,}|\s+\d|$)",
        re.IGNORECASE,
    )
    district_count_re = re.compile(
        r"^(\d{2}\.\d{2}\.\d{2})\s+\d+\s+(.+?)\s+(?:-|\d+)\s+(?:-|\d+)(?:\s|$)"
    )
    district_re = re.compile(r"^(\d{2}\.\d{2}\.\d{2})\s+(.+?)(?:\s{2,}|$)")
    village_re = re.compile(
        r"^(\d{2}\.\d{2}\.\d{2}\.(1|2)\d{3})\s+\d+\s+(.+?)(?:\s{2,}|$)"
    )

    for page_index, page in enumerate(reader.pages, start=1):
        text = page.extract_text() or ""

        for raw_line in text.splitlines():
            line = clean(raw_line)

            if not line:
                continue

            heading = heading_re.search(line)
            if heading:
                pending_regency = (
                    heading.group(1).upper(),
                    clean(heading.group(2)),
                )

            province = province_re.match(line)
            if province:
                province_id = province.group(1)
                province_name = clean(province.group(2)).upper()

                if len(province_name) > 2 and not province_name.startswith("JUMLAH"):
                    provinces.setdefault(
                        province_id,
                        {
                            "id": province_id,
                            "name": province_name,
                        },
                    )

            regency_code_only = regency_code_only_re.match(line)
            if regency_code_only and pending_regency:
                regency_id = regency_code_only.group(1)
                regency_type, regency_name = pending_regency
                regencies[regency_id] = {
                    "id": regency_id,
                    "provinceId": regency_id[:2],
                    "name": regency_name,
                    "type": regency_type,
                }
                pending_regency = None
                continue

            regency = regency_re.match(line)
            if regency and regency.group(1) not in regencies:
                regency_id = regency.group(1)
                regency_type = regency.group(2).upper()
                regencies[regency_id] = {
                    "id": regency_id,
                    "provinceId": regency_id[:2],
                    "name": normalize_regency_name(regency_type, regency.group(3)),
                    "type": regency_type,
                }

            village = village_re.match(line)
            if village:
                village_id = village.group(1)
                marker = village.group(2)
                village_name = clean(village.group(3))

                villages[village_id] = {
                    "id": village_id,
                    "districtId": ".".join(village_id.split(".")[:3]),
                    "name": village_name,
                    "type": "KELURAHAN" if marker == "1" else "DESA",
                }
                continue

            district = district_count_re.match(line) or district_re.match(line)
            if district:
                district_id = district.group(1)
                district_name = re.split(
                    r"\s{2,}|\s+Perbaikan\b|\s+Menjadi\b|\s+Undang\b",
                    clean(district.group(2)),
                )[0].strip()

                if re.match(
                    r"^(Kabupaten|Kota|Provinsi|Undang|Jumlah|Keterangan)\b",
                    district_name,
                    re.IGNORECASE,
                ):
                    skipped.append(
                        {
                            "page": page_index,
                            "code": district_id,
                            "reason": "invalid district name",
                        }
                    )
                    continue

                if len(district_name) > 1:
                    districts[district_id] = {
                        "id": district_id,
                        "regencyId": ".".join(district_id.split(".")[:2]),
                        "name": district_name,
                        "binderbyteId": f"dist_{district_id}",
                    }

    # Ensure every referenced parent has at least a placeholder record, so import can
    # surface parser gaps without failing halfway through a large import.
    for district_id in sorted({village["districtId"] for village in villages.values()}):
        districts.setdefault(
            district_id,
            {
                "id": district_id,
                "regencyId": ".".join(district_id.split(".")[:2]),
                "name": f"Kecamatan {district_id}",
                "binderbyteId": f"dist_{district_id}",
            },
        )

    for regency_id in sorted({district["regencyId"] for district in districts.values()}):
        regencies.setdefault(
            regency_id,
            {
                "id": regency_id,
                "provinceId": regency_id[:2],
                "name": f"Wilayah {regency_id}",
                "type": "KOTA" if int(regency_id.split(".")[1]) >= 70 else "KABUPATEN",
            },
        )

    for province_id in sorted({regency["provinceId"] for regency in regencies.values()}):
        provinces.setdefault(
            province_id,
            {
                "id": province_id,
                "name": f"PROVINSI {province_id}",
            },
        )

    return {
        "provinces": list(provinces.values()),
        "regencies": list(regencies.values()),
        "districts": list(districts.values()),
        "villages": list(villages.values()),
        "skipped": skipped[:100],
    }


def main() -> None:
    if len(sys.argv) != 3:
        raise SystemExit("Usage: parse_locations_pdf.py <source.pdf> <output.json>")

    pdf_path = Path(sys.argv[1])
    output_path = Path(sys.argv[2])

    if not pdf_path.exists():
        raise SystemExit(f"PDF not found: {pdf_path}")

    data = parse_locations(pdf_path)
    output_path.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")


if __name__ == "__main__":
    main()
