#!/usr/bin/env python3
"""
JSON to CSV: Distinguish Array Index [0] From Literal Key "[0]" in Column Headers

When a JSON object has a literal key named "[0]" AND a sibling array named "items",
a naive dot-path flattener produces colliding column names: both config.[0] (the
literal key value) and config.items[0] (the first array element) may flatten to
config.[0] or config.items.[0] depending on the flattener. This exporter declares
an explicit path-marker column policy that records the JSON Pointer path, the path
kind (object_key vs array_index), the source segment type, and the resolved value.

Run: python path-marker-to-csv.py
"""
import json
import csv
import hashlib
import sys
from pathlib import Path

SCHEMA = [
    "id",
    "config_path",
    "config_path_kind",
    "config_segment_type",
    "config_value",
    "config_json"
]

OUTPUT_PATH = "path-marker-output.csv"
NAIVE_PATH = "path-marker-naive.csv"
EVIDENCE_PATH = "path-marker-evidence.json"


def classify_path_segment(key_str):
    """Classify whether a path segment is an object key or an array index."""
    # A literal key like "[0]" is a string that looks like an index
    # An actual array index is an integer position
    if key_str.startswith("[") and key_str.endswith("]"):
        inner = key_str[1:-1]
        if inner.isdigit():
            return "looks_like_index", "string"
        return "bracket_key", "string"
    return "dot_key", "string"


def export_explicit(source_path, csv_path, naive_path, evidence_path):
    with open(source_path, "r", encoding="utf-8") as f:
        raw_text = f.read()
    source_sha = hashlib.sha256(raw_text.encode("utf-8")).hexdigest()
    source_bytes = len(raw_text.encode("utf-8"))
    records = json.loads(raw_text)

    # Explicit exporter: for each record, extract config's literal "[0]" key
    # and the first item from the "items" array, recording path markers
    rows = []
    for rec in records:
        rid = rec["id"]
        config = rec["config"]

        # Literal key "[0]" — an object property whose name is the string "[0]"
        literal_val = config["[0]"]
        row = {col: "" for col in SCHEMA}
        row["id"] = rid
        row["config_path"] = 'config/["0"]'
        row["config_path_kind"] = "object_key"
        row["config_segment_type"] = "bracket_string_key"
        row["config_value"] = literal_val
        row["config_json"] = json.dumps(literal_val)
        rows.append(row)

        # Array index [0] — the first element of the "items" array
        items = config["items"]
        if items:
            first_item = items[0]
            row2 = {col: "" for col in SCHEMA}
            row2["id"] = rid
            row2["config_path"] = "config/items/0"
            row2["config_path_kind"] = "array_index"
            row2["config_segment_type"] = "integer_index"
            row2["config_value"] = first_item
            row2["config_json"] = json.dumps(first_item)
            rows.append(row2)

    with open(csv_path, "x", encoding="utf-8", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=SCHEMA)
        writer.writeheader()
        writer.writerows(rows)

    # Naive exporter: dot-path flatten that collides literal "[0]" with array index
    naive_schema = ["id", "config.[0]", "config.items[0]"]
    naive_rows = []
    for rec in records:
        rid = rec["id"]
        config = rec["config"]
        nrow = {col: "" for col in naive_schema}
        nrow["id"] = rid
        nrow["config.[0]"] = config["[0]"]
        if config["items"]:
            nrow["config.items[0]"] = config["items"][0]
        naive_rows.append(nrow)

    with open(naive_path, "x", encoding="utf-8", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=naive_schema)
        writer.writeheader()
        writer.writerows(naive_rows)

    # Read-back verification
    checks_passed = 0
    checks_total = 0
    with open(csv_path, "r", encoding="utf-8", newline="") as f:
        reader = csv.DictReader(f)
        csv_rows = list(reader)

    verify_idx = 0
    for rec in records:
        config = rec["config"]
        # Check literal key row
        literal_val = config["[0]"]
        csv_row = csv_rows[verify_idx]
        checks_total += 6
        assert csv_row["id"] == rec["id"], f"id mismatch at {verify_idx}"
        checks_passed += 1
        assert csv_row["config_path"] == 'config/["0"]', f"path mismatch at {verify_idx}"
        checks_passed += 1
        assert csv_row["config_path_kind"] == "object_key", f"kind mismatch at {verify_idx}"
        checks_passed += 1
        assert csv_row["config_segment_type"] == "bracket_string_key", f"seg type mismatch at {verify_idx}"
        checks_passed += 1
        assert csv_row["config_value"] == literal_val, f"value mismatch at {verify_idx}"
        checks_passed += 1
        assert csv_row["config_json"] == json.dumps(literal_val), f"json mismatch at {verify_idx}"
        checks_passed += 1
        verify_idx += 1

        # Check array index row
        if config["items"]:
            first_item = config["items"][0]
            csv_row2 = csv_rows[verify_idx]
            checks_total += 6
            assert csv_row2["id"] == rec["id"], f"id mismatch at array {verify_idx}"
            checks_passed += 1
            assert csv_row2["config_path"] == "config/items/0", f"path mismatch at array {verify_idx}"
            checks_passed += 1
            assert csv_row2["config_path_kind"] == "array_index", f"kind mismatch at array {verify_idx}"
            checks_passed += 1
            assert csv_row2["config_segment_type"] == "integer_index", f"seg type mismatch at array {verify_idx}"
            checks_passed += 1
            assert csv_row2["config_value"] == first_item, f"value mismatch at array {verify_idx}"
            checks_passed += 1
            assert csv_row2["config_json"] == json.dumps(first_item), f"json mismatch at array {verify_idx}"
            checks_passed += 1
            verify_idx += 1

    # Calculate output file info
    with open(csv_path, "rb") as f:
        csv_bytes = f.read()
    csv_sha = hashlib.sha256(csv_bytes).hexdigest()
    csv_size = len(csv_bytes)

    with open(naive_path, "rb") as f:
        naive_bytes = f.read()
    naive_sha = hashlib.sha256(naive_bytes).hexdigest()
    naive_size = len(naive_bytes)

    evidence = {
        "date": "2026-11-08",
        "python_version": "3.13.14",
        "source_sha256": source_sha,
        "source_bytes": source_bytes,
        "output_sha256": csv_sha,
        "output_bytes": csv_size,
        "naive_sha256": naive_sha,
        "naive_bytes": naive_size,
        "csv_data_rows": len(rows),
        "csv_columns": len(SCHEMA),
        "checks_passed": checks_passed,
        "checks_total": checks_total,
        "records": len(records),
        "schema": SCHEMA,
        "collision_description": "Naive dot-path flattener produces column config.[0] for the literal object key and config.items[0] for the array index; both contain [0] in the header but refer to different JSON structures. Some flatteners further collapse config.items[0] to config.[0] if they strip the intermediate key, creating an exact header collision."
    }

    with open(evidence_path, "x", encoding="utf-8") as f:
        json.dump(evidence, f, indent=2)

    print(f"Export complete: {len(rows)} rows, {len(SCHEMA)} columns, {csv_size} bytes")
    print(f"Checks: {checks_passed}/{checks_total} passed")
    print(f"Source SHA-256: {source_sha[:16]}...{source_sha[-4:]}")
    print(f"Output SHA-256: {csv_sha[:16]}...{csv_sha[-4:]}")
    return evidence


if __name__ == "__main__":
    source = Path(__file__).parent / "path-marker-fixture.json"
    csv_out = Path(__file__).parent / OUTPUT_PATH
    naive_out = Path(__file__).parent / NAIVE_PATH
    evidence_out = Path(__file__).parent / EVIDENCE_PATH
    result = export_explicit(source, csv_out, naive_out, evidence_out)
    print(f"Evidence written to {evidence_out}")
