/**
 * Permission type used by [`chmod`](https://linux.die.net/man/1/chmod).
 *
 * Octal string where each digit represents a user class: `user`, `group` and
 * `others`.
 * Each digit's is a [bitfield](https://en.wikipedia.org/wiki/Bit_field)
 * representing `read`, `write` and `execute`. Special permissions
 * ([setuid](https://en.wikipedia.org/wiki/Setuid),
 * [setgid](https://en.wikipedia.org/wiki/Setuid),
 * [sticky](https://en.wikipedia.org/wiki/Sticky_bit)) can optionally be specified
 * by prepending another digit.
 *
 * An operator can be prepended:
 *  - `=` (default): unset omitted permissions
 *  - `+`: leave omitted permissions as is
 *  - `-`: unset specified permissions
 *
 * @example
 * ```js
 * console.log(convert.stat('720')) // 'rwx-w----'
 * console.log(convert.stat('7000')) // '--S--S--T'
 * console.log(convert.stat('\\720')) // 'rwx-w----'
 * console.log(convert.stat('0720')) // 'rwx-w----'
 * console.log(convert.stat('0o720')) // 'rwx-w----'
 * console.log(convert.symbolic('+720')) // 'u+rwx,g+w'
 * console.log(convert.symbolic('-720')) // 'u-rwx,g-w'
 * console.log(convert.symbolic('=720')) // 'u=rwx,g=w,o='
 * ```
 */
export type PermissionOctal =
  | `${OctalStart}${OctalDigit}`
  | `${OctalStart}${OctalDigit}${OctalDigit}`
  | `${OctalStart}${OctalDigit}${OctalDigit}${OctalDigit}`
  | `${OctalStart}${OctalDigit}${OctalDigit}${OctalDigit}${OctalDigit}`
type OctalDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7'

type OctalPrefix = '' | '0o' | '0' | '\\'

type OctalOperator = '' | '=' | '+' | '-'

type OctalStart = `${OctalOperator}${OctalPrefix}`

/**
 * Permission type used by Node.js
 * [`fs.chmod()`](https://nodejs.org/api/fs.html#fs_fs_chmod_path_mode_callback).
 *
 * It is the same as `octal` except:
 *  - as a decimal number.
 *  - no operator can be used.
 *  - it can be used as input in [JavaScript](../README.md#usage-javascript) but
 *    not on the [command line](../README.md#usage-cli), where all numbers
 *    should be in [`octal`](#octal) form instead.
 *
 * @example
 * ```js
 * console.log(convert.stat(0)) // '---------'
 * console.log(convert.stat(1)) // '--------x'
 * console.log(convert.stat(3)) // '-------wx'
 * console.log(convert.stat(8)) // '-----x---'
 * console.log(convert.stat(512)) // '--------T'
 * ```
 */
export type PermissionNumber = number

/**
 * Permission type used by [`stat`](https://linux.die.net/man/2/stat) and
 * [`ls`](https://linux.die.net/man/1/ls).
 *
 * It is a string where each character represents either the permission
 * (`r`, `w`, `x`) or no permission (`-`). The special permission are indicated
 * with `S`, `s`, `T` and `t` where lowercase implies `x` is also present.
 *
 * Optionally a first character can be specified to indicate the file type
 * (e.g. `d` for directories).
 *
 * @example
 * ```js
 * console.log(convert.octal('--------x')) // '0001'
 * console.log(convert.octal('--x--x--x')) // '0111'
 * console.log(convert.octal('--------T')) // '1000'
 * console.log(convert.octal('--------t')) // '1001'
 * console.log(convert.octal('d--------x')) // '0001'
 * console.log(convert.octal('--x --x --x')) // '0111'
 * console.log(convert.octal('rwx --- ---')) // '0700'
 * console.log(convert.octal('xwr --- ---')) // '0700'
 * ```
 */
export type PermissionStat =
  `${string}${PermissionStatOthersBit}${PermissionStatOthersBit}${PermissionStatOthersBit}`
type PermissionStatOthersBit = '-' | 'r' | 'w' | 'x' | 'X' | 't' | 'T'

/**
 * Permission type used by [`chmod`](https://linux.die.net/man/1/chmod) as a
 * string like `gu+rx`.
 *
 * Starts with the user class (`a` for all, `u` for user, `g` for group, `o` for
 * others) then the operator (`+`, `-` or `=`) and ends with the permissions
 * characters.
 *
 * While `+` leaves the omitted permissions as is, `=` unsets them. For example
 * `o=x` is the same as combining `o+x` and `o-rwt`.
 *
 * Several groups can be specified using a comma-separated list like `g+x,o+r`.
 *
 * User classes can be concatenated like `go+x`.
 *
 * @example
 * ```js
 * console.log(convert.octal('o+wx')) // '+0003'
 * console.log(convert.octal('o=wx')) // '0003'
 * console.log(convert.octal('o-wx')) // '-0003'
 * console.log(convert.octal('go+x')) // '+0011'
 * console.log(convert.octal('g+x,o+x')) // '+0011'
 * console.log(convert.octal('a+x')) // '+0111'
 * console.log(convert.octal('+x')) // '+0111'
 * console.log(convert.octal('a+s')) // '+6000'
 * console.log(convert.octal('o+')) // '+0000'
 * ```
 */
export type PermissionSymbolic =
  | `${PermissionSymbolicSingle}`
  | `${string},${PermissionSymbolicSingle}`
type PermissionSymbolicSingle =
  `${PermissionSymbolicClasses}${PermissionSymbolicOperator}${PermissionSymbolicActions}`

type PermissionSymbolicClass = 'a' | 'u' | 'g' | 'o'

type PermissionSymbolicClasses = '' | `${string}${PermissionSymbolicClass}`

type PermissionSymbolicOperator = '+' | '=' | '-'

type PermissionSymbolicAction = 'x' | 'w' | 'r' | 'X' | 's' | 't'

type PermissionSymbolicActions = '' | `${string}${PermissionSymbolicAction}`

/**
 * Permission type as an object.
 * `undefined` leaves permissions as is while `false` unsets them.
 *
 * @example
 * ```js
 * console.log(convert.symbolic({ others: { read: true, execute: true } }))
 * // 'o+rx'
 *
 * console.log(convert.symbolic({ others: { read: true, execute: false } }))
 * // 'o+r,o-x'
 *
 * console.log(convert.symbolic({ others: { read: true, execute: undefined } }))
 * // 'o+r'
 *
 * console.log(convert.symbolic({ all: { read: true } }))
 * // 'a+r'
 *
 * console.log(convert.symbolic({}))
 * // 'a+'
 *
 * console.log(
 *   convert.symbolic({ special: { setuid: true, setgid: true, sticky: true } }),
 * )
 * // 'ug+s,o+t'
 * ```
 */
export type PermissionObject = Partial<{
  user: PermissionObjectClass
  group: PermissionObjectClass
  others: PermissionObjectClass
  all: PermissionObjectClass
  special: PermissionObjectSpecial
}>
type PermissionObjectClass = Partial<{
  read: PermissionObjectValue
  write: PermissionObjectValue
  execute: PermissionObjectValue
}>

type PermissionObjectSpecial = Partial<{
  setuid: PermissionObjectValue
  setgid: PermissionObjectValue
  sticky: PermissionObjectValue
}>

type PermissionObjectValue = boolean | undefined

/**
 * Permissions of a file for:
 *  - Each user class (user, group, others)
 *  - Each file operation (read, write, execute), including special operations
 *    (setuid, setgid, sticky)
 */
export type Permission =
  | PermissionOctal
  | PermissionNumber
  | PermissionStat
  | PermissionSymbolic
  | PermissionObject

/**
 * Type of permission format.
 */
export type PermissionType = 'octal' | 'number' | 'stat' | 'symbolic' | 'object'

export declare const convert: {
  /**
   * Returns `permission` converted to the octal type.
   *
   * @example
   * ```js
   * convert.octal('a+x') // '+0111'
   *
   * try {
   *   convert.octal('z+x') // Throws an exception (permission syntax is invalid)
   * } catch (error) {
   *   console.log(error.message)
   * }
   * ```
   */
  octal: (permission: Permission) => PermissionOctal

  /**
   * Returns `permission` converted to the number type.
   *
   * @example
   * ```js
   * convert.number('a+x') // 0o111
   *
   * try {
   *   convert.number('z+x') // Throws an exception (permission syntax is invalid)
   * } catch (error) {
   *   console.log(error.message)
   * }
   * ```
   */
  number: (permission: Permission) => PermissionNumber

  /**
   * Returns `permission` converted to the stat type.
   *
   * @example
   * ```js
   * convert.stat('a+x') // '--x--x--x'
   *
   * try {
   *   convert.stat('z+x') // Throws an exception (permission syntax is invalid)
   * } catch (error) {
   *   console.log(error.message)
   * }
   * ```
   */
  stat: (permission: Permission) => PermissionStat

  /**
   * Returns `permission` converted to the symbolic type.
   *
   * @example
   * ```js
   * convert.symbolic('--x--x--x') // 'a=x'
   *
   * try {
   *   convert.symbolic('--o--o--o') // Throws an exception (permission syntax is invalid)
   * } catch (error) {
   *   console.log(error.message)
   * }
   * ```
   */
  symbolic: (permission: Permission) => PermissionSymbolic

  /**
   * Returns `permission` converted to the object type.
   *
   * @example
   * ```js
   * convert.object('a+x')
   * // {
   * //   user: { execute: true },
   * //   group: { execute: true },
   * //   others: { execute: true }
   * // }
   *
   * try {
   *   convert.object('z+x') // Throws an exception (permission syntax is invalid)
   * } catch (error) {
   *   console.log(error.message)
   * }
   * ```
   */
  object: (permission: Permission) => PermissionObject
}

/**
 * Returns the `permission`'s `type` or `'invalid'`.
 *
 * @example
 * ```js
 * console.log(type('1')) // 'octal'
 * console.log(type(1)) // 'number'
 * console.log(type('a+x')) // 'symbolic'
 * console.log(type('a+i')) // 'invalid'
 * ```
 */
export function type(permission: Permission): PermissionType
export function type(permission: unknown): 'invalid'

/**
 * Normalizes a `permission` to its canonical shape.
 * Throws an exception if `permission` is invalid.
 *
 * @example
 * ```js
 * console.log(normalize('1')) // '0001'
 * console.log(normalize('g+x,o+x')) // 'go+x'
 * console.log(normalize('d--- --- ---')) // '---------'
 * console.log(normalize({ user: { read: undefined, write: true } }))
 * // { user: { write: true } }
 *
 * try {
 *   normalize('z+x') // Throws an exception (permission syntax is invalid)
 * } catch (error) {
 *   console.log(error.message)
 * }
 * ```
 */
export function normalize(permission: PermissionOctal): PermissionOctal
export function normalize(permission: PermissionNumber): PermissionNumber
export function normalize(permission: PermissionStat): PermissionStat
export function normalize(permission: PermissionSymbolic): PermissionSymbolic
export function normalize(permission: PermissionObject): PermissionObject
export function normalize(permission: unknown): never

/**
 * Removes all negative permissions.
 *
 * @example
 * ```js
 * console.log(positive('o+x,o-rw')) // 'o+x'
 * console.log(positive('o=x')) // 'o+x'
 * console.log(positive('660')) // '+0660'
 * console.log(invert('660')) // '0117'
 * console.log(invert(positive('660'))) // '-0660'
 * ```
 */
export function positive(permission: PermissionOctal): PermissionOctal
export function positive(permission: PermissionNumber): PermissionNumber
export function positive(permission: PermissionStat): PermissionStat
export function positive(permission: PermissionSymbolic): PermissionSymbolic
export function positive(permission: PermissionObject): PermissionObject
export function positive(permission: unknown): never

/**
 * Tests whether `permission` includes `permissions`.
 *
 * @example
 * ```js
 * console.log(contain('--x--x--x', 'a=x')) // `true`
 * console.log(contain('--x--x--x', 'a+x')) // `true`
 * console.log(contain('--x--x--x', 'a-x')) // `false`
 * console.log(contain('--x--x--x', 'a-w')) // `true`
 * console.log(contain('o+x', 'o+x')) // `true`
 * console.log(contain('o+x', 'o+x,o+x')) // `true`
 * console.log(contain('o+x', 'o=w')) // `false`
 * console.log(contain('o+x,o-w', 'o-w,o+x')) // `true`
 * console.log(contain('o+x,o-w', 'o-w')) // `true`
 * console.log(contain('o+x,o-w', 'o+x', 'o-w')) // `true`
 * ```
 */
export function contain(
  permission: Permission,
  containedPermission: Permission,
  ...alsoContainedPermissions: Permission[]
): boolean

/**
 * Tests whether `permission` equals exactly `permissions`.
 *
 * @example
 * ```js
 * console.log(equal('--x--x--x', 'a=x')) // `true`
 * console.log(equal('--x--x--x', 'a+x')) // `false`
 * console.log(equal('--x--x--x', 'a-x')) // `false`
 * console.log(equal('--x--x--x', 'a-w')) // `false`
 * console.log(equal('o+x', 'o+x')) // `true`
 * console.log(equal('o+x', 'o+x,o+x')) // `true`
 * console.log(equal('o+x', 'o=w')) // `false`
 * console.log(equal('o+x,o-w', 'o-w,o+x')) // `true`
 * console.log(equal('o+x,o-w', 'o-w')) // `false`
 * console.log(equal('o+x,o-w', 'o+x', 'o-w')) // `false`
 * ```
 */
export function equal(
  permission: Permission,
  equaledPermission: Permission,
  ...alsoEqualedPermissions: Permission[]
): boolean

/**
 * Returns the result of setting `permissions` on `permission`.
 * This is useful to avoid error-prone bitwise operations (`|`, `&`, `^`, `~`).
 * This can also be used to remove special permissions using
 * `set(permission, 'a-st')` since some functions like
 * [`umask`](https://linux.die.net/man/2/umask) do not allow them.
 *
 * @example
 * ```js
 * console.log(set('---------', 'a+x')) // '--x--x--x'
 * console.log(set('---------', 'a+x', 'a+r')) // 'r-xr-xr-x'
 * console.log(set('--x--x--x', 'o-x')) // '--x--x---'
 * console.log(set('a+x', 'a+r')) // 'a+rx'
 * console.log(set('4660', 'a-st')) // '0660'
 * ```
 */
export function set(
  permission: PermissionOctal,
  setPermission: Permission,
  ...alsoSetPermissions: Permission[]
): PermissionOctal
export function set(
  permission: PermissionNumber,
  setPermission: Permission,
  ...alsoSetPermissions: Permission[]
): PermissionNumber
export function set(
  permission: PermissionStat,
  setPermission: Permission,
  ...alsoSetPermissions: Permission[]
): PermissionStat
export function set(
  permission: PermissionSymbolic,
  setPermission: Permission,
  ...alsoSetPermissions: Permission[]
): PermissionSymbolic
export function set(
  permission: PermissionObject,
  setPermission: Permission,
  ...alsoSetPermissions: Permission[]
): PermissionObject
export function set(
  permission: unknown,
  setPermission: Permission,
  ...alsoSetPermissions: Permission[]
): never

/**
 * Inverts `permission` including special permissions.
 * This can be used in combination with `set()` to unset `permissions` instead
 * of setting them.
 *
 * @example
 * ```js
 * console.log(not('u+xs')) // 'u-xs'
 * console.log(not('u-xs')) // 'u+xs'
 * console.log(not('u=x')) // 'u=rws'
 * console.log(not('a=x')) // 'ug=rws,o=rwt'
 * console.log(not('rws-ws-w-')) // '---r--r-t'
 * console.log(not('0660')) // '7117'
 * console.log(not('1660')) // '6117'
 * console.log(set('rwxrwxrwx', not('a+x'))) // 'rw-rw-rw-'
 * console.log(set('---------', not('a-x'))) // '--x--x--x'
 * console.log(set('a+xr', not('a+r'))) // 'a+x,a-r'
 * ```
 */
export function not(permission: PermissionOctal): PermissionOctal
export function not(permission: PermissionNumber): PermissionNumber
export function not(permission: PermissionStat): PermissionStat
export function not(permission: PermissionSymbolic): PermissionSymbolic
export function not(permission: PermissionObject): PermissionObject
export function not(permission: unknown): never

/**
 * Inverts `permission` and removes special permissions.
 * For example a [`umask`](https://linux.die.net/man/2/umask) of `117` means new
 * files will be created with `661` permissions.
 *
 * @example
 * ```js
 * console.log(invert('u+xs')) // 'u-x'
 * console.log(invert('u-xs')) // 'u+x'
 * console.log(invert('u=x')) // 'u+rw,u-x'
 * console.log(invert('a=x')) // 'a+rw,a-x'
 * console.log(invert('rws-ws-w-')) // '---r--r-x'
 * console.log(invert('0660')) // '0117'
 * console.log(invert('1660')) // '0117'
 * ```
 */
export function invert(permission: PermissionOctal): PermissionOctal
export function invert(permission: PermissionNumber): PermissionNumber
export function invert(permission: PermissionStat): PermissionStat
export function invert(permission: PermissionSymbolic): PermissionSymbolic
export function invert(permission: PermissionObject): PermissionObject
export function invert(permission: unknown): never

/**
 * Retrieves the lowest permissions among all arguments.
 *
 * This does not return the lowest argument. Instead it returns a combination of
 * the lowest bits of all arguments.
 *
 * This can be useful if you are looking for the lowest permission of a several
 * files, e.g. during a directory recursion.
 *
 * @example
 * ```js
 * console.log(min('404', '440', '402')) // '0400'
 * ```
 */
export function min(): undefined
export function min(
  permission: PermissionOctal,
  ...otherPermissions: Permission[]
): PermissionOctal
export function min(
  permission: PermissionNumber,
  ...otherPermissions: Permission[]
): PermissionNumber
export function min(
  permission: PermissionStat,
  ...otherPermissions: Permission[]
): PermissionStat
export function min(
  permission: PermissionSymbolic,
  ...otherPermissions: Permission[]
): PermissionSymbolic
export function min(
  permission: PermissionObject,
  ...otherPermissions: Permission[]
): PermissionObject
export function min(
  permission: unknown,
  ...otherPermissions: Permission[]
): never

/**
 * Retrieves the highest permissions among all arguments.
 *
 * This does not return the highest argument. Instead it returns a combination
 * of the highest bits of all arguments.
 *
 * This can be useful if you are looking for the highest permission of a several
 * files, e.g. during a directory recursion.
 *
 * @example
 * ```js
 * console.log(max('404', '440', '402')) // '04446'
 * ```
 */
export function max(): undefined
export function max(
  permission: PermissionOctal,
  ...otherPermissions: Permission[]
): PermissionOctal
export function max(
  permission: PermissionNumber,
  ...otherPermissions: Permission[]
): PermissionNumber
export function max(
  permission: PermissionStat,
  ...otherPermissions: Permission[]
): PermissionStat
export function max(
  permission: PermissionSymbolic,
  ...otherPermissions: Permission[]
): PermissionSymbolic
export function max(
  permission: PermissionObject,
  ...otherPermissions: Permission[]
): PermissionObject
export function max(
  permission: unknown,
  ...otherPermissions: Permission[]
): never
