#!/usr/bin/env python3
"""Export a mixed-type JSON array to CSV using an explicit type contract.

The JSON array contains a mix of objects and non-object scalars (strings,
numbers). Objects become data rows; scalars become rows with an explicit
scalar_label, scalar_type and scalar_repr column, and their id left empty.

The contract is: only objects with an "id" field are treated as structured
records. Every other element is classified as a scalar and never silently
coerced into a pseudo-record.

Usage:
    python mixed-types-export.py mixed-types-source.json [--output out.csv]
"""

import argparse
import json
import sys
import hashlib
from pathlib import Path

SCHEMA = ["id", "type", "value", "scalar_label", "scalar_type", "scalar_repr"]


def classify(element):
    """Return (kind, fields_dict) for one array element."""
    if isinstance(element, dict):
        return "object", element
    # Classify scalar types explicitly
    if isinstance(element, str):
        return "scalar", {"scalar_type": "string", "scalar_repr": element}
    if isinstance(element, bool):
        return "scalar", {"scalar_type": "boolean", "scalar_repr": "true" if element else "false"}
    if isinstance(element, int) and not isinstance(element, bool):
        return "scalar", {"scalar_type": "integer", "scalar_repr": str(element)}
    if isinstance(element, float):
        return "scalar", {"scalar_type": "float", "scalar_repr": repr(element)}
    if element is None:
        return "scalar", {"scalar_type": "null", "scalar_repr": ""}
    return "scalar", {"scalar_type": type(element).__name__, "scalar_repr": str(element)}


def escape_csv(value):
    """RFC 4180 quoting."""
    s = "" if value is None else str(value)
    if any(c in s for c in [",", '"', "\r", "\n"]):
        s = '"' + s.replace('"', '""') + '"'
    return s


def export(records, output_path):
    """Export the mixed-type array to CSV. Returns a summary dict."""
    rows = []
    scalar_count = 0
    object_count = 0
    rejected = []

    for idx, element in enumerate(records):
        kind, fields = classify(element)
        if kind == "object":
            # Require an "id" field for structured records
            if "id" not in fields:
                rejected.append({"index": idx, "reason": "object_without_id", "keys": list(fields.keys())})
                continue
            row = {col: "" for col in SCHEMA}
            row["id"] = fields.get("id", "")
            row["type"] = fields.get("type", "")
            # Flatten the value field: if it's a dict, serialize as JSON; else stringify
            val = fields.get("value", "")
            if isinstance(val, (dict, list)):
                row["value"] = json.dumps(val, ensure_ascii=False)
            else:
                row["value"] = str(val) if val is not None else ""
            rows.append(row)
            object_count += 1
        else:
            row = {col: "" for col in SCHEMA}
            row["scalar_label"] = "scalar"
            row["scalar_type"] = fields["scalar_type"]
            row["scalar_repr"] = fields["scalar_repr"]
            rows.append(row)
            scalar_count += 1

    # Write CSV
    lines = [",".join(SCHEMA)]
    for row in rows:
        lines.append(",".join(escape_csv(row[col]) for col in SCHEMA))
    csv_text = "\r\n".join(lines) + "\r\n"

    output_path.write_text(csv_text, encoding="utf-8")

    return {
        "input_elements": len(records),
        "object_rows": object_count,
        "scalar_rows": scalar_count,
        "rejected": rejected,
        "csv_data_rows": len(rows),
        "csv_columns": len(SCHEMA),
        "csv_bytes": len(csv_text.encode("utf-8")),
        "csv_sha256": hashlib.sha256(csv_text.encode("utf-8")).hexdigest(),
    }


def main():
    parser = argparse.ArgumentParser(description="Export mixed-type JSON array to CSV")
    parser.add_argument("input", help="Path to JSON source file")
    parser.add_argument("--output", default=None, help="Output CSV path (default: stdout)")
    args = parser.parse_args()

    src = Path(args.input)
    raw = src.read_bytes()
    source_sha = hashlib.sha256(raw).hexdigest()

    try:
        data = json.loads(raw.decode("utf-8"))
    except (json.JSONDecodeError, UnicodeDecodeError) as e:
        print(json.dumps({"error": str(e), "source_sha256": source_sha}, ensure_ascii=False))
        sys.exit(1)

    if not isinstance(data, list):
        print(json.dumps({"error": "Top-level JSON is not an array"}))
        sys.exit(1)

    if args.output:
        out = Path(args.output)
    else:
        out = Path("mixed-types-output.csv")

    summary = export(data, out)
    summary["source_sha256"] = source_sha
    summary["source_bytes"] = len(raw)
    summary["output_path"] = str(out)

    print(json.dumps(summary, indent=2, ensure_ascii=False))


if __name__ == "__main__":
    main()
