"use strict";
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkDependencies = checkDependencies;
const plugins_1 = require("./plugins");
const lodash_1 = __importDefault(require("lodash"));
const misc_1 = require("./misc");
const apiMiddleware_1 = require("./apiMiddleware");
const promises_1 = require("fs/promises");
const github_1 = require("./github");
const const_1 = require("./const");
const SendList_1 = require("./SendList");
const apis = {
    get_plugins({}, ctx) {
        const list = new SendList_1.SendListReadable({ addAtStart: [...(0, plugins_1.mapPlugins)(serialize, false), ...(0, plugins_1.getInactivePlugins)().map(serialize)] });
        return list.events(ctx, {
            pluginInstalled: p => list.add(serialize(p)),
            'pluginStarted pluginStopped pluginUpdated': p => {
                const { id, ...rest } = serialize(p);
                list.update({ id }, rest);
            },
            pluginUninstalled: id => list.remove({ id }),
            pluginLog: id => list.update({ id }, { log: true }) // SendList is already capping frequency
        });
    },
    async get_plugin_updates({}, ctx) {
        return new SendList_1.SendListReadable({
            async doAtStart(list) {
                const errs = {};
                list.events(ctx, {
                    pluginDownload({ repo, status }) {
                        list.update({ id: (0, plugins_1.findPluginByRepo)(repo)?.id }, { downloading: status ?? null });
                    },
                    pluginDownloaded({ id }) {
                        list.update({ id }, { updated: true });
                    }
                });
                await plugins_1.pluginsScanned;
                await Promise.allSettled(lodash_1.default.map((0, github_1.getFolder2repo)(), async (repo, folder) => {
                    try {
                        if (!repo)
                            return;
                        const online = await (0, github_1.readOnlineCompatiblePlugin)(repo);
                        if (!online)
                            return;
                        const disk = (0, plugins_1.getPluginInfo)(folder);
                        if (!disk)
                            return; // plugin removed in the meantime?
                        if (online.version === disk.version)
                            return; // different, not just newer ones, in case a version was retired
                        list.add(Object.assign(online, {
                            id: disk.id, // id is installation-dependant, and online cannot know
                            installedVersion: disk.version,
                            repo: serialize(disk).repo, // show the user the current repo we are getting this update from, not a possibly-changed future one
                            downgrade: online.version < disk.version,
                            downloading: lodash_1.default.isString(online.repo) && github_1.downloading[online.repo],
                        }));
                    }
                    catch (err) {
                        if (err.message !== '404') // the plugin is declaring a wrong repo
                            (errs[err.code || err.message] ||= []).push(repo);
                    }
                }));
                if (!lodash_1.default.isEmpty(errs))
                    list.error(errs);
                list.ready();
            }
        });
    },
    async start_plugin({ id }) {
        assertPluginId(id);
        if ((0, plugins_1.isPluginRunning)(id))
            return { msg: 'already running' };
        if (plugins_1.suspendPlugins.get())
            return new apiMiddleware_1.ApiError(misc_1.HTTP_PRECONDITION_FAILED, 'all plugins suspended');
        await (0, plugins_1.stopPlugin)(id);
        return (0, plugins_1.startPlugin)(id).then(() => ({}), e => new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, e.message));
    },
    async stop_plugin({ id }) {
        assertPluginId(id);
        if (!(0, plugins_1.isPluginRunning)(id))
            return { msg: 'already stopped' };
        await (0, plugins_1.stopPlugin)(id);
        return {};
    },
    async set_plugin({ id, enabled, config }) {
        assertPluginId(id);
        if (config)
            (0, plugins_1.setPluginConfig)(id, config);
        if (enabled !== undefined)
            (0, plugins_1.enablePlugin)(id, enabled);
        return {};
    },
    async get_plugin({ id }) {
        assertPluginId(id);
        return {
            enabled: plugins_1.enablePlugins.get().includes(id),
            config: {
                ...(0, misc_1.newObj)((0, plugins_1.getPluginConfigFields)(id), v => v?.defaultValue),
                ...plugins_1.pluginsConfig.get()[id]
            }
        };
    },
    get_online_plugins({ text }, ctx) {
        if (text !== undefined && !lodash_1.default.isString(text))
            return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad text');
        return new SendList_1.SendListReadable({
            async doAtStart(list) {
                const repos = [];
                list.events(ctx, {
                    pluginInstalled: p => {
                        if (repos.includes(p.repo))
                            list.update({ id: p.repo }, { installed: true });
                    },
                    pluginUninstalled: (_folder, repo) => {
                        if (typeof repo !== 'string')
                            return; // custom repo
                        if (repos.includes(repo))
                            list.update({ id: repo }, { installed: false });
                    },
                    pluginDownload({ repo, status }) {
                        if (repos.includes(repo))
                            list.update({ id: repo }, { downloading: status ?? null });
                    }
                });
                try {
                    const already = Object.values((0, github_1.getFolder2repo)()).filter(Boolean).map(String);
                    for await (const pl of await (0, github_1.searchPlugins)(text, { skipRepos: already })) {
                        if (ctx.isAborted())
                            return;
                        const repo = pl.repo || pl.id; // .repo property can be more trustworthy in case github user renamed and left the previous link in 'repo'
                        const missing = (0, plugins_1.getMissingDependencies)(pl);
                        if (missing.length)
                            pl.missing = missing;
                        list.add(pl);
                        repos.push(repo);
                    }
                }
                catch (err) {
                    list.error(err.code || err.message);
                }
                list.ready();
            }
        });
    },
    async download_plugin({ id, branch, stop }) {
        assertPluginId(id);
        await checkDependencies(await (0, github_1.readOnlinePlugin)(id, branch));
        const folder = await (0, github_1.downloadPlugin)(id, { branch });
        if (stop) // be sure this is not automatically started
            await (0, plugins_1.stopPlugin)(folder);
        return (await (0, misc_1.waitFor)(() => (0, plugins_1.getPluginInfo)(folder), { timeout: 5000 }))
            || new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
    },
    async update_plugin({ id }) {
        assertPluginId(id);
        const found = (0, plugins_1.getPluginInfo)(id);
        if (!found)
            return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
        const online = await (0, github_1.readOnlineCompatiblePlugin)(found.repo); // branch returned by readOnlineCompatiblePlugin is possibly fresher, so we use that
        if (!online)
            return new apiMiddleware_1.ApiError(misc_1.HTTP_CONFLICT);
        await checkDependencies(online);
        await (0, github_1.downloadPlugin)(found.repo, { branch: online.branch, overwrite: true });
        return {};
    },
    async uninstall_plugin({ id, deleteConfig }) {
        assertPluginId(id);
        await (0, plugins_1.stopPlugin)(id);
        await (0, promises_1.rm)(plugins_1.PATH + '/' + id, { recursive: true, force: true });
        if (deleteConfig)
            (0, plugins_1.setPluginConfig)(id, null);
        return {};
    },
    get_plugin_log({ id }, ctx) {
        assertPluginId(id);
        const p = (0, plugins_1.getPluginInfo)(id);
        if (!p)
            return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
        const list = new SendList_1.SendListReadable({ addAtStart: p.log });
        return list.events(ctx, {
            ['pluginLog:' + id]: x => list.add(x)
        });
    },
};
exports.default = apis;
function assertPluginId(id) {
    (0, misc_1.apiAssertTypes)({ string: { id } });
}
function serialize(p) {
    let o = 'getData' in p ? Object.assign(lodash_1.default.pick(p, ['id', 'started']), p.getData())
        : { ...p }; // _.defaults mutates object, and we don't want that
    if (typeof o.repo === 'object') // custom repo
        o.repo = o.repo.web;
    o.log = 'log' in p && p.log?.length > 0;
    o.config &&= lodash_1.default.isFunction(o.config) ? String(o.config)
        : JSON.stringify(o.config, (_k, v) => lodash_1.default.isFunction(v) ? String(v) : v); // allow simple functions
    return lodash_1.default.defaults(o, { started: null, badApi: null }); // nulls should be used to be sure to overwrite previous values,
}
async function checkDependencies(plugin) {
    const miss = await (0, plugins_1.getMissingDependencies)(plugin);
    if (miss.length)
        throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, miss);
}
