// https://github.com/nodejs/node/blob/a1b27b25bb01aadd3fd2714e4b136db11b7eb85a/lib/test/reporter/spec.js
'use strict'

const {
  ArrayPrototypeJoin,
  ArrayPrototypePop,
  ArrayPrototypeShift,
  ArrayPrototypeUnshift,
  hardenRegExp,
  RegExpPrototypeSymbolSplit,
  SafeMap,
  StringPrototypeRepeat
} = require('#internal/per_context/primordials')
const assert = require('assert')
const Transform = require('stream').Transform
const { inspectWithNoCustomRetry } = require('#internal/errors')
const { green, blue, red, white, gray } = require('#internal/util/colors')

const inspectOptions = { __proto__: null, colors: true, breakLength: Infinity }

const colors = {
  __proto__: null,
  'test:fail': red,
  'test:pass': green,
  'test:diagnostic': blue
}
const symbols = {
  __proto__: null,
  'test:fail': '\u2716 ',
  'test:pass': '\u2714 ',
  'test:diagnostic': '\u2139 ',
  'arrow:right': '\u25B6 '
}
class SpecReporter extends Transform {
  #stack = []
  #reported = []
  #indentMemo = new SafeMap()

  constructor () {
    super({ writableObjectMode: true })
  }

  #indent (nesting) {
    let value = this.#indentMemo.get(nesting)
    if (value === undefined) {
      value = StringPrototypeRepeat('  ', nesting)
      this.#indentMemo.set(nesting, value)
    }

    return value
  }

  #formatError (error, indent) {
    if (!error) return ''
    const err = error.code === 'ERR_TEST_FAILURE' ? error.cause : error
    const message = ArrayPrototypeJoin(
      RegExpPrototypeSymbolSplit(
        hardenRegExp(/\r?\n/),
        inspectWithNoCustomRetry(err, inspectOptions)
      ), `\n${indent}  `)
    return `\n${indent}  ${message}\n`
  }

  #handleEvent ({ type, data }) {
    const color = colors[type] ?? white
    const symbol = symbols[type] ?? ' '

    switch (type) {
      case 'test:fail':
      case 'test:pass': {
        const subtest = ArrayPrototypeShift(this.#stack) // This is the matching `test:start` event
        if (subtest) {
          assert(subtest.type === 'test:start')
          assert(subtest.data.nesting === data.nesting)
          assert(subtest.data.name === data.name)
        }
        let prefix = ''
        while (this.#stack.length) {
          // Report all the parent `test:start` events
          const parent = ArrayPrototypePop(this.#stack)
          assert(parent.type === 'test:start')
          const msg = parent.data
          ArrayPrototypeUnshift(this.#reported, msg)
          prefix += `${this.#indent(msg.nesting)}${symbols['arrow:right']}${msg.name}\n`
        }
        const indent = this.#indent(data.nesting)
        const duration_ms = data.details?.duration_ms ? ` ${gray}(${data.details.duration_ms}ms)${white}` : '' // eslint-disable-line camelcase
        const title = `${data.name}${duration_ms}` // eslint-disable-line camelcase
        if (this.#reported[0] && this.#reported[0].nesting === data.nesting && this.#reported[0].name === data.name) {
          // If this test has had children - it was already reporter, so slightly modify the output
          ArrayPrototypeShift(this.#reported)
          return `${prefix}${indent}${color}${symbols['arrow:right']}${white}${title}\n\n`
        }
        const error = this.#formatError(data.details?.error, indent)
        return `${prefix}${indent}${color}${symbol}${title}${error}${white}\n`
      }
      case 'test:start':
        ArrayPrototypeUnshift(this.#stack, { __proto__: null, data, type })
        break
      case 'test:diagnostic':
        return `${color}${this.#indent(data.nesting)}${symbol}${data.message}${white}\n`
    }
  }

  _transform ({ type, data }, encoding, callback) {
    callback(null, this.#handleEvent({ type, data }))
  }
}

module.exports = SpecReporter
