'use strict'
const { inspect } = require('util')

// adapted from node's internal/errors
// https://github.com/nodejs/node/blob/c8a04049/lib/internal/errors.js

// close copy of node's internal SystemError class.
class SystemError {
  constructor (code, prefix, context) {
    // XXX context.code is undefined in all constructors used in cp/polyfill
    // that may be a bug copied from node, maybe the constructor should use
    // `code` not `errno`?  nodejs/node#41104
    let message = `${prefix}: ${context.syscall} returned ` +
                  `${context.code} (${context.message})`

    if (context.path !== undefined) {
      message += ` ${context.path}`
    }
    if (context.dest !== undefined) {
      message += ` => ${context.dest}`
    }

    this.code = code
    Object.defineProperties(this, {
      name: {
        value: 'SystemError',
        enumerable: false,
        writable: true,
        configurable: true,
      },
      message: {
        value: message,
        enumerable: false,
        writable: true,
        configurable: true,
      },
      info: {
        value: context,
        enumerable: true,
        configurable: true,
        writable: false,
      },
      errno: {
        get () {
          return context.errno
        },
        set (value) {
          context.errno = value
        },
        enumerable: true,
        configurable: true,
      },
      syscall: {
        get () {
          return context.syscall
        },
        set (value) {
          context.syscall = value
        },
        enumerable: true,
        configurable: true,
      },
    })

    if (context.path !== undefined) {
      Object.defineProperty(this, 'path', {
        get () {
          return context.path
        },
        set (value) {
          context.path = value
        },
        enumerable: true,
        configurable: true,
      })
    }

    if (context.dest !== undefined) {
      Object.defineProperty(this, 'dest', {
        get () {
          return context.dest
        },
        set (value) {
          context.dest = value
        },
        enumerable: true,
        configurable: true,
      })
    }
  }

  toString () {
    return `${this.name} [${this.code}]: ${this.message}`
  }

  [Symbol.for('nodejs.util.inspect.custom')] (_recurseTimes, ctx) {
    return inspect(this, {
      ...ctx,
      getters: true,
      customInspect: false,
    })
  }
}

function E (code, message) {
  module.exports[code] = class NodeError extends SystemError {
    constructor (ctx) {
      super(code, message, ctx)
    }
  }
}

E('ERR_FS_CP_DIR_TO_NON_DIR', 'Cannot overwrite directory with non-directory')
E('ERR_FS_CP_EEXIST', 'Target already exists')
E('ERR_FS_CP_EINVAL', 'Invalid src or dest')
E('ERR_FS_CP_FIFO_PIPE', 'Cannot copy a FIFO pipe')
E('ERR_FS_CP_NON_DIR_TO_DIR', 'Cannot overwrite non-directory with directory')
E('ERR_FS_CP_SOCKET', 'Cannot copy a socket file')
E('ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY', 'Cannot overwrite symlink in subdirectory of self')
E('ERR_FS_CP_UNKNOWN', 'Cannot copy an unknown file type')
E('ERR_FS_EISDIR', 'Path is a directory')

module.exports.ERR_INVALID_ARG_TYPE = class ERR_INVALID_ARG_TYPE extends Error {
  constructor (name, expected, actual) {
    super()
    this.code = 'ERR_INVALID_ARG_TYPE'
    this.message = `The ${name} argument must be ${expected}. Received ${typeof actual}`
  }
}
