"""Combine per-branch changelog entries into a package CHANGELOG.md.

Usage:
    python scripts/combine-changelogs.py PACKAGE

Reads all .md files from packages/<PACKAGE>/changelogs/, combines them by
subsection (Enhancements / Features / Fixes), prepends the result to
packages/<PACKAGE>/CHANGELOG.md, and deletes the source entries.
"""

from __future__ import annotations

import os
import sys
import warnings
from collections import OrderedDict
from pathlib import Path

import tomlkit

REPO_ROOT = Path(__file__).resolve().parent.parent
PACKAGES_DIR = REPO_ROOT / "packages"

SUBSECTION_TYPES = ["### Enhancements", "### Features", "### Fixes"]


def ensure_changelog_folder_purity(folder: Path) -> None:
    """Raise if the changelogs folder contains non-.md files."""
    if not folder.exists():
        return
    for name in os.listdir(folder):
        if not name.endswith(".md"):
            msg = (
                f"Found non-markdown file {name!r} in {folder}. "
                "Changelogs must be .md files."
            )
            raise ValueError(msg)


def parse_subsections(path: Path) -> dict[str, list[str]]:
    """Extract subsection -> lines mapping from a changelog entry."""
    subsections: dict[str, list[str]] = {}
    current: str | None = None

    for line in path.read_text().splitlines():
        if any(st in line for st in SUBSECTION_TYPES):
            current = line.strip()
            subsections[current] = []
        elif line.strip() and current is not None:
            subsections[current].append(line.strip().lstrip("-").lstrip(" "))

    return subsections


def combine_files(folder: Path) -> dict[str, list[str]]:
    """Combine subsections from all changelog entries in a folder."""
    ensure_changelog_folder_purity(folder)
    combined: dict[str, list[str]] = {}

    if not folder.exists():
        return combined

    for name in sorted(os.listdir(folder)):
        if not name.endswith(".md"):
            warnings.warn(
                f"Ignoring non-markdown file {name!r} in {folder}.",
                stacklevel=2,
            )
            continue

        for section, lines in parse_subsections(folder / name).items():
            combined.setdefault(section, []).extend(lines)

    return combined


def serialize(combined: dict[str, list[str]], version: str) -> str:
    """Format combined subsections as a markdown release block."""
    out = f"## {version}"
    for section, lines in combined.items():
        out += f"\n\n{section}"
        for line in lines:
            out += f"\n- {line}"
    out += "\n\n"
    return out


def main() -> None:
    """Combine changelogs for a package and update CHANGELOG.md."""
    if len(sys.argv) < 2:
        print("Usage: python scripts/combine-changelogs.py PACKAGE")
        sys.exit(1)

    package = sys.argv[1]
    pkg_dir = PACKAGES_DIR / package
    changelogs_dir = pkg_dir / "changelogs"
    changelog_md = pkg_dir / "CHANGELOG.md"
    pyproject = pkg_dir / "pyproject.toml"

    if not pyproject.exists():
        print(f"No pyproject.toml at {pyproject}")
        sys.exit(1)

    data = tomlkit.parse(pyproject.read_text())
    version = str(data["project"]["version"])

    combined = OrderedDict(sorted(combine_files(changelogs_dir).items()))
    if not any(combined.values()):
        print(f"{package}: no changelog entries to combine.")
        return

    updates = serialize(combined, version)

    existing = changelog_md.read_text() if changelog_md.exists() else ""
    changelog_md.write_text(updates + existing)
    print(f"{package}: updated {changelog_md.relative_to(REPO_ROOT)}")

    # Clean up individual entries.
    if changelogs_dir.exists():
        for entry in changelogs_dir.iterdir():
            entry.unlink()
    print(f"{package}: cleaned up {changelogs_dir.relative_to(REPO_ROOT)}/")


if __name__ == "__main__":
    main()
