"""Count UTF-8 text lines with bounded parallel submissions. Python 3.9+."""
import argparse
from concurrent.futures import FIRST_COMPLETED, ThreadPoolExecutor, wait
import json
from pathlib import Path
import sys


def worker_count(value):
    number = int(value)
    if not 1 <= number <= 16:
        raise argparse.ArgumentTypeError("workers must be 1..16")
    return number


def count_lines(path):
    with path.open(encoding="utf-8", errors="strict") as source:
        return sum(1 for _ in source)


def collect(pending):
    done, _ = wait(pending, return_when=FIRST_COMPLETED)
    failed = 0
    for future in done:
        path = pending.pop(future)
        record = {"type": "file", "path": str(path)}
        try:
            record.update(ok=True, lines=future.result())
        except (OSError, UnicodeError, ValueError) as exc:
            failed += 1
            record.update(ok=False, error=f"{type(exc).__name__}: {exc}")
        print(json.dumps(record), flush=True)
    return len(done), failed


def run(manifest, workers):
    manifest = manifest.absolute()
    pending = {}
    processed = failed = peak_pending = 0
    with manifest.open(encoding="utf-8", errors="strict") as source:
        with ThreadPoolExecutor(max_workers=workers) as pool:
            for line in source:
                name = line.rstrip("\r\n")
                if not name:
                    continue
                path = manifest.parent / name
                pending[pool.submit(count_lines, path)] = path
                peak_pending = max(peak_pending, len(pending))
                if len(pending) >= 2 * workers:
                    count, errors = collect(pending)
                    processed += count
                    failed += errors
            while pending:
                count, errors = collect(pending)
                processed += count
                failed += errors
    print(json.dumps({
        "type": "summary", "processed": processed, "failed": failed,
        "workers": workers, "peak_pending": peak_pending,
    }), flush=True)
    return 1 if failed else 0


def main():
    parser = argparse.ArgumentParser(description=__doc__, allow_abbrev=False)
    parser.add_argument("manifest", type=Path, help="UTF-8 path list, one per line")
    parser.add_argument("--workers", type=worker_count, default=4)
    args = parser.parse_args()
    try:
        return run(args.manifest, args.workers)
    except (OSError, UnicodeError, ValueError) as exc:
        print(f"FATAL: {exc}", file=sys.stderr)
        return 1


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