#!/usr/bin/env python3
"""Convert a bounded CSV file with a header row to a JSON array."""

from __future__ import annotations

import argparse
import csv
import json
from pathlib import Path
from typing import Any


def read_rows(path: Path) -> tuple[list[str], list[dict[str, str]]]:
    """Read *path* and return its unique header plus string-valued rows."""
    try:
        with path.open("r", encoding="utf-8-sig", newline="") as handle:
            reader = csv.reader(handle, strict=True)
            try:
                header = next(reader)
            except StopIteration as exc:
                raise ValueError("CSV input is empty") from exc
            if not header or any(not field for field in header):
                raise ValueError("CSV header fields must be non-empty")
            if len(set(header)) != len(header):
                raise ValueError("CSV header fields must be unique")
            rows: list[dict[str, str]] = []
            for row_number, values in enumerate(reader, start=2):
                if len(values) != len(header):
                    raise ValueError(
                        f"row {row_number} has {len(values)} fields; expected {len(header)}"
                    )
                rows.append(dict(zip(header, values)))
    except (OSError, UnicodeError, csv.Error) as exc:
        raise ValueError(f"could not read valid CSV from {path}: {exc}") from exc
    return header, rows


def convert(input_path: Path, output_path: Path) -> tuple[int, list[str]]:
    header, rows = read_rows(input_path)
    output_path.parent.mkdir(parents=True, exist_ok=True)
    with output_path.open("w", encoding="utf-8") as handle:
        json.dump(rows, handle, indent=2, ensure_ascii=False)
        handle.write("\n")
    return len(rows), header


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("input", type=Path, help="CSV file with a unique header row")
    parser.add_argument("output", type=Path, help="JSON array file to create")
    args = parser.parse_args()
    try:
        rows, fields = convert(args.input, args.output)
    except ValueError as exc:
        parser.error(str(exc))
    except OSError as exc:
        parser.error(f"could not write {args.output}: {exc}")
    print(f"wrote {rows} rows and {len(fields)} columns to {args.output}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
