"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());
    });
};
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
const viewTemplate = require("./viewBuilder");
const { apiFileReturn } = require("../../../utilities/fileSystem");
const exporters = require("./exporters");
const { saveView, getView, getViews, deleteView } = require("./utils");
const { fetchView } = require("../row");
const { getTable } = require("../table/utils");
const { FieldTypes } = require("../../../constants");
const { getAppDB } = require("@devlego/backend-core/context");
const { events } = require("@devlego/backend-core");
const { DocumentTypes } = require("../../../db/utils");
const { cloneDeep, isEqual } = require("lodash");
exports.fetch = (ctx) => __awaiter(void 0, void 0, void 0, function* () {
    ctx.body = yield getViews();
});
exports.save = (ctx) => __awaiter(void 0, void 0, void 0, function* () {
    const db = getAppDB();
    const _a = ctx.request.body, { originalName } = _a, viewToSave = __rest(_a, ["originalName"]);
    const view = viewTemplate(viewToSave);
    const viewName = viewToSave.name;
    if (!viewName) {
        ctx.throw(400, "Cannot create view without a name");
    }
    yield saveView(originalName, viewName, view);
    // add views to table document
    const existingTable = yield db.get(ctx.request.body.tableId);
    const table = cloneDeep(existingTable);
    if (!table.views)
        table.views = {};
    if (!view.meta.schema) {
        view.meta.schema = table.schema;
    }
    table.views[viewName] = view.meta;
    if (originalName) {
        delete table.views[originalName];
        existingTable.views[viewName] = existingTable.views[originalName];
    }
    yield db.put(table);
    yield handleViewEvents(existingTable.views[viewName], table.views[viewName]);
    ctx.body = Object.assign(Object.assign({}, table.views[viewToSave.name]), { name: viewToSave.name });
});
const calculationEvents = (existingView, newView) => __awaiter(void 0, void 0, void 0, function* () {
    const existingCalculation = existingView && existingView.calculation;
    const newCalculation = newView && newView.calculation;
    if (existingCalculation && !newCalculation) {
        yield events.view.calculationDeleted(existingView);
    }
    if (!existingCalculation && newCalculation) {
        yield events.view.calculationCreated(newView);
    }
    if (existingCalculation &&
        newCalculation &&
        existingCalculation !== newCalculation) {
        yield events.view.calculationUpdated(newView);
    }
});
const filterEvents = (existingView, newView) => __awaiter(void 0, void 0, void 0, function* () {
    const hasExistingFilters = !!(existingView &&
        existingView.filters &&
        existingView.filters.length);
    const hasNewFilters = !!(newView && newView.filters && newView.filters.length);
    if (hasExistingFilters && !hasNewFilters) {
        yield events.view.filterDeleted(newView);
    }
    if (!hasExistingFilters && hasNewFilters) {
        yield events.view.filterCreated(newView);
    }
    if (hasExistingFilters &&
        hasNewFilters &&
        !isEqual(existingView.filters, newView.filters)) {
        yield events.view.filterUpdated(newView);
    }
});
const handleViewEvents = (existingView, newView) => __awaiter(void 0, void 0, void 0, function* () {
    if (!existingView) {
        yield events.view.created(newView);
    }
    else {
        yield events.view.updated(newView);
    }
    yield calculationEvents(existingView, newView);
    yield filterEvents(existingView, newView);
});
exports.destroy = (ctx) => __awaiter(void 0, void 0, void 0, function* () {
    const db = getAppDB();
    const viewName = decodeURI(ctx.params.viewName);
    const view = yield deleteView(viewName);
    const table = yield db.get(view.meta.tableId);
    delete table.views[viewName];
    yield db.put(table);
    yield events.view.deleted(view);
    ctx.body = view;
});
exports.exportView = (ctx) => __awaiter(void 0, void 0, void 0, function* () {
    const viewName = decodeURI(ctx.query.view);
    const view = yield getView(viewName);
    const format = ctx.query.format;
    if (!format || !Object.values(exporters.ExportFormats).includes(format)) {
        ctx.throw(400, "Format must be specified, either csv or json");
    }
    if (view) {
        ctx.params.viewName = viewName;
        // Fetch view rows
        ctx.query = {
            group: view.meta.groupBy,
            calculation: view.meta.calculation,
            stats: !!view.meta.field,
            field: view.meta.field,
        };
    }
    else {
        // table all_ view
        /* istanbul ignore next */
        ctx.params.viewName = viewName;
    }
    yield fetchView(ctx);
    let rows = ctx.body;
    let schema = view && view.meta && view.meta.schema;
    const tableId = ctx.params.tableId || view.meta.tableId;
    const table = yield getTable(tableId);
    if (!schema) {
        schema = table.schema;
    }
    // remove any relationships
    const relationships = Object.entries(schema)
        .filter(entry => entry[1].type === FieldTypes.LINK)
        .map(entry => entry[0]);
    // iterate relationship columns and remove from and row and schema
    relationships.forEach(column => {
        rows.forEach(row => {
            delete row[column];
        });
        delete schema[column];
    });
    // make sure no "undefined" entries appear in the CSV
    if (format === exporters.ExportFormats.CSV) {
        const schemaKeys = Object.keys(schema);
        for (let key of schemaKeys) {
            for (let row of rows) {
                if (row[key] == null) {
                    row[key] = "";
                }
            }
        }
    }
    // Export part
    let headers = Object.keys(schema);
    const exporter = exporters[format];
    const filename = `${viewName}.${format}`;
    // send down the file
    ctx.attachment(filename);
    ctx.body = apiFileReturn(exporter(headers, rows));
    if (viewName.startsWith(DocumentTypes.TABLE)) {
        yield events.table.exported(table, format);
    }
    else {
        yield events.view.exported(table, format);
    }
});
