import      _                                /**/ from 'lodash'
import type * as TY                               from '../types.ts'
import { inspect } from 'util'
import { inspectify } from './stringify.ts'

/**
 * Helper function to create bad outcomes with consistent structure
 * @param err - The error to wrap
 * @param gist - The gist of the error (e.g. 'badPath', 'badInput', 'blankPath')
 * @param preambleMsg - A message to prepend to the error message
 * @param tmi - Additional error extensions to add to the error
 */
export function badOutcome<GT extends string = string>(preambleMsg: string, gist: GT, err?: Error | undefined, tmi?: TY.AnyBag | undefined): TY.BadOutcome<GT> {
  const extError = throwable(preambleMsg, gist, tmi, err)
  const result = { ok: false, gist, err: extError } as TY.BadOutcome<GT>
  if (tmi) {
    // we're going to all this trouble so we may distinguish
    // between given=undefined and "don't include given in this report"
    // it also is more convenient to let it bubble up to the caller,
    // and less likely to be swapped with tmi in the args
    const { given, ...restTMI } = tmi
    if ('given' in tmi)   { result.given = given }
    if (Object.keys(restTMI).length > 0) { result.tmi = restTMI }
  }
  return result
}
export function throwable<GT extends string = string>(preambleMsg: string, gist: GT, tmi?: TY.AnyBag | undefined, err?: Error | undefined): TY.ExtError {
  const extError      = (err ?? (new Error(preambleMsg))) as TY.ExtError
  extError.extensions = { ...tmi, gist }
  if (err) {
    const { errno, code, syscall, path, message:origmsg } = err as any
    extError.message = `${preambleMsg}: ${origmsg}`
    _.merge(extError.extensions, _.omitBy({ errno, code, syscall, path, origmsg }, _.isUndefined))
  }
  Error.captureStackTrace?.(extError, throwable)
  return extError
}

export function Missing(missings: TY.AnyBag, obj: TY.AnyBag, tmi?: TY.AnyBag | undefined, err?: Error | undefined): TY.ExtError {
  return throwable(`Missing values: ${inspectify(missings)}`, 'missing', { obj, ...tmi, missings }, err)
}