export interface PreviewOptions {
  maxDepth?: number;
  maxArrayItems?: number;
  maxObjectKeys?: number;
  maxStringLength?: number;
}

const DEFAULT_PREVIEW_OPTIONS: Required<PreviewOptions> = {
  maxDepth: 4,
  maxArrayItems: 6,
  maxObjectKeys: 32,
  maxStringLength: 1000,
};

function optionsWithDefaults(options: PreviewOptions = {}): Required<PreviewOptions> {
  return {
    maxDepth: options.maxDepth ?? DEFAULT_PREVIEW_OPTIONS.maxDepth,
    maxArrayItems: options.maxArrayItems ?? DEFAULT_PREVIEW_OPTIONS.maxArrayItems,
    maxObjectKeys: options.maxObjectKeys ?? DEFAULT_PREVIEW_OPTIONS.maxObjectKeys,
    maxStringLength: options.maxStringLength ?? DEFAULT_PREVIEW_OPTIONS.maxStringLength,
  };
}

export function jsonByteLength(value: unknown): number {
  try {
    return Buffer.byteLength(JSON.stringify(value), "utf8");
  } catch {
    return Buffer.byteLength(String(value), "utf8");
  }
}

export function previewJson(value: unknown, options: PreviewOptions = {}, depth = 0): unknown {
  const limits = optionsWithDefaults(options);
  if (value === null || typeof value === "number" || typeof value === "boolean") return value;
  if (typeof value === "string") {
    if (value.length <= limits.maxStringLength) return value;
    return {
      _previewType: "string",
      length: value.length,
      text: value.slice(0, limits.maxStringLength),
      truncated: true,
    };
  }
  if (typeof value !== "object") return String(value);
  if (depth >= limits.maxDepth) {
    return {
      _previewType: Array.isArray(value) ? "array" : "object",
      truncated: true,
      ...(Array.isArray(value) ? { length: value.length } : { keys: Object.keys(value as Record<string, unknown>).length }),
    };
  }
  if (Array.isArray(value)) {
    return {
      _previewType: "array",
      length: value.length,
      items: value.slice(0, limits.maxArrayItems).map((item) => previewJson(item, limits, depth + 1)),
      truncatedItems: Math.max(0, value.length - limits.maxArrayItems),
    };
  }
  const record = value as Record<string, unknown>;
  const entries = Object.entries(record);
  const preview: Record<string, unknown> = {};
  for (const [key, item] of entries.slice(0, limits.maxObjectKeys)) {
    preview[key] = previewJson(item, limits, depth + 1);
  }
  if (entries.length > limits.maxObjectKeys) {
    preview._preview = {
      truncatedKeys: entries.length - limits.maxObjectKeys,
      totalKeys: entries.length,
    };
  }
  return preview;
}

export function boundedJsonDetail(value: unknown, maxBytes: number, options: PreviewOptions = {}): unknown {
  const originalBytes = jsonByteLength(value);
  if (originalBytes <= maxBytes) return value;
  return {
    _truncated: true,
    originalBytes,
    maxBytes,
    preview: previewJson(value, options),
  };
}
