const ERROR_CODE = {
    0: 'ERAR_SUCCESS',
    10: 'ERAR_END_ARCHIVE',
    11: 'ERAR_NO_MEMORY',
    12: 'ERAR_BAD_DATA',
    13: 'ERAR_BAD_ARCHIVE',
    14: 'ERAR_UNKNOWN_FORMAT',
    15: 'ERAR_EOPEN',
    16: 'ERAR_ECREATE',
    17: 'ERAR_ECLOSE',
    18: 'ERAR_EREAD',
    19: 'ERAR_EWRITE',
    20: 'ERAR_SMALL_BUF',
    21: 'ERAR_UNKNOWN',
    22: 'ERAR_MISSING_PASSWORD',
    23: 'ERAR_EREFERENCE',
    24: 'ERAR_BAD_PASSWORD',
};
const ERROR_MSG = {
    ERAR_NO_MEMORY: 'Not enough memory',
    ERAR_BAD_DATA: 'Archive header or data are damaged',
    ERAR_BAD_ARCHIVE: 'File is not RAR archive',
    ERAR_UNKNOWN_FORMAT: 'Unknown archive format',
    ERAR_EOPEN: 'File open error',
    ERAR_ECREATE: 'File create error',
    ERAR_ECLOSE: 'File close error',
    ERAR_EREAD: 'File read error',
    ERAR_EWRITE: 'File write error',
    ERAR_SMALL_BUF: 'Buffer for archive comment is too small, comment truncated',
    ERAR_UNKNOWN: 'Unknown error',
    ERAR_MISSING_PASSWORD: 'Password for encrypted file or header is not specified',
    ERAR_EREFERENCE: 'Cannot open file source for reference record',
    ERAR_BAD_PASSWORD: 'Wrong password is specified',
};
export class UnrarError extends Error {
    constructor(reason, message, file) {
        super(message);
        this.reason = reason;
        this.file = file;
    }
}
export class Extractor {
    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
    constructor(unrar, password = '') {
        this.unrar = unrar;
        this._password = password;
        this._archive = null;
    }
    getFileList() {
        const arcHeader = this.openArc(true);
        function* getFileHeaders() {
            while (true) {
                const arcFile = this.processNextFile(() => true);
                if (arcFile === 'ERAR_END_ARCHIVE') {
                    break;
                }
                yield arcFile.fileHeader;
            }
            this.closeArc();
        }
        return { arcHeader, fileHeaders: getFileHeaders.call(this) };
    }
    extract({ files, password } = {}) {
        const arcHeader = this.openArc(false, password);
        function* getFiles() {
            let count = 0;
            while (true) {
                let shouldSkip = () => false;
                if (Array.isArray(files)) {
                    if (count === files.length) {
                        break;
                    }
                    shouldSkip = ({ name }) => !files.includes(name);
                }
                else if (files) {
                    shouldSkip = (fileHeader) => !files(fileHeader);
                }
                const arcFile = this.processNextFile(shouldSkip);
                if (arcFile === 'ERAR_END_ARCHIVE') {
                    break;
                }
                if (arcFile.extraction === 'skipped') {
                    continue;
                }
                count++;
                yield {
                    fileHeader: arcFile.fileHeader,
                };
            }
            this.closeArc();
        }
        return { arcHeader, files: getFiles.call(this) };
    }
    fileCreated(filename) {
        return;
    }
    close(fd) {
        this.closeFile(fd);
    }
    openArc(listOnly, password) {
        this._archive = new this.unrar.RarArchive();
        const header = this._archive.open(this._filePath, password ? password : this._password, listOnly);
        if (header.state.errCode !== 0) {
            throw this.getFailException(header.state.errCode, header.state.errType);
        }
        return {
            comment: header.comment,
            flags: {
                volume: (header.flags & 0x0001) !== 0,
                lock: (header.flags & 0x0004) !== 0,
                solid: (header.flags & 0x0008) !== 0,
                authInfo: (header.flags & 0x0020) !== 0,
                recoveryRecord: (header.flags & 0x0040) !== 0,
                headerEncrypted: (header.flags & 0x0080) !== 0,
            },
        };
    }
    processNextFile(shouldSkip) {
        function getDateString(dosTime) {
            const bitLen = [5, 6, 5, 5, 4, 7];
            let parts = [];
            for (const len of bitLen) {
                parts.push(dosTime & ((1 << len) - 1));
                dosTime >>= len;
            }
            parts = parts.reverse();
            const pad = (num) => (num < 10 ? '0' + num : '' + num);
            return (`${1980 + parts[0]}-${pad(parts[1])}-${pad(parts[2])}` +
                `T${pad(parts[3])}:${pad(parts[4])}:${pad(parts[5] * 2)}.000`);
        }
        function getMethod(method) {
            const methodMap = {
                0x30: 'Storing',
                0x31: 'Fastest',
                0x32: 'Fast',
                0x33: 'Normal',
                0x34: 'Good',
                0x35: 'Best',
            };
            return methodMap[method] || 'Unknown';
        }
        const arcFileHeader = this._archive.getFileHeader();
        if (arcFileHeader.state.errCode === 10) {
            return 'ERAR_END_ARCHIVE';
        }
        if (arcFileHeader.state.errCode !== 0) {
            throw this.getFailException(arcFileHeader.state.errCode, arcFileHeader.state.errType);
        }
        const fileHeader = {
            name: arcFileHeader.name,
            flags: {
                encrypted: (arcFileHeader.flags & 0x04) !== 0,
                solid: (arcFileHeader.flags & 0x10) !== 0,
                directory: (arcFileHeader.flags & 0x20) !== 0,
            },
            packSize: arcFileHeader.packSize,
            unpSize: arcFileHeader.unpSize,
            // hostOS: arcFileHeader.hostOS
            crc: arcFileHeader.crc,
            time: getDateString(arcFileHeader.time),
            unpVer: `${Math.floor(arcFileHeader.unpVer / 10)}.${arcFileHeader.unpVer % 10}`,
            method: getMethod(arcFileHeader.method),
            comment: arcFileHeader.comment,
            // // fileAttr: arcFileHeader.fileAttr,
        };
        const skip = shouldSkip(fileHeader);
        const fileState = this._archive.readFile(skip);
        if (fileState.errCode !== 0) {
            throw this.getFailException(fileState.errCode, fileState.errType, fileHeader.name);
        }
        return {
            fileHeader,
            extraction: skip ? 'skipped' : 'extracted',
        };
    }
    closeArc() {
        this._archive.delete();
        this._archive = null;
    }
    getFailException(errCode, _errType, file) {
        const reason = ERROR_CODE[errCode];
        this.closeArc();
        return new UnrarError(reason, ERROR_MSG[reason], file);
    }
}
//# sourceMappingURL=Extractor.js.map