import { format, inspect } from 'node:util'
import pino from 'pino'

/**
 * customize how pino handles extra arguments and have it work in a similar way to node Console log
 * should be passed to the logMethod hook of pino
 *
 * See {@link https://getpino.io/#/docs/api?id=logmethod} for more details about pino hooks
 *
 * @example we handle passing a string as first argument with object as a second argument
 * ```ts
 * // will serialize as { "message": "foo", "lorem": "ipsum" }
 * logger.info('foo', { lorem: 'ipsum' })
 * ```
 *
 * @example we handle passing extra arguments
 * ```ts
 * // will serialize as { "message": "foo { lorem: 'ipsum' } { foo: 'bar' }"}
 * logger.info('foo', { lorem: 'ipsum' }, { foo: 'bar' })
 * // will serialize as { "message": "{ lorem: 'ipsum' }, 'foo', { foo: 'bar' }"}
 * logger.info({ lorem: 'ipsum' }, 'foo', { foo: 'bar' })
 * ```
 *
 * @example we use standard pino for one argument
 * ```ts
 * // will serialize as { "message": "foo" }
 * logger.info('foo')
 * // will serialize as { "lorem": "ipsum" }
 * logger.info({ lorem: 'ipsum'})
 * ```
 *
 * @example we use standard pino for first argument object and second argument string
 * ```ts
 * // will serialize as { "message": "foo", "lorem": "ipsum" }
 * logger.info({ lorem: 'ipsum', 'foo' })
 * ```
 */
export function consoleLikeLogHook(instance: pino.Logger, args: Array<any>, method: pino.LogFn): void {
	// filter out the undefined from args
	// this happens when logger.error is used
	args = args.filter((arg) => arg !== undefined)

	const firstArgIsString = typeof args[0] === 'string'

	// pino has been called like this logger.info('foo', { lorem: 'ipsum' }) instead of logger.info({ lorem: 'ipsum' }, 'foo')
	// so we switch the arguments and call it correctly
	if (args.length === 2 && firstArgIsString) {
		return method.apply(instance, [args[1], args[0]] as never)
	}

	// pino has been called with more arguments than what it normally would use (eg logger.info({ lorem: 'ipsum' }, 'foo', { fizz: 'buzz' }))
	// we serialize the arguments like what the standard console logger would do, format if the first argument is a string and inspect otherwise
	// inspect has an increased depth compared to the default value of 2 to better handle nested objects but be careful that this can be quite slow
	if (args.length > 2) {
		return firstArgIsString
			? method.apply(instance, [format(...args)])
			: method.apply(instance, [inspect(args, { depth: 4 })])
	}

	// pino has been invoked with one argument or with an object as first argument and a string as second, we simply call it without doing anything different
	return method.apply(instance, [args[0], ...args.slice(1)] as never)
}
