/* eslint-disable no-console */

/* eslint-env browser */

/**
 * This is the web browser implementation of `debug()`.
 */
import humanize from 'ms'
import setup from './common.js'

const storage = localstorage()

/**
 * Colors.
 */
const colors = [
  '#0000CC',
  '#0000FF',
  '#0033CC',
  '#0033FF',
  '#0066CC',
  '#0066FF',
  '#0099CC',
  '#0099FF',
  '#00CC00',
  '#00CC33',
  '#00CC66',
  '#00CC99',
  '#00CCCC',
  '#00CCFF',
  '#3300CC',
  '#3300FF',
  '#3333CC',
  '#3333FF',
  '#3366CC',
  '#3366FF',
  '#3399CC',
  '#3399FF',
  '#33CC00',
  '#33CC33',
  '#33CC66',
  '#33CC99',
  '#33CCCC',
  '#33CCFF',
  '#6600CC',
  '#6600FF',
  '#6633CC',
  '#6633FF',
  '#66CC00',
  '#66CC33',
  '#9900CC',
  '#9900FF',
  '#9933CC',
  '#9933FF',
  '#99CC00',
  '#99CC33',
  '#CC0000',
  '#CC0033',
  '#CC0066',
  '#CC0099',
  '#CC00CC',
  '#CC00FF',
  '#CC3300',
  '#CC3333',
  '#CC3366',
  '#CC3399',
  '#CC33CC',
  '#CC33FF',
  '#CC6600',
  '#CC6633',
  '#CC9900',
  '#CC9933',
  '#CCCC00',
  '#CCCC33',
  '#FF0000',
  '#FF0033',
  '#FF0066',
  '#FF0099',
  '#FF00CC',
  '#FF00FF',
  '#FF3300',
  '#FF3333',
  '#FF3366',
  '#FF3399',
  '#FF33CC',
  '#FF33FF',
  '#FF6600',
  '#FF6633',
  '#FF9900',
  '#FF9933',
  '#FFCC00',
  '#FFCC33'
]

/**
 * Currently only WebKit-based Web Inspectors, Firefox >= v31,
 * and the Firebug extension (any Firefox version) are known
 * to support "%c" CSS customizations.
 *
 * TODO: add a `localStorage` variable to explicitly enable/disable colors
 */

// eslint-disable-next-line complexity
function useColors (): boolean {
  // NB: In an Electron preload script, document will be defined but not fully
  // initialized. Since we know we're in Chrome, we'll just detect this case
  // explicitly
  // @ts-expect-error window.process.type and window.process.__nwjs are not in the types
  if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) {
    return true
  }

  // Internet Explorer and Edge do not support colors.
  if (typeof navigator !== 'undefined' && (navigator.userAgent?.toLowerCase().match(/(edge|trident)\/(\d+)/) != null)) {
    return false
  }

  // Is webkit? http://stackoverflow.com/a/16459606/376773
  // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
  // @ts-expect-error document.documentElement.style.WebkitAppearance is not in the types
  return (typeof document !== 'undefined' && document.documentElement?.style?.WebkitAppearance) ||
    // Is firebug? http://stackoverflow.com/a/398120/376773
    // @ts-expect-error window.console.firebug and window.console.exception are not in the types
    (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) ||
    // Is firefox >= v31?
    // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
    (typeof navigator !== 'undefined' && (navigator.userAgent?.toLowerCase().match(/firefox\/(\d+)/) != null) && parseInt(RegExp.$1, 10) >= 31) ||
    // Double check webkit in userAgent just in case we are in a worker
    (typeof navigator !== 'undefined' && navigator.userAgent?.toLowerCase().match(/applewebkit\/(\d+)/))
}

/**
 * Colorize log arguments if enabled.
 */
function formatArgs (this: any, args: any[]): void {
  args[0] = (this.useColors ? '%c' : '') +
    this.namespace +
    (this.useColors ? ' %c' : ' ') +
    args[0] +
    (this.useColors ? '%c ' : ' ') +
    '+' + humanize(this.diff)

  if (!this.useColors) {
    return
  }

  const c = 'color: ' + this.color
  args.splice(1, 0, c, 'color: inherit')

  // The final "%c" is somewhat tricky, because there could be other
  // arguments passed either before or after the %c, so we need to
  // figure out the correct index to insert the CSS into
  let index = 0
  let lastC = 0
  args[0].replace(/%[a-zA-Z%]/g, (match: string) => {
    if (match === '%%') {
      return
    }
    index++
    if (match === '%c') {
      // We only are interested in the *last* %c
      // (the user may have provided their own)
      lastC = index
    }
  })

  args.splice(lastC, 0, c)
}

/**
 * Invokes `console.debug()` when available.
 * No-op when `console.debug` is not a "function".
 * If `console.debug` is not available, falls back
 * to `console.log`.
 */
const log = console.debug ?? console.log ?? (() => { })

/**
 * Save `namespaces`.
 *
 * @param {string} namespaces
 */
function save (namespaces: string): void {
  try {
    if (namespaces) {
      storage?.setItem('debug', namespaces)
    } else {
      storage?.removeItem('debug')
    }
  } catch (error) {
    // Swallow
    // XXX (@Qix-) should we be logging these?
  }
}

/**
 * Load `namespaces`.
 *
 * @returns {string} returns the previously persisted debug modes
 */
function load (): string | null | undefined {
  let r
  try {
    r = storage?.getItem('debug')
  } catch (error) {
    // Swallow
    // XXX (@Qix-) should we be logging these?
  }

  // If debug isn't set in LS, and we're in Electron, try to load $DEBUG
  if (!r && typeof globalThis.process !== 'undefined' && 'env' in globalThis.process) {
    r = globalThis.process.env.DEBUG
  }

  return r
}

/**
 * Localstorage attempts to return the localstorage.
 *
 * This is necessary because safari throws
 * when a user disables cookies/localstorage
 * and you attempt to access it.
 */
function localstorage (): Storage | undefined {
  try {
    // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context
    // The Browser also has localStorage in the global context.
    return localStorage
  } catch (error) {
    // Swallow
    // XXX (@Qix-) should we be logging these?
  }
}

function setupFormatters (formatters: any): void {
  /**
   * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
   */
  formatters.j = function (v: any) {
    try {
      return JSON.stringify(v)
    } catch (error: any) {
      return '[UnexpectedJSONParseError]: ' + error.message
    }
  }
}

export default setup({ formatArgs, save, load, useColors, setupFormatters, colors, storage, log })
