/**
 * JSON pretty-printer for Vega-Lite specs (spec §03A; docs/architecture/08 →
 * "compact formatter").
 *
 * Uses `json-stringify-pretty-compact` for Vega's house style — arrays/objects
 * stay on one line until they exceed a width budget, then wrap — which reads far
 * better for VL specs than `JSON.stringify(…, null, 2)`. Pure and portable: no
 * Monaco, no browser. The Monaco formatting adapter
 * (`infrastructure/monaco-format`) is a thin wrapper over this.
 */

import stringify from 'json-stringify-pretty-compact';

/** Width budget before an array/object wraps onto multiple lines. */
const DEFAULT_MAX_LINE = 80;

/** Default indent (spaces) — matches the editor's default tab size (spec §07). */
const DEFAULT_INDENT = 2;

/**
 * Pretty-print a spec *object* (not text) in the same Vega house style as
 * {@link formatJson}. Object-in callers — the lesson source panes, anything that
 * holds a spec as a parsed value — use this so their rendered JSON matches the
 * editor's formatting exactly. Key order is the object's own insertion order,
 * deliberately: where consecutive specs are small edits of one another (a lesson's
 * 80%→100% chain), a stable order is what keeps a line-diff between them legible.
 */
export function formatSpec(
  value: unknown,
  opts: { indent?: number; maxLength?: number } = {},
): string {
  return stringify(value, {
    indent: opts.indent ?? DEFAULT_INDENT,
    maxLength: opts.maxLength ?? DEFAULT_MAX_LINE,
  });
}

/**
 * Reformat `text` as consistently-indented JSON, or return `null` when it is not
 * valid JSON. Returning `null` (rather than throwing) lets callers skip a no-op
 * rather than mangle an in-progress edit, mirroring auto-save's "skip when
 * unparseable" rule (spec §03B). The result is idempotent: formatting
 * already-formatted text returns it unchanged.
 */
export function formatJson(
  text: string,
  opts: { indent?: number; maxLength?: number } = {},
): string | null {
  let parsed: unknown;
  try {
    parsed = JSON.parse(text);
  } catch {
    return null;
  }
  return formatSpec(parsed, opts);
}
