import argparse, csv, json
from pathlib import Path

def reject_constant(token):
    raise ValueError("Non-JSON constant: " + token)

def unique_keys(pairs):
    result = {}
    for key, value in pairs:
        if key in result:
            raise ValueError("Duplicate key: " + key)
        result[key] = value
    return result

def decode(raw):
    return json.loads(raw, parse_constant=reject_constant,
                      object_pairs_hook=unique_keys)

def export(source, target, envelope=False):
    source, target = Path(source), Path(target)
    if source.resolve() == target.resolve():
        raise ValueError("Source and destination must differ")
    if source.stat().st_size > 1_000_000:
        raise ValueError("Demo byte cap exceeded")
    data = decode(source.read_text(encoding="utf-8"))
    if envelope:
        if not isinstance(data, str):
            raise ValueError("Envelope mode requires a top-level string")
        data = decode(data)  # one declared envelope, never a loop
    if not isinstance(data, list):
        raise ValueError("Expected a records array after declared decoding")
    for row in data:
        if not isinstance(row, dict) or set(row) != {"id", "text"}:
            raise ValueError("Expected exact id/text objects")
        if not all(isinstance(row[k], str) for k in ("id", "text")):
            raise ValueError("id and text must be strings")
    with target.open("x", encoding="utf-8", newline="") as f:
        writer = csv.writer(f, lineterminator="\n")
        writer.writerow(["id", "text"])
        writer.writerows((row["id"], row["text"]) for row in data)

if __name__ == "__main__":
    p = argparse.ArgumentParser()
    p.add_argument("source")
    p.add_argument("target")
    p.add_argument("--decode-envelope", action="store_true")
    args = p.parse_args()
    try:
        export(args.source, args.target, args.decode_envelope)
    except (ValueError, OSError, RecursionError) as e:
        p.exit(1, str(e) + "\n")
