/**
 * JSON -> CSV converter behaviour matrix.
 *
 * Runs the same 15 JSON inputs (cases.json) through four converters and records
 * the exact CSV each one produces:
 *
 *   1. FormatArc          — lib/tooling.ts runToolConversion("json-to-csv", ...)
 *   2. json-2-csv (npm)   — json2csv(), default options
 *   3. Miller (mlr)       — mlr --ijson --ocsv cat
 *   4. pandas             — pandas.json_normalize(data).to_csv(index=False)
 *
 * Run:
 *   npm install                       # inside this directory (json-2-csv)
 *   npx tsx scripts/benchmarks/json-to-csv-guide/measure.ts
 *
 * Miller and pandas are optional: if the binary is missing the row records
 * "(not installed)" instead of failing the run. The pandas interpreter can be
 * overridden with PANDAS_PYTHON=/path/to/python.
 */
import { execFileSync } from "node:child_process";
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { runToolConversion } from "@/lib/tooling";

// process.argv[1] is this script's path under both tsx (cjs) and plain node (esm).
const here = path.dirname(path.resolve(process.argv[1]));

type Case = { id: string; label: string; input: string };
const cases: Case[] = JSON.parse(readFileSync(path.join(here, "cases.json"), "utf8"));

const scratch = mkdtempSync(path.join(tmpdir(), "json-to-csv-bench-"));

function formatarc(input: string): string {
  const result = runToolConversion("json-to-csv", input, "en");
  return result.error ? `ERROR: ${result.error}` : result.output;
}

async function jsonTwoCsv(input: string): Promise<string> {
  const { json2csv } = await import("json-2-csv");
  try {
    return json2csv(JSON.parse(input));
  } catch (error) {
    return `ERROR: ${(error as Error).message}`;
  }
}

function runBinary(
  bin: string,
  args: string[],
  stdin: string,
  errorLine: "first" | "last" = "first",
): string {
  try {
    return execFileSync(bin, args, { input: stdin, encoding: "utf8" }).replace(/\n$/, "");
  } catch (error) {
    const err = error as NodeJS.ErrnoException & { stderr?: string };
    if (err.code === "ENOENT") return "(not installed)";
    // mlr prints the useful diagnostic first and a generic "exiting" line last;
    // python prints the traceback first and the exception message last.
    const lines = (err.stderr ?? err.message ?? "")
      .toString()
      .trim()
      .split("\n")
      .filter((line) => line.trim());
    return `ERROR: ${errorLine === "last" ? lines[lines.length - 1] : lines[0]}`;
  }
}

function miller(input: string): string {
  return runBinary("mlr", ["--ijson", "--ocsv", "cat"], input);
}

const PANDAS_PYTHON = process.env.PANDAS_PYTHON ?? "python3";
const pandasScript = path.join(scratch, "pandas_normalize.py");
writeFileSync(
  pandasScript,
  [
    "import sys, json, pandas as pd",
    "data = json.load(sys.stdin)",
    "if isinstance(data, dict):",
    "    data = [data]",
    "print(pd.json_normalize(data).to_csv(index=False), end='')",
  ].join("\n"),
  "utf8",
);

function pandas(input: string): string {
  return runBinary(PANDAS_PYTHON, [pandasScript], input, "last");
}

function pandasVersion(): string {
  const out = runBinary(PANDAS_PYTHON, ["-c", "import pandas;print(pandas.__version__)"], "");
  return out.startsWith("ERROR") || out === "(not installed)" ? out : `pandas ${out}`;
}

function millerVersion(): string {
  const out = runBinary("mlr", ["--version"], "");
  return out.startsWith("ERROR") || out === "(not installed)" ? out : out;
}

function pkgVersion(name: string): string {
  try {
    return `${name} ${require(`${name}/package.json`).version}`;
  } catch {
    return `${name} (unknown)`;
  }
}

async function main() {
  const results = [];
  for (const testCase of cases) {
    results.push({
      id: testCase.id,
      label: testCase.label,
      input: testCase.input,
      outputs: {
        FormatArc: formatarc(testCase.input),
        "json-2-csv": await jsonTwoCsv(testCase.input),
        Miller: miller(testCase.input),
        "pandas json_normalize": pandas(testCase.input),
      },
    });
  }

  const payload = {
    generatedAt: new Date().toISOString(),
    environment: {
      node: process.version,
      platform: `${process.platform}/${process.arch}`,
      converters: {
        FormatArc: `lib/tooling.ts runToolConversion('json-to-csv') — ${pkgVersion("papaparse")}`,
        "json-2-csv": pkgVersion("json-2-csv"),
        Miller: millerVersion(),
        pandas: pandasVersion(),
      },
    },
    results,
  };

  const outPath = path.join(here, "results.json");
  writeFileSync(outPath, JSON.stringify(payload, null, 2) + "\n", "utf8");
  console.log(`wrote ${outPath} (${results.length} cases)`);
  for (const row of results) {
    console.log(`\n=== ${row.id} ${row.label} ===`);
    for (const [name, out] of Object.entries(row.outputs)) {
      console.log(`--- ${name}\n${out}`);
    }
  }
}

void main();
