"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const chalk_1 = require("chalk");
const express = require("express");
const lodash_1 = require("lodash");
const store_1 = require("./saves/store");
const errors_1 = require("./errors");
const error_1 = require("./metadata/error");
const evilsniffer_1 = require("./metadata/evilsniffer");
const profile_1 = require("./profile");
const bundler_1 = require("./publish/bundler");
const uploader_1 = require("./publish/uploader");
const util_1 = require("./util");
function route(handler) {
    return (req, res) => {
        Promise.resolve(handler(req, res))
            .then(data => {
            if (res.headersSent) {
                return;
            }
            if (!data) {
                res.status(204).send();
                return;
            }
            res.json(data);
        })
            .catch(err => {
            if (errors_1.isHttpableError(err)) {
                res.status(err.statusCode()).json({
                    name: err.constructor.name,
                    message: err.message,
                    stack: err.stack,
                    metadata: err.metadata ? err.metadata() : undefined,
                });
            }
            else {
                const contents = err.stack || err.message || String(err);
                console.error(chalk_1.default.red(`Error in ${req.method} ${req.path}:\n${contents}`));
                res.status(500).send(contents);
            }
        });
    };
}
/**
 * This file contains the parent dev server run using `miix serve`. This has
 * endpoints for saving environment/profile settings as well as making calls
 * out to the Mixer API on behalf of the logged in user.
 */
function createApp(project) {
    const app = express();
    app.use(require('body-parser').json());
    function requireAuth(fn) {
        return (req, res) => __awaiter(this, void 0, void 0, function* () {
            if (yield project.profile.hasAuthenticated()) {
                return fn(req, res);
            }
            res.status(401).send();
        });
    }
    app.use((_req, res, next) => {
        res.setHeader('Access-Control-Allow-Origin', '*');
        res.setHeader('Access-Control-Allow-Methods', '*');
        res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
        next();
    });
    app.get('/connect-participant', route(requireAuth((req) => __awaiter(this, void 0, void 0, function* () {
        const udata = yield project.profile.user();
        const joinRes = yield new util_1.Fetcher()
            .with(yield project.profile.tokens())
            .json('get', `/interactive/${Number(req.query.channelID) || udata.channel}`);
        if (joinRes.status === 404 || joinRes.status === 400) {
            throw new errors_1.NotInteractiveError(); // remap
        }
        if (joinRes.status !== 200) {
            throw new Error(`Unexpected status code ${joinRes.status} from ${joinRes.url}: ${yield joinRes.text()}`);
        }
        return yield joinRes.json();
    }))));
    let grantingCode;
    app.get('/login', route(() => __awaiter(this, void 0, void 0, function* () {
        if (yield project.profile.hasAuthenticated()) {
            grantingCode = undefined;
            return yield project.profile.user();
        }
        if (grantingCode) {
            return { code: grantingCode };
        }
        return new Promise(resolve => {
            project.profile
                .grant({
                prompt: code => {
                    grantingCode = code;
                    resolve(code);
                },
                isCancelled: () => grantingCode !== undefined,
            })
                .catch(err => {
                if (err instanceof profile_1.GrantCancelledError) {
                    grantingCode = undefined;
                }
                else {
                    throw err;
                }
            });
        }).then(code => ({ code }));
    })));
    app.get('/interactive-versions', route(requireAuth((_req, res) => __awaiter(this, void 0, void 0, function* () {
        const user = yield project.profile.user();
        const fetcher = new util_1.Fetcher().with(yield project.profile.tokens());
        let output = [];
        for (let page = 0; true; page++) {
            // tslint:disable-line
            const next = yield fetcher.json('get', `/interactive/games/owned?user=${user.id}&page=${page}`);
            const contents = yield next.json();
            if (!contents.length) {
                break;
            }
            output = output.concat(contents);
        }
        res.json(output);
    }))));
    app.get('/ensure-no-eval', route(() => __awaiter(this, void 0, void 0, function* () {
        try {
            yield new evilsniffer_1.EvilSniffer().compile(project.baseDir());
        }
        catch (e) {
            return { hasEval: e instanceof error_1.DeclarationError };
        }
        return { hasEval: false };
    })));
    app.get('/metadata', route(() => __awaiter(this, void 0, void 0, function* () { return project.packageConfig(); })));
    app.post('/modify-package', route((req) => __awaiter(this, void 0, void 0, function* () {
        const packageJson = yield project.packageJson();
        lodash_1.merge(packageJson, req.body);
        yield util_1.writeFile(project.baseDir('package.json'), JSON.stringify(packageJson, null, 2));
    })));
    app.post('/update-interactive-version/:versionId', route(requireAuth((req, res) => __awaiter(this, void 0, void 0, function* () {
        const body = Object.assign({}, req.body, { controlVersion: '2.0' });
        const updateRes = yield new util_1.Fetcher()
            .with(yield project.profile.tokens())
            .json('put', `/interactive/versions/${req.params.versionId}`, body);
        res.status(updateRes.status).json(yield updateRes.json());
    }))));
    app.post('/start-version-upload/:versionId', route(requireAuth((req) => __awaiter(this, void 0, void 0, function* () {
        const fetcher = new util_1.Fetcher().with(yield project.profile.tokens());
        const config = yield project.packageConfig();
        const output = yield new bundler_1.Bundler(project).bundle();
        yield new uploader_1.Uploader(fetcher, project).upload(output.filename);
        yield fetcher.json('put', `/interactive/versions/${req.params.versionId}`, {
            bundle: `${config.name}_${config.version}`,
        });
    }))));
    app.post('/saves', route((req) => __awaiter(this, void 0, void 0, function* () {
        const saves = new store_1.Saves(project);
        yield saves.append(req.body);
        return saves.list();
    })));
    app.get('/saves', route(() => __awaiter(this, void 0, void 0, function* () {
        return new store_1.Saves(project).list();
    })));
    app.get('/saves/latest', route((_req) => __awaiter(this, void 0, void 0, function* () {
        return {
            contents: yield new store_1.Saves(project).loadLatest(),
        };
    })));
    app.get('/saves/:saveId', route((req) => __awaiter(this, void 0, void 0, function* () {
        return {
            contents: yield new store_1.Saves(project).load(req.params.saveId),
        };
    })));
    return app;
}
exports.createApp = createApp;
