'use strict'

const { log } = require('proc-log')
const { format } = require('util')

// helper to emit log messages with a predefined prefix
const withPrefix = (prefix) => log.LEVELS.reduce((acc, level) => {
  acc[level] = (...args) => log[level](prefix, ...args)
  return acc
}, {})

// very basic ansi color generator
const COLORS = {
  wrap: (str, colors) => {
    const codes = colors.filter(c => typeof c === 'number')
    return `\x1b[${codes.join(';')}m${str}\x1b[0m`
  },
  inverse: 7,
  fg: {
    black: 30,
    red: 31,
    green: 32,
    yellow: 33,
    blue: 34,
    magenta: 35,
    cyan: 36,
    white: 37
  },
  bg: {
    black: 40,
    red: 41,
    green: 42,
    yellow: 43,
    blue: 44,
    magenta: 45,
    cyan: 46,
    white: 47
  }
}

class Logger {
  #buffer = []
  #paused = null
  #level = null
  #stream = null

  // ordered from loudest to quietest
  #levels = [{
    id: 'silly',
    display: 'sill',
    style: { inverse: true }
  }, {
    id: 'verbose',
    display: 'verb',
    style: { fg: 'cyan', bg: 'black' }
  }, {
    id: 'info',
    style: { fg: 'green' }
  }, {
    id: 'http',
    style: { fg: 'green', bg: 'black' }
  }, {
    id: 'notice',
    style: { fg: 'cyan', bg: 'black' }
  }, {
    id: 'warn',
    display: 'WARN',
    style: { fg: 'black', bg: 'yellow' }
  }, {
    id: 'error',
    display: 'ERR!',
    style: { fg: 'red', bg: 'black' }
  }]

  constructor (stream) {
    process.on('log', (...args) => this.#onLog(...args))
    this.#levels = new Map(this.#levels.map((level, index) => [level.id, { ...level, index }]))
    this.level = 'info'
    this.stream = stream
    log.pause()
  }

  get stream () {
    return this.#stream
  }

  set stream (stream) {
    this.#stream = stream
  }

  get level () {
    return this.#levels.get(this.#level) ?? null
  }

  set level (level) {
    this.#level = this.#levels.get(level)?.id ?? null
  }

  isVisible (level) {
    return this.level?.index <= this.#levels.get(level)?.index ?? -1
  }

  #onLog (...args) {
    const [level] = args

    if (level === 'pause') {
      this.#paused = true
      return
    }

    if (level === 'resume') {
      this.#paused = false
      this.#buffer.forEach((b) => this.#log(...b))
      this.#buffer.length = 0
      return
    }

    if (this.#paused) {
      this.#buffer.push(args)
      return
    }

    this.#log(...args)
  }

  #color (str, { fg, bg, inverse }) {
    if (!this.#stream?.isTTY) {
      return str
    }

    return COLORS.wrap(str, [
      COLORS.fg[fg],
      COLORS.bg[bg],
      inverse && COLORS.inverse
    ])
  }

  #log (levelId, msgPrefix, ...args) {
    if (!this.isVisible(levelId) || typeof this.#stream?.write !== 'function') {
      return
    }

    const level = this.#levels.get(levelId)

    const prefixParts = [
      this.#color('gyp', { fg: 'white', bg: 'black' }),
      this.#color(level.display ?? level.id, level.style)
    ]
    if (msgPrefix) {
      prefixParts.push(this.#color(msgPrefix, { fg: 'magenta' }))
    }

    const prefix = prefixParts.join(' ').trim() + ' '
    const lines = format(...args).split(/\r?\n/).map(l => prefix + l.trim())

    this.#stream.write(lines.join('\n') + '\n')
  }
}

// used to suppress logs in tests
const NULL_LOGGER = !!process.env.NODE_GYP_NULL_LOGGER

module.exports = {
  logger: new Logger(NULL_LOGGER ? null : process.stderr),
  stdout: NULL_LOGGER ? () => {} : (...args) => console.log(...args),
  withPrefix,
  ...log
}
