import      _                                /**/ from 'lodash'
import      * as NodeUtil                         from 'node:util'
import      jsonifyImp                            from 'fast-json-stable-stringify'
import      * as CO                               from '../Consts.ts'

interface KeyAndValue  { key: string, value: any }
export interface jsonifyOpts { cycles?: boolean, reverse?: boolean, cmp?: (oa: KeyAndValue, ob: KeyAndValue) => number }
export type CircularReplacer = <RT = any>(kk: string, vv: RT) => RT | string
export type KVAnyPair  = [string | number, any]
export interface stringifyOpts {
  depth?: number, maxlen?: number, maxarr?: number, maxobj?: number, naked?: boolean, ellipsis?: string,
}
//
export function scrub_textish(val: string): string   { return val.replaceAll(CO.CTRL_SP_NOT_TABNL, ' ').trim() }
export function scrub_stringish(val: string): string { return val.replaceAll(CO.CTRL_SP_TAB_NL,    ' ').trim() }

export function reverseJsonifyCmp({ key: ka, value: va }: KeyAndValue, { key: kb, value: vb }: KeyAndValue) {
  if (va?.id && vb?.id) { return (va.id < vb.id) ? 1 : -1 }
  if (ka === kb)        { return (va < vb) ? 1 : -1 }
  return                         (ka < kb) ? 1 : -1
}

// FIXME -- I think I prefer the features this brings: https://www.npmjs.com/package/safe-stable-stringify
//
export function jsonify(val: any, optsIn: jsonifyOpts = {}) {
  const { reverse = false, ...opts } = optsIn
  if (reverse) { opts.cmp = reverseJsonifyCmp }
  // @ts-expect-error WONTFIX: compat types are wrong or something in import
  return jsonifyImp(val, { cycles: true, ...opts })
}

const DEFAULT_INSPECT_OPTS: NodeUtil.InspectOptions = {
  depth:      2,     maxStringLength:  150, sorted:     false,  numericSeparator: true,
  colors:     false, maxArrayLength:   100, showHidden: false,
  compact:    true,  breakLength: Infinity, getters:    false,
}
const QUOTED_RE = /^([`'"])(.*)\1$/
export type InspectifyOpts = stringifyOpts & NodeUtil.InspectOptions & { verbosity?: number | undefined }
export function inspectify(val: any, opts: InspectifyOpts = {}) {
  if ((_.isFinite(val) && Math.abs(val) - 0.533_333_33 < 0.000_000_1)) { opts.numericSeparator = false } // ?? wth node breaks on this number
  const { maxlen = (opts.maxStringLength ?? 150), depth = 2 } = opts
  let inspected = NodeUtil.inspect(val, { ...DEFAULT_INSPECT_OPTS, ...opts, maxStringLength: maxlen, depth: (_.max([depth, 0]) || 2) })
  // choosing not to let empty strings run naked
  if (opts.naked && QUOTED_RE.test(inspected) && inspected.length > 2) {
    inspected = inspected.slice(1, -1)
  }
  const dots = (inspected.length > maxlen) ? (opts.ellipsis ?? '...') : ''
  return inspected.slice(0, maxlen).trim() + dots
}
