"use strict";
var _a, _b, _c, _d;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileHandle = void 0;
const readline_1 = require("readline");
const kBaseFs = Symbol(`kBaseFs`);
const kFd = Symbol(`kFd`);
const kClosePromise = Symbol(`kClosePromise`);
const kCloseResolve = Symbol(`kCloseResolve`);
const kCloseReject = Symbol(`kCloseReject`);
const kRefs = Symbol(`kRefs`);
const kRef = Symbol(`kRef`);
const kUnref = Symbol(`kUnref`);
class FileHandle {
    constructor(fd, baseFs) {
        this[_a] = 1;
        this[_b] = undefined;
        this[_c] = undefined;
        this[_d] = undefined;
        this[kBaseFs] = baseFs;
        this[kFd] = fd;
    }
    get fd() {
        return this[kFd];
    }
    async appendFile(data, options) {
        try {
            this[kRef](this.appendFile);
            const encoding = (typeof options === `string` ? options : options?.encoding) ?? undefined;
            return await this[kBaseFs].appendFilePromise(this.fd, data, encoding ? { encoding } : undefined);
        }
        finally {
            this[kUnref]();
        }
    }
    async chown(uid, gid) {
        try {
            this[kRef](this.chown);
            return await this[kBaseFs].fchownPromise(this.fd, uid, gid);
        }
        finally {
            this[kUnref]();
        }
    }
    async chmod(mode) {
        try {
            this[kRef](this.chmod);
            return await this[kBaseFs].fchmodPromise(this.fd, mode);
        }
        finally {
            this[kUnref]();
        }
    }
    createReadStream(options) {
        return this[kBaseFs].createReadStream(null, { ...options, fd: this.fd });
    }
    createWriteStream(options) {
        return this[kBaseFs].createWriteStream(null, { ...options, fd: this.fd });
    }
    // FIXME: Missing FakeFS version
    datasync() {
        throw new Error(`Method not implemented.`);
    }
    // FIXME: Missing FakeFS version
    sync() {
        throw new Error(`Method not implemented.`);
    }
    async read(bufferOrOptions, offsetOrOptions, length, position) {
        try {
            this[kRef](this.read);
            let buffer;
            let offset;
            if (!ArrayBuffer.isView(bufferOrOptions)) {
                // read([options])
                // TypeScript isn't able to infer that the coalescing happens only in the no-generic case
                buffer = bufferOrOptions?.buffer ?? Buffer.alloc(16384);
                offset = bufferOrOptions?.offset ?? 0;
                length = bufferOrOptions?.length ?? buffer.byteLength - offset;
                position = bufferOrOptions?.position ?? null;
            }
            else if (typeof offsetOrOptions === `object` && offsetOrOptions !== null) {
                // read(buffer[, options])
                buffer = bufferOrOptions;
                offset = offsetOrOptions?.offset ?? 0;
                length = offsetOrOptions?.length ?? buffer.byteLength - offset;
                position = offsetOrOptions?.position ?? null;
            }
            else {
                // read(buffer, offset[, length[, position]])
                buffer = bufferOrOptions;
                offset = offsetOrOptions ?? 0;
                length ??= 0;
            }
            if (length === 0) {
                return {
                    bytesRead: length,
                    buffer,
                };
            }
            const bytesRead = await this[kBaseFs].readPromise(this.fd, 
            // FIXME: FakeFS should support ArrayBufferViews directly
            Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength), offset, length, position);
            return {
                bytesRead,
                buffer,
            };
        }
        finally {
            this[kUnref]();
        }
    }
    async readFile(options) {
        try {
            this[kRef](this.readFile);
            const encoding = (typeof options === `string` ? options : options?.encoding) ?? undefined;
            return await this[kBaseFs].readFilePromise(this.fd, encoding);
        }
        finally {
            this[kUnref]();
        }
    }
    readLines(options) {
        return (0, readline_1.createInterface)({
            input: this.createReadStream(options),
            crlfDelay: Infinity,
        });
    }
    async stat(opts) {
        try {
            this[kRef](this.stat);
            return await this[kBaseFs].fstatPromise(this.fd, opts);
        }
        finally {
            this[kUnref]();
        }
    }
    async truncate(len) {
        try {
            this[kRef](this.truncate);
            return await this[kBaseFs].ftruncatePromise(this.fd, len);
        }
        finally {
            this[kUnref]();
        }
    }
    // FIXME: Missing FakeFS version
    utimes(atime, mtime) {
        throw new Error(`Method not implemented.`);
    }
    async writeFile(data, options) {
        try {
            this[kRef](this.writeFile);
            const encoding = (typeof options === `string` ? options : options?.encoding) ?? undefined;
            await this[kBaseFs].writeFilePromise(this.fd, data, encoding);
        }
        finally {
            this[kUnref]();
        }
    }
    async write(...args) {
        try {
            this[kRef](this.write);
            if (ArrayBuffer.isView(args[0])) {
                const [buffer, offset, length, position] = args;
                const bytesWritten = await this[kBaseFs].writePromise(this.fd, buffer, offset ?? undefined, length ?? undefined, position ?? undefined);
                return { bytesWritten, buffer };
            }
            else {
                const [data, position, encoding] = args;
                // @ts-expect-error - FIXME: Types/implementation need to be updated in FakeFS
                const bytesWritten = await this[kBaseFs].writePromise(this.fd, data, position, encoding);
                return { bytesWritten, buffer: data };
            }
        }
        finally {
            this[kUnref]();
        }
    }
    // TODO: Use writev from FakeFS when that is implemented
    async writev(buffers, position) {
        try {
            this[kRef](this.writev);
            let bytesWritten = 0;
            if (typeof position !== `undefined`) {
                for (const buffer of buffers) {
                    const writeResult = await this.write(buffer, undefined, undefined, position);
                    bytesWritten += writeResult.bytesWritten;
                    position += writeResult.bytesWritten;
                }
            }
            else {
                for (const buffer of buffers) {
                    const writeResult = await this.write(buffer);
                    bytesWritten += writeResult.bytesWritten;
                }
            }
            return {
                buffers,
                bytesWritten,
            };
        }
        finally {
            this[kUnref]();
        }
    }
    // FIXME: Missing FakeFS version
    readv(buffers, position) {
        throw new Error(`Method not implemented.`);
    }
    close() {
        if (this[kFd] === -1)
            return Promise.resolve();
        if (this[kClosePromise])
            return this[kClosePromise];
        this[kRefs]--;
        if (this[kRefs] === 0) {
            const fd = this[kFd];
            this[kFd] = -1;
            this[kClosePromise] = this[kBaseFs].closePromise(fd).finally(() => {
                this[kClosePromise] = undefined;
            });
        }
        else {
            this[kClosePromise] =
                new Promise((resolve, reject) => {
                    this[kCloseResolve] = resolve;
                    this[kCloseReject] = reject;
                }).finally(() => {
                    this[kClosePromise] = undefined;
                    this[kCloseReject] = undefined;
                    this[kCloseResolve] = undefined;
                });
        }
        return this[kClosePromise];
    }
    [(_a = kRefs, _b = kClosePromise, _c = kCloseResolve, _d = kCloseReject, kRef)](caller) {
        if (this[kFd] === -1) {
            const err = new Error(`file closed`);
            err.code = `EBADF`;
            err.syscall = caller.name;
            throw err;
        }
        this[kRefs]++;
    }
    [kUnref]() {
        this[kRefs]--;
        if (this[kRefs] === 0) {
            const fd = this[kFd];
            this[kFd] = -1;
            this[kBaseFs].closePromise(fd).then(this[kCloseResolve], this[kCloseReject]);
        }
    }
}
exports.FileHandle = FileHandle;
