/*
 Copyright (C) 2026 3NSoft Inc.

 This program is free software: you can redistribute it and/or modify it under
 the terms of the GNU General Public License as published by the Free Software
 Foundation, either version 3 of the License, or (at your option) any later
 version.

 This program is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along with
 this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { join } from 'path';
import { toBuffer } from '../../lib-common/buffer-utils';
import { toCanonicalAddress } from '../../lib-common/canonical-address';
import { makeFileException, Code } from '../../lib-common/exceptions/file';
import { WeakCache } from '../../lib-common/weak-cache';
import { FuserCallbacks, FileAttr, ForgetOpCB, GetAttrOpCB, SetAttrOpCB, OpenOpCB, ReadOpCB, ReleaseOpCB, OpenDirOpCB, ReadDirOpCB, ReleaseDirOpCB, GetXAttrOpCB, ListXAttrOpCB, AccessOpCB, DirectoryKind, FileKind, kindOf, DirEntry } from './native-mods';
import { UserMounts } from './user-mounts';
import { defer, Deferred } from '../../lib-common/processes/deferred';

type FS = web3n.files.FS;
type File = web3n.files.File;
type Stats = web3n.files.Stats;
type FileException = web3n.files.FileException;
type ListingEntry = web3n.files.ListingEntry;


const DIR_OPENING_FLAGS = 1;
const FILE_OPENING_FLAGS = 1;

export function makeMounting(rootMountpointPath: string): {
	getOrMakeUserMounts: (userId: string) => Promise<UserMounts>;
	implOfFuserCallbacks: () => FuserCallbacks;
	destroySignal: Promise<void>;
} {

	const userMounts = new Map<string, UserMounts>();
	const deferredCompletion = defer<void>();

	let logsCounter = 0;
	function debugLog(...toLog: any[]): void {
		if (logsCounter > 20) {
			return;
		}
		logsCounter += 1;
		console.log(...toLog);
	}

	async function getOrMakeUserMounts(userId: string): Promise<UserMounts> {
		const uId = toCanonicalAddress(userId);
		let mounts = userMounts.get(uId);
		if (!mounts) {
			mounts = new UserMounts(
				userId, await descriptors.makeNodeForUser(userId), join(rootMountpointPath, userId)
			);
			userMounts.set(uId, mounts);
		}
		return mounts;
	}

	const descriptors = new Descriptors();

	const init: FuserCallbacks['init'] = (_err, rootIno) => {
		debugLog(` ▶ 👀 fuser init called with`, {_err, rootIno});
		descriptors.init(rootIno);
	};

	const destroy: FuserCallbacks['destroy'] = () => {
		debugLog(` 🏁 👀 removing mount at ${rootMountpointPath}`);
		descriptors.destroy();
		userMounts.clear();
		deferredCompletion.resolve();
	};

	const lookup: FuserCallbacks['lookup'] = async (_err, dirIno, childName) => {
		debugLog(` ▶ 👀 fuser lookup called with`, {_err, dirIno, childName});
		let dir = descriptors.getDirectory(dirIno);
		if (typeof dir === 'number') {
			return errReply(dir);
		}
		if (dir instanceof DirectoryInMounted) {
			childName = `${dir.pathInFS}/${childName}`;
			dir = dir.fsNode;
		}
		try {
			return { type: 'Attr', field0: await dir.lookup(childName) };
		} catch (exc) {
			return errReply(exc);
		}
	};

	const forget: ForgetOpCB = (_err, ino, nlookup) => {
		debugLog(` ▶ 👀 fuser forget called with`, {_err, ino, nlookup});
		descriptors?.forgetDescriptorBy(ino, nlookup);
	};

	const getattr: GetAttrOpCB = async (_err, ino, fh) => {
		debugLog(` ▶ 👀 fuser getattr called with`, {_err, ino, fh});
		let d = descriptors.get(ino);
		if (typeof d === 'number') {
			return errReply(d);
		}
		try {
			return { type: 'Attr', field0: await d.attrs() };
		} catch (exc) {
			return errReply(exc);
		}
	};

	const setattr: SetAttrOpCB = async (_err, ino, fh, changes) => {
		console.log(` ► setattr needs impl`, {ino, fh, changes});
		// XXX

		return errReply(ENOSYS);
	};

	const open: OpenOpCB = async (_err, ino, flags) => {
		debugLog(` ▶ 👀 fuser open called with`, {_err, ino, flags});
		const d = descriptors.getFile(ino);
		if (typeof d === 'number') {
			return errReply(d);
		}
		try {
			const fh = d.handles.open(flags);
			return { type: 'Params', field0: { fh, flags: FILE_OPENING_FLAGS } };
		} catch (exc) {
			return errReply(exc);
		}
	};

	const read: ReadOpCB = async (_err, ino, fh, readArgs) => {
		debugLog(` ▶ 👀 fuser open called with`, {_err, ino, fh, readArgs});
		const d = descriptors.getFile(ino);
		if (typeof d === 'number') {
			return errReply(d);
		}
		try {
			const { offset, size, flags, lockOwner } = readArgs;
			const bytes = await d.readFileBytes(fh, offset, size);
			const buf = (bytes ? toBuffer(bytes) : Buffer.alloc(0));
			return { type: 'Ok', field0: buf };
		} catch (exc) {
			return errReply(exc);
		}
	};

	const release: ReleaseOpCB = async (_err, ino, fh, releaseArgs) => {
		debugLog(` ▶ 👀 fuser open called with`, {_err, ino, fh, releaseArgs});
		const d = descriptors.get(ino);
		if (typeof d !== 'number') {
			d.handles.drop(fh);
		}
	};

	const opendir: OpenDirOpCB = async (_err, dirIno, flags) => {
		debugLog(` ▶ 👀 fuser open called with`, {_err, dirIno, flags});
		let dir = descriptors.getDirectory(dirIno);
		if (typeof dir === 'number') {
			return errReply(dir);
		}
		try {
			const fh = dir.handles.open(flags);
			return { type: 'Params', field0: { fh, flags: DIR_OPENING_FLAGS } };
		} catch (exc) {
			return errReply(exc);
		}
	};

	const readdir: ReadDirOpCB = async (_err, dirIno, fh, offset) => {
		debugLog(` ▶ 👀 fuser open called with`, {_err, dirIno, fh, offset});
		let dir = descriptors.getDirectory(dirIno);
		if (typeof dir === 'number') {
			return errReply(dir);
		}
		try {
			const h = dir.handles.get(fh);
			let entries = h?.lst;
			if (!entries) {
				entries = fsListingToFuse(await dir.list());
				if (h) {
					h.lst = entries;
				}
			}
			if (offset > 0) {
				entries = entries.slice(offset);
			}
			return { type: 'Lst', field0: entries };
		} catch (exc) {
			return errReply(exc);
		}
	};

	const releasedir: ReleaseDirOpCB = async (_err, dirIno, fh, flags) => {
		debugLog(` ▶ 👀 fuser open called with`, {_err, dirIno, fh, flags});
		let dir = descriptors.getDirectory(dirIno);
		if (typeof dir !== 'number') {
			dir.handles.drop(fh);
		}
	};

	const getxattr: GetXAttrOpCB = async (_err, ino, name, size) => {
		console.log(` ► getxattr needs impl`, {ino, name, size});
		// XXX

		return errReply(ENOSYS);
	};

	const listxattr: ListXAttrOpCB = async (_err, ino, size) => {
		console.log(` ► listxattr needs impl`, {ino, size});
		const d = descriptors.get(ino);
		if (typeof d === 'number') {
			return errReply(d);
		} else if (d instanceof Node) {
			return { type: 'Data', field0: Buffer.alloc(0) };
		}
		try {

			// XXX 
			return errReply(ENOSYS);

			// let lst: string[];
			// if (d instanceof DescriptorInMountedFS) {
			// 	lst = await d.fsNode.fs.listXAttrs(d.pathInFS);
			// } else if (d instanceof NodeWithFS) {
			// 	lst = await d.fs.listXAttrs('');
			// } else {
			// 	lst = await d.file.listXAttrs();
			// }

			// return { type: 'Data', field0: buffer };
			// return { type: 'Size', field0: nums };
		} catch (exc) {
			return errReply(exc);
		}
	};

	const access: AccessOpCB = async (_err, ino, mask) => {
		debugLog(` ▶ 👀 fuser access called with`, {_err, ino, mask});
		// console.log(` ► access`, {ino, mask});
		const d = descriptors.get(ino);
		if (typeof d === 'number') {
			return d;
		}

		// XXX check access

		return 0;
	};

	return {
		getOrMakeUserMounts,
		implOfFuserCallbacks: () => ({
			init, destroy, lookup, forget, getattr, setattr, open, read, release, opendir, readdir, releasedir, getxattr, listxattr, access
		}),
		destroySignal: deferredCompletion.promise
	}
}

function fsListingToFuse(lst: ListingEntry[]): DirEntry[] {
	const entries: DirEntry[] = [];
	for (let i=0; i<lst.length; i+=1) {
		const entry = lst[i];
		entries.push({
			offset: i+1,
			name: entry.name,
			kind: kindOf(entry)
		});
	}
	return entries;
}

class Descriptors {

	private readonly descriptors = new Map<number, Descriptor>();
	private rootNode: Node|undefined = undefined;
	private deferredInit: Deferred<void>|undefined = defer();

	init(rootIno: number) {
		this.rootNode = new Node(
			rootIno,
			undefined as any,
			nextRDev(),
			this.newUniqueInoValue.bind(this),
			this.descriptors
		);
		this.deferredInit?.resolve();
		this.deferredInit = undefined;
		this.descriptors.set(this.rootNode.ino, this.rootNode);
	}

	private newUniqueInoValue(): number {
		let ino = nextIno();
		while (this.descriptors.has(ino)) {
			ino = nextIno();
		}
		return ino;
	}

	async makeNodeForUser(userId: string): Promise<Node> {
		if (!this.rootNode) {
			await this.deferredInit!.promise;
		}
		return this.rootNode!.makeNewChildNode(userId);
	}

	get(ino: number): Descriptor|number {
		if (this.rootNode) {
			const d = this.descriptors.get(ino);
			return (d ?? ENOENT);
		} else {
			return ENOSYS;
		}
	}

	getFile(ino: number): FileDescriptor|NodeWithFile|number {
		if (this.rootNode) {
			const d = this.descriptors.get(ino);
			if (!d) {
				return ENOENT;
			} else if ((d as DirectoryDescriptor).isDir) {
				return EISDIR;
			} else {
				return d as FileDescriptor|NodeWithFile;
			}
		} else {
			return ENOSYS;
		}
	}

	getDirectory(dirIno: number): DirectoryDescriptor|number {
		if (this.rootNode) {
			const d = this.descriptors.get(dirIno);
			if (!d) {
				return ENOENT;
			} else if ((d as DirectoryDescriptor).isDir) {
				return d as DirectoryDescriptor;
			} else {
				return EISDIR;
			}
		} else {
			return ENOSYS;
		}
	}

	destroy(): void {
		if (this.rootNode) {
			this.closeMountedIn(this.rootNode);
			this.descriptors.clear();
			this.rootNode = undefined;
		}
	}

	forgetDescriptorBy(ino: number, nlookup: number): void {
		const d = this.descriptors.get(ino);
		if (!d) {
			return;
		}
		if (d instanceof DescriptorInMountedFS) {
			d.nlookup -= nlookup;
			if (d.nlookup < 1) {
				this.dropDescriptor(d);
			}
		}
	}

	private dropDescriptor(d: DescriptorInMountedFS): void {
		this.descriptors.delete(d.ino);
		d.fsNode.descriptorsCache.delete(d.pathInFS);
	}

	private closeMountedIn(n: Node|NodeWithFS|NodeWithFile): void {}

}

class Handles<H> {

	private readonly handles = new Map<number, H>();
	private readonly nextFh = makeUIntGenerator();
	constructor(
		private readonly makeHandle: () => H
	) {}

	open(flagsOnOpening: number): number {
		let fh = this.nextFh();
		while (this.handles.has(fh)) {
			fh = this.nextFh();
		}
		this.handles.set(fh, this.makeHandle());
		return fh;
	}

	drop(fh: number): void {
		this.handles.delete(fh);
	}

	get(fh: number): H|undefined {
		return this.handles.get(fh);
	}

}

type Descriptor = DirectoryDescriptor | FileDescriptor | SymLinkDescriptor;
type DirectoryDescriptor = Node | NodeWithFS | DirectoryInMounted;
export type Nodes = (NodeWithFS | NodeWithFile | Node)[];
type FileDescriptor = NodeWithFile | FileInMounted;
type SymLinkDescriptor = SymLinkInMounted;

function makeUIntGenerator(): (() => number) {
	let nextNum = 1;
	return () => {
		let num = nextNum;
		nextNum += 1;
		if (nextNum >= Number.MAX_SAFE_INTEGER) {
			nextNum = 1;
		}
		return num;
	};
}

const nextIno = makeUIntGenerator();
const nextRDev = makeUIntGenerator();

const uid = process.getuid?.()!;
const gid = process.getgid?.()!;

export class Node {
	readonly isDir = true;
	readonly children = new Map<string, NodeWithFS|NodeWithFile|Node>();
	readonly handles = new Handles(Node.makeDirectoryHandle);
	btime = Date.now();
	ctime = Date.now();
	mtime = Date.now();
	constructor(
		public readonly ino: number,
		public readonly parent: Node,
		public readonly rdev: number,
		private readonly newUniqueInoValue: () => number,
		private readonly allNodesById: Map<number, Descriptor>
	) {}

	private static makeDirectoryHandle(): DirectoryHandle {
		return {};
	}

	makeNewChildNode(name: string): Node {
		if (this.children.has(name)) {
			throw makeFileException('alreadyExists', name);
		}
		const ino = this.newUniqueInoValue();
		const node = new Node(ino, this, this.rdev, this.newUniqueInoValue, this.allNodesById);
		this.children.set(name, node);
		this.allNodesById.set(node.ino, node);
		return node;
	}

	getOrMakeChildNode(name: string): Node {
		let child = this.children.get(name);
		if (!child) {
			return this.makeNewChildNode(name);
		} else if ((child instanceof NodeWithFS) || (child instanceof NodeWithFile)) {
			throw makeFileException('alreadyExists', name);
		} else {
			return child;
		}
	}

	mountFS(name:string, fs: FS): NodeWithFS {
		if (this.children.has(name)) {
			throw makeFileException('alreadyExists', name);
		}
		const ino = this.newUniqueInoValue();
		const node = new NodeWithFS(ino, this, fs, nextRDev(), this.newUniqueInoValue, this.allNodesById);
		this.children.set(name, node);
		this.allNodesById.set(node.ino, node);
		return node;
	}

	mountFile(name:string, file: File): NodeWithFile {
		if (this.children.has(name)) {
			throw makeFileException('alreadyExists', name);
		}
		const ino = this.newUniqueInoValue();
		const node = new NodeWithFile(ino, this, file, nextRDev());
		this.children.set(name, node);
		this.allNodesById.set(node.ino, node);
		return node;
	}

	async lookup(name: string): Promise<FileAttr> {
		const child = this.children.get(name);
		if (!child) {
			throw makeFileException('notFound', name);
		}
		return await child.attrs();
	}

	attrs(): FileAttr {
		let keysLen = 0;
		for (const s of this.children.keys()) {
			keysLen += s.length + 1;
		}
		return {
			ino: this.ino,
			rdev: this.rdev,
			uid,
			gid,
			kind: DirectoryKind,
			btime: this.btime,
			ctime: this.ctime,
			mtime: this.mtime,
			size: Math.max(255, keysLen),
			flags: 0,
			perm: 0o550,
		};
	}

	list(): ListingEntry[] {
		const lst: ListingEntry[] = [];
		for (const [name, node] of this.children.entries()) {
			lst.push((node instanceof NodeWithFile) ? { name, isFile: true } : { name, isFolder: true });
		}
		return lst;
	}

	removeFromTree(): void {
		this.parent.removeChildNode(this);
		this.allNodesById.delete(this.ino);
		this.removeChildren();
	}

	private removeChildren(): void {
		const children = Array.from(this.children.values());
		this.children.clear();
		for (const child of children) {
			this.allNodesById.delete(child.ino);
			if (child instanceof Node) {
				child.removeChildren();
			} else if (child instanceof NodeWithFS) {
				child.fs.close();
				for (const d of child.descriptorsCache.values()) {
					this.allNodesById.delete(d.ino);
				}
			}
		}
	}

	removeChildNode(child: NodeWithFS|NodeWithFile|Node): void {
		let nameToRm: string|undefined = undefined;
		for (const [name, node] of this.children.entries()) {
			if (node === child) {
				nameToRm = name;
				break;
			}
		}
		if (nameToRm !== undefined) {
			this.children.delete(nameToRm);
			this.mtime = Date.now();
		}
	}

}

export class NodeWithFS {
	readonly isDir = true;
	readonly descriptorsCache = new WeakCache<string, DirectoryInMounted|FileInMounted|SymLinkDescriptor>();
	readonly handles = new Handles(NodeWithFS.makeDirectoryHandle);
	constructor(
		public readonly ino: number,
		public readonly parent: Node,
		public readonly fs: FS,
		public readonly rdev: number,
		private readonly newUniqueInoValue: () => number,
		private readonly allNodesById: Map<number, Descriptor>
	) {}

	static makeDirectoryHandle(): DirectoryHandle {
		return {};
	}

	async lookup(path: string): Promise<FileAttr> {
		let d = this.descriptorsCache.get(path);
		let attrs: FileAttr;
		if (d) {
			attrs = await this.attrsOf(d);
		} else {
			const stats = await this.fs.stat(path);
			const ino = this.newUniqueInoValue();
			if (stats.isFolder) {
				d = new DirectoryInMounted(ino, this, path);
			} else if (stats.isFile) {
				d = new FileInMounted(ino, this, path);
			} else {
				d = new SymLinkInMounted(ino, this, path);
			}
			this.allNodesById.set(d.ino, d);
			this.descriptorsCache.set(path, d);
			attrs = await this.attrsOf(d, stats);
		}
		d.nlookup += 1;
		return attrs;
	}

	async attrs(): Promise<FileAttr> {
		const stats = await this.fs.stat('');
		return {
			ino: this.ino,
			rdev: this.rdev,
			uid,
			gid,
			kind: DirectoryKind,
			// XXX this needs to change, when core returns proper b(irth)time
			btime: stats.ctime?.valueOf() ?? 0,
			ctime: stats.ctime?.valueOf() ?? 0,
			mtime: stats.mtime?.valueOf() ?? 0,
			size: stats.size ?? 0,
			flags: 0,
			perm: 0o550,
		};
	}

	async attrsOf(d: DescriptorInMountedFS, stats?: Stats): Promise<FileAttr> {
		const { pathInFS, ino } = d;
		if (!stats) {
			stats = await this.fs.stat(pathInFS);
		}
		return {
			ino,
			rdev: this.rdev,
			uid,
			gid,
			kind: kindOf(stats),
			// XXX this needs to change, when core returns proper b(irth)time
			btime: stats.ctime?.valueOf() ?? 0,
			ctime: stats.ctime?.valueOf() ?? 0,
			mtime: stats.mtime?.valueOf() ?? 0,
			size: stats.size ?? 0,
			flags: 0,
			perm: (stats.isFolder ? 0o550 : 0o440)
		};
	}

	async list(): Promise<ListingEntry[]> {
		return this.fs.listFolder('');
	}

	removeFromTree(): void {
		this.parent.removeChildNode(this);
	}

}

export class NodeWithFile {
	readonly handles = new Handles(NodeWithFile.makeFileHandle);
	constructor(
		public readonly ino: number,
		public readonly parent: Node,
		public readonly file: File,
		public readonly rdev: number,
	) {}

	static makeFileHandle(): FileHandle {
		return {};
	}

	async attrs(): Promise<FileAttr> {
		const stats = await this.file.stat();
		return {
			ino: this.ino,
			rdev: this.rdev,
			uid,
			gid,
			kind: FileKind,
			// XXX this needs to change, when core returns proper b(irth)time
			btime: stats.ctime?.valueOf() ?? 0,
			ctime: stats.ctime?.valueOf() ?? 0,
			mtime: stats.mtime?.valueOf() ?? 0,
			size: stats.size ?? 0,
			flags: 0,
			perm: 0o440,
		};
	}

	readFileBytes(fh: number, ofs: number, len: number) {
		// XXX add work with file handler
		return this.file.readBytes(ofs, ofs+len);
	}

	removeFromTree(): void {
		this.parent.removeChildNode(this);
	}

}

class DescriptorInMountedFS {
	nlookup = 0;
	constructor(
		public readonly ino: number,
		public readonly fsNode: NodeWithFS,
		public readonly pathInFS: string
	) {}

	attrs(): Promise<FileAttr> {
		return this.fsNode.attrsOf(this);
	}

	list() {
		return this.fsNode.fs.listFolder(this.pathInFS);
	}

}

interface DirectoryHandle {
	lst?: DirEntry[];
	version?: number;
}

interface FileHandle {}

interface SymLinkHandle {}

class DirectoryInMounted extends DescriptorInMountedFS {
	readonly isDir = true;
	readonly handles = new Handles(NodeWithFS.makeDirectoryHandle);
	constructor(ino: number, fsNode: NodeWithFS, pathInFS: string) {
		super(ino, fsNode, pathInFS);
	}
}

class FileInMounted extends DescriptorInMountedFS {
	readonly handles = new Handles(NodeWithFile.makeFileHandle);
	constructor(ino: number, fsNode: NodeWithFS, pathInFS: string) {
		super(ino, fsNode, pathInFS);
	}

	readFileBytes(fh: number, ofs: number, len: number) {
		// XXX add work with file handler
		return this.fsNode.fs.readBytes(this.pathInFS, ofs, ofs+len);
	}

}

class SymLinkInMounted extends DescriptorInMountedFS {
	readonly handles = new Handles(NodeWithFile.makeFileHandle);
	constructor(ino: number, fsNode: NodeWithFS, pathInFS: string) {
		super(ino, fsNode, pathInFS);
	}
}

function errReply(err: number|FileException): { type: 'Err', field0: number } {
	if (typeof err === 'number') {
		return { type: 'Err', field0: err };
	}
	switch (err?.code) {
		case Code.notFound:
			return errReply(ENOENT);
		case Code.ioError:
			return errReply(EIO);
		case Code.notImplemented:
			return errReply(ENOSYS);
		case Code.alreadyExists:
			return errReply(EEXIST);
		case Code.notDirectory:
			return errReply(ENOTDIR);
		case Code.notFile:
			return errReply(ENOENT);
		case Code.isDirectory:
			return errReply(EISDIR);
		case Code.notEmpty:
			return errReply(ENOTEMPTY);
		case Code.opNotPermitted:
			return errReply(EPERM);
		case Code.busy:
			return errReply(EBUSY);
		default:
			return errReply(ENOSYS);
	}
}

// constants for errors that FUSE callback gives to kernel driver
const EPERM = 1;
const ENOENT = 2;
const ESRCH = 3;
const EINTR = 4;
const EIO = 5;
const ENXIO = 6;
const E2BIG = 7;
const ENOEXEC = 8;
const EBADF = 9;
const ECHILD = 10;
const EAGAIN = 11;
const ENOMEM = 12;
const EACCES = 13;
const EFAULT = 14;
const ENOTBLK = 15;
const EBUSY = 16;
const EEXIST = 17;
const EXDEV = 18;
const ENODEV = 19;
const ENOTDIR = 20;
const EISDIR = 21;
const EINVAL = 22;
const ENFILE = 23;
const EMFILE = 24;
const ENOTTY = 25;
const ETXTBSY = 26;
const EFBIG = 27;
const ENOSPC = 28;
const ESPIPE = 29;
const EROFS = 30;
const EMLINK = 31;
const EPIPE = 32;
const EDOM = 33;
const ERANGE = 34;
const EDEADLK = 35;
const ENAMETOOLONG = 36;
const ENOLCK = 37;
const ENOSYS = 38;
const ENOTEMPTY = 39;
const ELOOP = 40;
const EWOULDBLOCK = 11;
const ENOMSG = 42;
const EIDRM = 43;
const ECHRNG = 44;
const EL2NSYNC = 45;
const EL3HLT = 46;
const EL3RST = 47;
const ELNRNG = 48;
const EUNATCH = 49;
const ENOCSI = 50;
const EL2HLT = 51;
const EBADE = 52;
const EBADR = 53;
const EXFULL = 54;
const ENOANO = 55;
const EBADRQC = 56;
const EBADSLT = 57;
const EDEADLOCK = 35;
const EBFONT = 59;
const ENOSTR = 60;
const ENODATA = 61;
const ETIME = 62;
const ENOSR = 63;
const ENONET = 64;
const ENOPKG = 65;
const EREMOTE = 66;
const ENOLINK = 67;
const EADV = 68;
const ESRMNT = 69;
const ECOMM = 70;
const EPROTO = 71;
const EMULTIHOP = 72;
const EDOTDOT = 73;
const EBADMSG = 74;
const EOVERFLOW = 75;
const ENOTUNIQ = 76;
const EBADFD = 77;
const EREMCHG = 78;
const ELIBACC = 79;
const ELIBBAD = 80;
const ELIBSCN = 81;
const ELIBMAX = 82;
const ELIBEXEC = 83;
const EILSEQ = 84;
const ERESTART = 85;
const ESTRPIPE = 86;
const EUSERS = 87;
const ENOTSOCK = 88;
const EDESTADDRREQ = 89;
const EMSGSIZE = 90;
const EPROTOTYPE = 91;
const ENOPROTOOPT = 92;
const EPROTONOSUPPORT = 93;
const ESOCKTNOSUPPORT = 94;
const EOPNOTSUPP = 95;
const EPFNOSUPPORT = 96;
const EAFNOSUPPORT = 97;
const EADDRINUSE = 98;
const EADDRNOTAVAIL = 99;
const ENETDOWN = 100;
const ENETUNREACH = 101;
const ENETRESET = 102;
const ECONNABORTED = 103;
const ECONNRESET = 104;
const ENOBUFS = 105;
const EISCONN = 106;
const ENOTCONN = 107;
const ESHUTDOWN = 108;
const ETOOMANYREFS = 109;
const ETIMEDOUT = 110;
const ECONNREFUSED = 111;
const EHOSTDOWN = 112;
const EHOSTUNREACH = 113;
const EALREADY = 114;
const EINPROGRESS = 115;
const ESTALE = 116;
const EUCLEAN = 117;
const ENOTNAM = 118;
const ENAVAIL = 119;
const EISNAM = 120;
const EREMOTEIO = 121;
const EDQUOT = 122;
const ENOMEDIUM = 123;
const EMEDIUMTYPE = 124;


Object.freeze(exports);