// builtin
import {
	stat as _stat,
	rm as _rm,
	unlink as _unlink,
	readdir as _readdir,
} from 'node:fs'
import { platform } from 'node:os'
const isWindows = platform() === 'win32'
import { exec } from 'node:child_process'
import { versions } from 'node:process'
const nodeVersion = String(versions.node || '0')

// external
import accessible, { W_OK } from 'https://unpkg.com/@bevry/fs-accessible@^2.5.0/edition-deno/index.ts'
import Errlop from 'https://unpkg.com/errlop@^8.4.0/edition-deno/index.ts'
import versionCompare from 'https://unpkg.com/version-compare@^3.10.0/edition-deno/index.ts'

/** Remove a file or directory. */
export default async function remove(
	path: string | Array<string>
): Promise<void> {
	if (Array.isArray(path)) {
		return Promise.all(path.map((i) => remove(i))).then(() => {})
	}

	// sanity check
	if (path === '' || path === '/') {
		throw new Error('will not remove root directory')
	}

	// check exists
	try {
		await accessible(path)
	} catch (err: any) {
		// if it doesn't exist, then we don't care
		return
	}

	// check writable
	try {
		await accessible(path, W_OK)
	} catch (err: any) {
		if (err.code === 'ENOENT') {
			// if it doesn't exist, then we don't care (this may not seem necessary due to the earlier accessible check, however it is necessary, testen would fail on @bevry/json otherwise)
			return
		}
		throw new Errlop(`unable to remove the non-writable path: ${path}`, err)
	}

	// attempt removal
	return new Promise(function (resolve, reject) {
		function next(err: any) {
			if (err && err.code === 'ENOENT') return resolve()
			if (err) {
				return reject(
					new Errlop(
						`failed to remove the accessible and writable path: ${path}`,
						err
					)
				)
			}
			return resolve()
		}

		// modern versions have a command that works on files and directories
		if (versionCompare(nodeVersion, '14.14.0') >= 0) {
			// use rm builtin via dynamic import (necessary as older versions will fail as you can't import something that doesn't exist
			import('fs').then(function ({ rm: _rm }) {
				_rm(path, { recursive: true, force: true, maxRetries: 10 }, next)
			})
			return
		}

		// older versions require a different command for files and directories, so determine which it is
		_stat(path, function (err, stat) {
			if (err) return next(err)
			const isDirectory = stat.isDirectory()

			// is file
			if (!isDirectory) {
				_unlink(path, next)
				return
			}

			// is directory
			// NOTE: if you are wondering why this now differs from @bevry/fs-rmdir, it is because the 14.14.0 option is earlier in this file
			if (
				versionCompare(nodeVersion, '12.16.0') >= 0 &&
				versionCompare(nodeVersion, '16') < 0
			) {
				// use rmdir builtin via dynamic import (necessary as older versions will fail as you can't import something that doesn't exist
				import('fs').then(function ({ rmdir: _rmdir }) {
					_rmdir(path, { recursive: true, maxRetries: 10 }, next)
				})
			} else if (
				versionCompare(nodeVersion, '12.10.0') >= 0 &&
				versionCompare(nodeVersion, '12.16.0') < 0
			) {
				// use rmdir builtin via dynamic import (necessary as older versions will fail as you can't import something that doesn't exist
				import('fs').then(function ({ rmdir: _rmdir }) {
					// as any is to workaround that type definition is only for the latest version
					_rmdir(path, { recursive: true, maxBusyTries: 10 } as any, next)
				})
			} else {
				// no builtin option exists, so use platform-specific workarounds
				// rmdir: https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-xp/bb490990(v=technet.10)?redirectedfrom=MSDN
				exec(
					`${isWindows ? `rmdir /s /q` : `rm -rf`} ${JSON.stringify(path)}`,
					next
				)
			}
		})
	})
}
