/**
 * Utilities for converting arbitrary JavaScript values into human-readable
 * strings, with support for circular references, redaction, and common JS
 * types that `JSON.stringify` handles poorly.
 *
 * Mental model:
 * - A `Formatter<Value, Format>` is a callable `(value: Value) => Format`.
 * - {@link format} is the general-purpose pretty-printer: it handles
 *   primitives, arrays, objects, `BigInt`, `Symbol`, `Date`, `RegExp`,
 *   `Set`, `Map`, class instances, and circular references.
 * - {@link formatJson} is a safe `JSON.stringify` wrapper that silently
 *   drops circular references and applies redaction.
 * - Both functions accept a `space` option for indentation control.
 *
 * Common tasks:
 * - Pretty-print any value for debugging / logging -> {@link format}
 * - Serialize to JSON safely (no circular throws) -> {@link formatJson}
 * - Format a single object property key -> {@link formatPropertyKey}
 * - Format a property path like `["a"]["b"]` -> {@link formatPath}
 * - Format a `Date` to ISO string safely -> {@link formatDate}
 *
 * Gotchas:
 * - {@link format} output is **not** valid JSON; use {@link formatJson} when
 *   you need parseable JSON.
 * - {@link format} calls `toString()` on objects by default; pass
 *   `ignoreToString: true` to disable.
 * - {@link formatJson} silently omits circular references (the key is
 *   dropped from the output).
 * - Values implementing the `Redactable` protocol are automatically
 *   redacted by both {@link format} and {@link formatJson}.
 *
 * **Example** (Pretty-print a value)
 *
 * ```ts
 * import { Formatter } from "effect"
 *
 * const obj = { name: "Alice", scores: [100, 97] }
 * console.log(Formatter.format(obj))
 * // {"name":"Alice","scores":[100,97]}
 *
 * console.log(Formatter.format(obj, { space: 2 }))
 * // {
 * //   "name": "Alice",
 * //   "scores": [
 * //     100,
 * //     97
 * //   ]
 * // }
 * ```
 *
 * See also: {@link Formatter}, {@link format}, {@link formatJson}
 *
 * @since 4.0.0
 */
import * as Predicate from "./Predicate.js";
import { getRedacted, redact, symbolRedactable } from "./Redactable.js";
/**
 * Converts any JavaScript value into a human-readable string.
 *
 * When to use:
 * - Pretty-printing values for debugging, logging, or error messages.
 * - You need to handle `BigInt`, `Symbol`, `Set`, `Map`, `Date`, `RegExp`,
 *   or class instances that `JSON.stringify` cannot represent.
 * - You want circular references shown as `"[Circular]"` instead of
 *   throwing.
 *
 * Behavior:
 * - Does not mutate input.
 * - Output is **not** valid JSON; use {@link formatJson} when you need
 *   parseable JSON.
 * - Primitives: stringified naturally (`null`, `undefined`, `123`, `true`).
 *   Strings are JSON-quoted.
 * - Objects with a custom `toString` (not `Object.prototype.toString`):
 *   `toString()` is called unless `ignoreToString` is `true`.
 * - Errors with a `cause`: formatted as `"<message> (cause: <cause>)"`.
 * - Iterables (`Set`, `Map`, etc.): formatted as
 *   `ClassName([...elements])`.
 * - Class instances: wrapped as `ClassName({...})`.
 * - `Redactable` values are automatically redacted.
 * - Arrays/objects with 0–1 entries are inline; larger ones are
 *   pretty-printed when `space` is set.
 * - Circular references are replaced with `"[Circular]"`.
 *
 * Options:
 * - `space` — indentation unit (number of spaces, or a string like
 *   `"\t"`). Defaults to `0` (compact).
 * - `ignoreToString` — skip calling `toString()`. Defaults to `false`.
 *
 * **Example** (Compact output)
 *
 * ```ts
 * import { Formatter } from "effect"
 *
 * console.log(Formatter.format({ a: 1, b: [2, 3] }))
 * // {"a":1,"b":[2,3]}
 * ```
 *
 * **Example** (Pretty-printed output)
 *
 * ```ts
 * import { Formatter } from "effect"
 *
 * console.log(Formatter.format({ a: 1, b: [2, 3] }, { space: 2 }))
 * // {
 * //   "a": 1,
 * //   "b": [
 * //     2,
 * //     3
 * //   ]
 * // }
 * ```
 *
 * **Example** (Circular reference handling)
 *
 * ```ts
 * import { Formatter } from "effect"
 *
 * const obj: any = { name: "loop" }
 * obj.self = obj
 * console.log(Formatter.format(obj))
 * // {"name":"loop","self":[Circular]}
 * ```
 *
 * See also: {@link formatJson}, {@link Formatter}
 *
 * @since 4.0.0
 */
export function format(input, options) {
  const space = options?.space ?? 0;
  const seen = new WeakSet();
  const gap = !space ? "" : typeof space === "number" ? " ".repeat(space) : space;
  const ind = d => gap.repeat(d);
  const wrap = (v, body) => {
    const ctor = v?.constructor;
    return ctor && ctor !== Object.prototype.constructor && ctor.name ? `${ctor.name}(${body})` : body;
  };
  const ownKeys = o => {
    try {
      return Reflect.ownKeys(o);
    } catch {
      return ["[ownKeys threw]"];
    }
  };
  function recur(v, d = 0) {
    if (Array.isArray(v)) {
      if (seen.has(v)) return CIRCULAR;
      seen.add(v);
      if (!gap || v.length <= 1) return `[${v.map(x => recur(x, d)).join(",")}]`;
      const inner = v.map(x => recur(x, d + 1)).join(",\n" + ind(d + 1));
      return `[\n${ind(d + 1)}${inner}\n${ind(d)}]`;
    }
    if (v instanceof Date) return formatDate(v);
    if (!options?.ignoreToString && Predicate.hasProperty(v, "toString") && typeof v["toString"] === "function" && v["toString"] !== Object.prototype.toString && v["toString"] !== Array.prototype.toString) {
      const s = safeToString(v);
      if (v instanceof Error && v.cause) {
        return `${s} (cause: ${recur(v.cause, d)})`;
      }
      return s;
    }
    if (typeof v === "string") return JSON.stringify(v);
    if (typeof v === "number" || v == null || typeof v === "boolean" || typeof v === "symbol") return String(v);
    if (typeof v === "bigint") return String(v) + "n";
    if (typeof v === "object" || typeof v === "function") {
      if (seen.has(v)) return CIRCULAR;
      seen.add(v);
      if (symbolRedactable in v) return format(getRedacted(v));
      if (Symbol.iterator in v) {
        return `${v.constructor.name}(${recur(Array.from(v), d)})`;
      }
      const keys = ownKeys(v);
      if (!gap || keys.length <= 1) {
        const body = `{${keys.map(k => `${formatPropertyKey(k)}:${recur(v[k], d)}`).join(",")}}`;
        return wrap(v, body);
      }
      const body = `{\n${keys.map(k => `${ind(d + 1)}${formatPropertyKey(k)}: ${recur(v[k], d + 1)}`).join(",\n")}\n${ind(d)}}`;
      return wrap(v, body);
    }
    return String(v);
  }
  return recur(input, 0);
}
const CIRCULAR = "[Circular]";
/**
 * Formats a single property key for display.
 *
 * When to use:
 * - You are building a custom formatter that needs to render object keys.
 *
 * Behavior:
 * - String keys are JSON-quoted (e.g. `"foo"`).
 * - Symbol and number keys are converted with `String()`.
 * - Pure function; does not mutate input.
 *
 * **Example** (Format property keys)
 *
 * ```ts
 * import { Formatter } from "effect"
 *
 * console.log(Formatter.formatPropertyKey("name"))
 * // "name"
 *
 * console.log(Formatter.formatPropertyKey(Symbol.for("id")))
 * // Symbol(id)
 * ```
 *
 * See also: {@link formatPath}, {@link format}
 *
 * @internal
 */
export function formatPropertyKey(name) {
  return typeof name === "string" ? JSON.stringify(name) : String(name);
}
/**
 * Formats an array of property keys as a bracket-notation path string.
 *
 * When to use:
 * - You need to display a path through a nested object (e.g. in error
 *   messages or schema validation output).
 *
 * Behavior:
 * - Each key is wrapped in brackets and formatted via
 *   {@link formatPropertyKey}.
 * - Returns an empty string for an empty path.
 * - Pure function; does not mutate input.
 *
 * **Example** (Render a property path)
 *
 * ```ts
 * import { Formatter } from "effect"
 *
 * console.log(Formatter.formatPath(["users", 0, "name"]))
 * // ["users"][0]["name"]
 * ```
 *
 * See also: {@link formatPropertyKey}, {@link format}
 *
 * @internal
 */
export function formatPath(path) {
  return path.map(key => `[${formatPropertyKey(key)}]`).join("");
}
/**
 * Formats a `Date` as an ISO 8601 string, returning `"Invalid Date"` for
 * invalid dates instead of throwing.
 *
 * When to use:
 * - You want a safe `toISOString()` that never throws.
 *
 * Behavior:
 * - Returns `date.toISOString()` on success.
 * - Returns `"Invalid Date"` if `toISOString()` throws (e.g. for
 *   `new Date(NaN)`).
 * - Pure function; does not mutate input.
 *
 * **Example** (Safe date formatting)
 *
 * ```ts
 * import { Formatter } from "effect"
 *
 * console.log(Formatter.formatDate(new Date("2024-01-15T10:30:00Z")))
 * // 2024-01-15T10:30:00.000Z
 *
 * console.log(Formatter.formatDate(new Date("invalid")))
 * // Invalid Date
 * ```
 *
 * See also: {@link format}
 *
 * @internal
 */
export function formatDate(date) {
  try {
    return date.toISOString();
  } catch {
    return "Invalid Date";
  }
}
function safeToString(input) {
  try {
    const s = input.toString();
    return typeof s === "string" ? s : String(s);
  } catch {
    return "[toString threw]";
  }
}
/**
 * Safely stringifies a value to JSON, silently dropping circular references.
 *
 * When to use:
 * - You need valid JSON output (unlike {@link format}).
 * - The input may contain circular references and you want them silently
 *   omitted rather than throwing a `TypeError`.
 *
 * Behavior:
 * - Does not mutate input.
 * - Uses `JSON.stringify` internally with a replacer that tracks the
 *   current object ancestry.
 * - Circular references are replaced with `undefined` (omitted from
 *   output).
 * - `Redactable` values are automatically redacted before serialization.
 * - Types not supported by JSON (`BigInt`, `Symbol`, `undefined`,
 *   functions) follow standard `JSON.stringify` behavior (omitted or
 *   `null` in arrays).
 *
 * Options:
 * - `space` — indentation unit (number of spaces, or a string like
 *   `"\t"`). Defaults to `0` (compact).
 *
 * **Example** (Compact JSON)
 *
 * ```ts
 * import { Formatter } from "effect"
 *
 * console.log(Formatter.formatJson({ name: "Alice", age: 30 }))
 * // {"name":"Alice","age":30}
 * ```
 *
 * **Example** (Circular reference handling)
 *
 * ```ts
 * import { Formatter } from "effect"
 *
 * const obj: any = { name: "test" }
 * obj.self = obj
 * console.log(Formatter.formatJson(obj))
 * // {"name":"test"}
 * ```
 *
 * **Example** (Pretty-printed JSON)
 *
 * ```ts
 * import { Formatter } from "effect"
 *
 * console.log(Formatter.formatJson({ name: "Alice", age: 30 }, { space: 2 }))
 * // {
 * //   "name": "Alice",
 * //   "age": 30
 * // }
 * ```
 *
 * See also: {@link format}, {@link Formatter}
 *
 * @since 4.0.0
 */
export function formatJson(input, options) {
  const ancestors = [];
  return JSON.stringify(input, function (_key, value) {
    const redacted = redact(value);
    if (typeof redacted !== "object" || redacted === null) {
      return redacted;
    }
    while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
      ancestors.pop();
    }
    if (ancestors.includes(redacted)) {
      return undefined; // circular reference
    }
    ancestors.push(redacted);
    return redacted;
  }, options?.space);
}
//# sourceMappingURL=Formatter.js.map