"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const JoplinError_1 = require("./JoplinError");
const path_utils_1 = require("./path-utils");
// All input paths should be in the format: "path/to/file". This is converted to
// "root:/path/to/file:" when doing the API call.
class FileApiDriverJoplinServer {
    constructor(api) {
        this.api_ = api;
    }
    async initialize(basePath) {
        const pieces = (0, path_utils_1.trimSlashes)(basePath).split('/');
        if (!pieces.length)
            return;
        const parent = [];
        for (let i = 0; i < pieces.length; i++) {
            const p = pieces[i];
            const subPath = parent.concat(p).join('/');
            parent.push(p);
            await this.mkdir(subPath);
        }
    }
    api() {
        return this.api_;
    }
    get supportsMultiPut() {
        return true;
    }
    get supportsAccurateTimestamp() {
        return true;
    }
    get supportsLocks() {
        return true;
    }
    requestRepeatCount() {
        return 3;
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    metadataToStat_(md, path, isDeleted = false, rootPath) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
        const output = {
            path: rootPath ? path.substr(rootPath.length + 1) : path,
            updated_time: md.updated_time,
            jop_updated_time: md.jop_updated_time,
            isDir: false,
            isDeleted: isDeleted,
        };
        // Only add this object is it's also present in the raw data. This is
        // because `getSupportsDeltaWithItems()` relies on it being present or
        // not to decide if the sync target supports "delta with items".
        if ('jopItem' in md)
            output.jopItem = md.jopItem;
        return output;
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    metadataToStats_(mds, rootPath) {
        const output = [];
        for (let i = 0; i < mds.length; i++) {
            output.push(this.metadataToStat_(mds[i], mds[i].name, false, rootPath));
        }
        return output;
    }
    // Transforms a path such as "Apps/Joplin/file.txt" to a complete a complete
    // API URL path: "api/items/root:/Apps/Joplin/file.txt:"
    apiFilePath_(p) {
        return `api/items/root:/${(0, path_utils_1.trimSlashes)(p)}:`;
    }
    async stat(path) {
        try {
            const response = await this.api().exec('GET', this.apiFilePath_(path));
            return this.metadataToStat_(response, path, false, '');
        }
        catch (error) {
            if (error.code === 404)
                return null;
            throw error;
        }
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    async delta(path, options) {
        const context = options ? options.context : null;
        let cursor = context ? context.cursor : null;
        while (true) {
            try {
                const query = cursor ? { cursor } : {};
                const response = await this.api().exec('GET', `${this.apiFilePath_(path)}/delta`, query);
                const stats = response.items
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
                    .filter((item) => {
                    // We don't need to know about lock changes, since this
                    // is handled by the LockHandler.
                    if (item.item_name.indexOf('locks/') === 0)
                        return false;
                    // We don't need to sync what's in the temp folder
                    if (item.item_name.indexOf('temp/') === 0)
                        return false;
                    // Although we sync the content of .resource, whether we
                    // fetch or upload data to it is driven by the
                    // associated resource item (.md) file. So at this point
                    // we don't want to automatically fetch from it.
                    if (item.item_name.indexOf('.resource/') === 0)
                        return false;
                    return true;
                })
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
                    .map((item) => {
                    return this.metadataToStat_(item, item.item_name, item.type === 3, '');
                });
                const output = {
                    items: stats,
                    hasMore: response.has_more,
                    context: { cursor: response.cursor },
                };
                return output;
            }
            catch (error) {
                // If there's an error related to an invalid cursor, clear the cursor and retry.
                if (cursor && error.code === 'resyncRequired') {
                    cursor = null;
                    continue;
                }
                throw error;
            }
        }
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    async list(path, options = null) {
        var _a;
        options = Object.assign({ context: null }, options);
        let isUsingWildcard = false;
        let searchPath = path;
        if (searchPath) {
            searchPath += '/*';
            isUsingWildcard = true;
        }
        const query = ((_a = options.context) === null || _a === void 0 ? void 0 : _a.cursor) ? { cursor: options.context.cursor } : null;
        const results = await this.api().exec('GET', `${this.apiFilePath_(searchPath)}/children`, query);
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
        const newContext = {};
        if (results.cursor)
            newContext.cursor = results.cursor;
        return {
            items: this.metadataToStats_(results.items, isUsingWildcard ? path : ''),
            hasMore: results.has_more,
            context: newContext,
            // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
        };
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    async get(path, options) {
        if (!options)
            options = {};
        if (!options.responseFormat)
            options.responseFormat = 'text';
        try {
            const response = await this.api().exec('GET', `${this.apiFilePath_(path)}/content`, null, null, null, options);
            return response;
        }
        catch (error) {
            if (error.code !== 404)
                throw error;
            return null;
        }
    }
    async mkdir(_path) {
        // This is a no-op because all items technically are at the root, but
        // they can have names such as ".resources/xxxxxxxxxx'
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    isRejectedBySyncTargetError(error) {
        return error.code === 413 || error.code === 409 || error.httpCode === 413 || error.httpCode === 409;
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    isReadyOnlyError(error) {
        return error && error.code === 'isReadOnly';
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    async put(path, content, options = null) {
        try {
            const output = await this.api().exec('PUT', `${this.apiFilePath_(path)}/content`, options && options.shareId ? { share_id: options.shareId } : null, content, {
                'Content-Type': 'application/octet-stream',
            }, options);
            return output;
        }
        catch (error) {
            if (this.isRejectedBySyncTargetError(error)) {
                throw new JoplinError_1.default(error.message, 'rejectedByTarget');
            }
            if (this.isReadyOnlyError(error)) {
                throw new JoplinError_1.default(error.message, 'isReadOnly');
            }
            throw error;
        }
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    async multiPut(items, options = null) {
        const output = await this.api().exec('PUT', 'api/batch_items', null, { items: items }, null, options);
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
        for (const [, response] of Object.entries(output.items)) {
            if (response.error && this.isRejectedBySyncTargetError(response.error)) {
                response.error.code = 'rejectedByTarget';
            }
            else if (response.error && this.isReadyOnlyError(response.error)) {
                response.error.code = 'isReadOnly';
            }
        }
        return output;
    }
    async delete(path) {
        return this.api().exec('DELETE', this.apiFilePath_(path));
    }
    format() {
        throw new Error('Not supported');
    }
    // private lockClientTypeToId(clientType:AppType):number {
    // 	if (clientType === AppType.Desktop) return 1;
    // 	if (clientType === AppType.Mobile) return 2;
    // 	if (clientType === AppType.Cli) return 3;
    // 	throw new Error('Invalid client type: ' + clientType);
    // }
    // private lockTypeToId(lockType:LockType):number {
    // 	if (lockType === LockType.None) return 0; // probably not possible?
    // 	if (lockType === LockType.Sync) return 1;
    // 	if (lockType === LockType.Exclusive) return 2;
    // 	throw new Error('Invalid lock type: ' + lockType);
    // }
    // private lockClientIdTypeToType(clientType:number):AppType {
    // 	if (clientType === 1) return AppType.Desktop;
    // 	if (clientType === 2) return AppType.Mobile;
    // 	if (clientType === 3) return AppType.Cli;
    // 	throw new Error('Invalid client type: ' + clientType);
    // }
    // private lockIdToType(lockType:number):LockType {
    // 	if (lockType === 0) return LockType.None; // probably not possible?
    // 	if (lockType === 1) return LockType.Sync;
    // 	if (lockType === 2) return LockType.Exclusive;
    // 	throw new Error('Invalid lock type: ' + lockType);
    // }
    async acquireLock(type, clientType, clientId) {
        return this.api().exec('POST', 'api/locks', null, {
            type,
            clientType,
            clientId: clientId,
        });
    }
    async releaseLock(type, clientType, clientId) {
        await this.api().exec('DELETE', `api/locks/${type}_${clientType}_${clientId}`);
    }
    async listLocks() {
        return this.api().exec('GET', 'api/locks');
    }
    async clearRoot(path) {
        const response = await this.list(path);
        for (const item of response.items) {
            await this.delete(item.path);
        }
        await this.api().exec('POST', 'api/debug', null, { action: 'clearKeyValues' });
        if (response.has_more)
            throw new Error('has_more support not implemented');
    }
}
exports.default = FileApiDriverJoplinServer;
//# sourceMappingURL=file-api-driver-joplinServer.js.map