#!/usr/bin/env python3
"""Rewrap over-long Zig comment lines at a column limit (default 100).

Rules:
- Only comment-only lines are touched; trailing comments after code are left alone.
- Consecutive comment lines with the same indentation and marker (`//`, `///`, `//!`)
  form a block. Blank comment lines separate paragraphs within a block.
- A line whose text starts with `- ` begins a bullet paragraph; its continuation
  lines are the ones indented to align under the bullet's text (bullet lead + 2).
- Plain paragraphs join consecutive lines with the same text indentation;
  wrapped lines align where the first line's text begins.
- A paragraph is rewrapped only if at least one of its lines exceeds the limit,
  so deliberate short line breaks elsewhere are preserved.
"""

import argparse
import difflib
import re
import subprocess
import sys
import textwrap

LIMIT = 100
COMMENT_RE = re.compile(r"^(\s*)(//[/!]?)(?:\s(.*))?$")
BULLET_RE = re.compile(r"^(\s*)- (.*)$")


def split_paragraphs(texts):
    """texts: list of comment text (None for a bare marker line).
    Returns paragraphs: dicts with lead/hang/bullet/texts/lines(indices)."""
    paragraphs = []
    current = None
    for index, text in enumerate(texts):
        if text is None or text.strip() == "":
            paragraphs.append({"literal": True, "lines": [index]})
            current = None
            continue
        lead = len(text) - len(text.lstrip(" "))
        bullet = BULLET_RE.match(text)
        if bullet:
            current = {
                "lead": len(bullet.group(1)),
                "hang": len(bullet.group(1)) + 2,
                "bullet": True,
                "texts": [bullet.group(2)],
                "lines": [index],
            }
            paragraphs.append(current)
        elif current is not None and lead == current["hang"]:
            current["texts"].append(text)
            current["lines"].append(index)
        else:
            current = {
                "lead": lead,
                "hang": lead,
                "bullet": False,
                "texts": [text],
                "lines": [index],
            }
            paragraphs.append(current)
    return paragraphs


def wrap_paragraph(paragraph, prefix):
    joined = re.sub(r"\s+", " ", " ".join(t.strip() for t in paragraph["texts"]))
    if paragraph["bullet"]:
        initial = prefix + " " * paragraph["lead"] + "- "
    else:
        initial = prefix + " " * paragraph["lead"]
    subsequent = prefix + " " * paragraph["hang"]
    return textwrap.wrap(
        joined,
        width=LIMIT,
        initial_indent=initial,
        subsequent_indent=subsequent,
        break_long_words=False,
        break_on_hyphens=False,
    )


def rewrap_block(original_lines, indent, marker, texts):
    prefix = indent + marker + " "
    output = []
    for paragraph in split_paragraphs(texts):
        block_originals = [original_lines[i] for i in paragraph["lines"]]
        if paragraph.get("literal") or all(len(l) <= LIMIT for l in block_originals):
            output.extend(block_originals)
        else:
            output.extend(wrap_paragraph(paragraph, prefix))
    return output


def process(source):
    lines = source.split("\n")
    result = []
    i = 0
    while i < len(lines):
        match = COMMENT_RE.match(lines[i])
        if not match:
            result.append(lines[i])
            i += 1
            continue
        indent, marker = match.group(1), match.group(2)
        block_lines, texts = [], []
        while i < len(lines):
            m = COMMENT_RE.match(lines[i])
            if not m or m.group(1) != indent or m.group(2) != marker:
                break
            block_lines.append(lines[i])
            texts.append(m.group(3))
            i += 1
        result.extend(rewrap_block(block_lines, indent, marker, texts))
    return "\n".join(result)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--write", action="store_true", help="apply changes (default: diff only)")
    parser.add_argument("files", nargs="*", help="files to process (default: git ls-files '*.zig')")
    args = parser.parse_args()

    files = args.files or subprocess.run(
        ["git", "ls-files", "*.zig"], capture_output=True, text=True, check=True
    ).stdout.split()

    changed = 0
    for path in files:
        with open(path, encoding="utf-8") as f:
            source = f.read()
        rewrapped = process(source)
        if rewrapped == source:
            continue
        changed += 1
        if args.write:
            with open(path, "w", encoding="utf-8") as f:
                f.write(rewrapped)
            print(f"rewrapped {path}")
        else:
            sys.stdout.writelines(
                difflib.unified_diff(
                    source.splitlines(keepends=True),
                    rewrapped.splitlines(keepends=True),
                    fromfile=path,
                    tofile=path,
                )
            )
    if not changed:
        print("no comments over the limit")


if __name__ == "__main__":
    main()
