#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Build ornamentation JSON files from ornamentation Excel workbooks.

Purpose
-------
Export the pre-normalized / ornamented witness text from Excel into JSON.

Input schema:
    Column 1 = sura
    Column 2 = verse
    Column 3 = text

Output schema:
    [
      {"sura": 1, "verse": 1, "text": "..."}
    ]

This script does NOT normalize text.
It preserves the ornamented text exactly as found in column 3.

Recursive behavior
------------------
By default, recursively discovers .xlsx files under --in-dir, while skipping
generated/output/system folders.
"""

from __future__ import annotations

import argparse
import json
import re
from pathlib import Path
from typing import Iterable, List

from openpyxl import load_workbook


RE_ORNAMENTATION_TOKEN = re.compile(r"(?i)(^|[\s_\-]+)ornamentation(?=$|[\s_\-]+)")
RE_MULTI_UNDERSCORES = re.compile(r"_+")
RE_VARIANT_SUFFIX = re.compile(r"(\([^)]+\))$")

DEFAULT_EXCLUDE_DIRS = {
    ".git",
    ".venv",
    "venv",
    "__pycache__",
    "unicode_reports",
    "normalized_outputs_all_with_json",
    "ornamentation_json",
    "json",
    "_audit",
    "_errors",
}


def output_stem_from_path(path: Path) -> str:
    stem = path.stem
    m = RE_VARIANT_SUFFIX.search(stem)
    variant = m.group(1) if m else ""
    if variant:
        stem = stem[:m.start()]
    stem = RE_ORNAMENTATION_TOKEN.sub(" ", stem)
    stem = stem.replace("-", " ").replace("_", " ")
    stem = " ".join(stem.split())
    stem = stem.replace(" ", "_")
    stem = RE_MULTI_UNDERSCORES.sub("_", stem).strip("_")
    stem = stem or "output"
    if variant:
        stem = stem + variant
    return stem


def is_excluded(path: Path, root: Path, exclude_dirs: set[str]) -> bool:
    try:
        rel_parts = path.relative_to(root).parts
    except ValueError:
        rel_parts = path.parts
    return any(part in exclude_dirs for part in rel_parts)


def discover_excel_files(root: Path, recursive: bool, exclude_dirs: set[str]) -> List[Path]:
    pattern = "**/*.xlsx" if recursive else "*.xlsx"
    files = []
    for path in root.glob(pattern):
        if path.name.startswith("~$"):
            continue
        if is_excluded(path, root, exclude_dirs):
            continue
        if path.is_file():
            files.append(path)
    return sorted(files, key=lambda p: str(p).lower())


def workbook_to_records(xlsx_path: Path) -> list[dict]:
    wb = load_workbook(xlsx_path, data_only=True, read_only=True)
    ws = wb.active

    header = next(ws.iter_rows(min_row=1, max_row=1, max_col=3, values_only=True), (None, None, None))
    h0 = str(header[0]).strip().lower() if header[0] is not None else ""
    h1 = str(header[1]).strip().lower() if header[1] is not None else ""
    if h0 != "sura" or h1 != "verse":
        raise ValueError(f"{xlsx_path.name}: unexpected first-row header in columns 1-2: {header}")

    records = []
    for row in ws.iter_rows(min_row=2, max_col=3, values_only=True):
        a, b, c = row
        if a is None and b is None and c is None:
            continue
        if a is None or b is None or c is None:
            continue
        try:
            sura = int(a)
            verse = int(b)
        except Exception:
            continue
        records.append({"sura": sura, "verse": verse, "text": str(c)})

    return records


def main() -> None:
    ap = argparse.ArgumentParser(description="Build ornamentation JSON from Excel files.")
    ap.add_argument("--in-dir", default=".", help="Input folder to scan for .xlsx files. Default: current folder.")
    ap.add_argument("--out-dir", default="ornamentation_json", help="Output folder for JSON files.")
    ap.add_argument("--no-recursive", action="store_true", help="Only scan the input folder, not subfolders.")
    ap.add_argument("--keep-existing", action="store_true", help="Do not delete previous JSON files in the output folder.")
    ap.add_argument("--exclude-dir", action="append", default=[], help="Additional directory name to exclude. Can be used multiple times.")
    args = ap.parse_args()

    root = Path(args.in_dir).resolve()
    out_dir = Path(args.out_dir).resolve()
    exclude_dirs = DEFAULT_EXCLUDE_DIRS | set(args.exclude_dir)

    out_dir.mkdir(parents=True, exist_ok=True)

    if not args.keep_existing:
        for stale in out_dir.glob("*.json"):
            stale.unlink()

    excel_files = discover_excel_files(root, recursive=not args.no_recursive, exclude_dirs=exclude_dirs)

    written = []
    failures = []

    for xlsx_path in excel_files:
        try:
            records = workbook_to_records(xlsx_path)
            out_name = output_stem_from_path(xlsx_path) + "_Ornamentation.json"
            out_path = out_dir / out_name
            out_path.write_text(json.dumps(records, ensure_ascii=False, indent=2), encoding="utf-8")
            written.append((str(xlsx_path.relative_to(root)), out_name, len(records)))
            print(f"[OK] {xlsx_path.relative_to(root)} -> {out_name} ({len(records)} rows)")
        except Exception as exc:
            failures.append((str(xlsx_path), str(exc)))
            print(f"[FAIL] {xlsx_path} :: {exc}")

    print(f"\nCreated folder: {out_dir}")
    print(f"Source folder: {root}")
    print(f"Excel files found: {len(excel_files)}")
    print(f"JSON files written: {len(written)}")

    if failures:
        error_path = out_dir / "_ornamentation_json_errors.json"
        error_path.write_text(json.dumps([
            {"file": file, "error": error} for file, error in failures
        ], ensure_ascii=False, indent=2), encoding="utf-8")
        print(f"Failures: {len(failures)}. Error report: {error_path}")
        raise SystemExit(2)


if __name__ == "__main__":
    main()
