"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
const { APP_DEV_PREFIX } = require("../db/utils");
const { doesUserHaveLock, updateLock, checkDebounce, setDebounce, } = require("../utilities/redis");
const { doWithDB } = require("@devlego/backend-core/db");
const { DocumentTypes, getGlobalIDFromUserMetadataID } = require("../db/utils");
const { PermissionTypes } = require("@devlego/backend-core/permissions");
const { app: appCache } = require("@devlego/backend-core/cache");
const DEBOUNCE_TIME_SEC = 30;
/************************************************** *
 * This middleware has been broken out of the       *
 * "authorized" middleware as it had nothing to do  *
 * with authorization, but requires the perms       *
 * imparted by it. This middleware shouldn't        *
 * be called directly, it should always be called   *
 * through the authorized middleware                *
 ****************************************************/
function checkDevAppLocks(ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        const appId = ctx.appId;
        // if any public usage, don't proceed
        if (!ctx.user._id && !ctx.user.userId) {
            return;
        }
        // not a development app, don't need to do anything
        if (!appId || !appId.startsWith(APP_DEV_PREFIX)) {
            return;
        }
        if (!(yield doesUserHaveLock(appId, ctx.user))) {
            ctx.throw(400, "User does not hold app lock.");
        }
        // they do have lock, update it
        yield updateLock(appId, ctx.user);
    });
}
function updateAppUpdatedAt(ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        const appId = ctx.appId;
        // if debouncing skip this update
        // get methods also aren't updating
        if (ctx.method === "GET" || (yield checkDebounce(appId))) {
            return;
        }
        yield doWithDB(appId, (db) => __awaiter(this, void 0, void 0, function* () {
            const metadata = yield db.get(DocumentTypes.APP_METADATA);
            metadata.updatedAt = new Date().toISOString();
            metadata.updatedBy = getGlobalIDFromUserMetadataID(ctx.user.userId);
            const response = yield db.put(metadata);
            metadata._rev = response.rev;
            yield appCache.invalidateAppMetadata(appId, metadata);
            // set a new debounce record with a short TTL
            yield setDebounce(appId, DEBOUNCE_TIME_SEC);
        }));
    });
}
module.exports = (ctx, permType) => __awaiter(void 0, void 0, void 0, function* () {
    const appId = ctx.appId;
    // this only functions within an app context
    if (!appId) {
        return;
    }
    const isBuilderApi = permType === PermissionTypes.BUILDER;
    const referer = ctx.headers["referer"];
    const overviewPath = "/builder/portal/overview/";
    const overviewContext = !referer ? false : referer.includes(overviewPath);
    if (overviewContext) {
        return;
    }
    const hasAppId = !referer ? false : referer.includes(appId);
    const editingApp = referer ? hasAppId : false;
    // check this is a builder call and editing
    if (!isBuilderApi || !editingApp) {
        return;
    }
    // check locks
    yield checkDevAppLocks(ctx);
    // set updated at time on app
    yield updateAppUpdatedAt(ctx);
});
