"use strict";
/**
 * Process a given payload for a collection to ensure the special fields (hash, uuid, date etc) are
 * handled correctly.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
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 __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.processO2M = exports.processM2O = exports.processValues = void 0;
const argon2_1 = __importDefault(require("argon2"));
const uuid_1 = require("uuid");
const database_1 = __importDefault(require("../database"));
const lodash_1 = require("lodash");
const ItemsService = __importStar(require("./items"));
const url_1 = require("url");
/**
 * @todo allow this to be extended
 *
 * @todo allow these extended special types to have "field dependencies"?
 * f.e. the file-links transformer needs the id and filename_download to be fetched from the DB
 * in order to work
 */
const transformers = {
    hash(operation, value) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!value)
                return;
            if (operation === 'create' || operation === 'update') {
                return yield argon2_1.default.hash(String(value));
            }
            return value;
        });
    },
    uuid(operation, value) {
        return __awaiter(this, void 0, void 0, function* () {
            if (operation === 'create' && !value) {
                return uuid_1.v4();
            }
            return value;
        });
    },
    'file-links'(operation, value, payload) {
        return __awaiter(this, void 0, void 0, function* () {
            if (operation === 'read' && payload && payload.storage && payload.filename_disk) {
                const publicKey = `STORAGE_${payload.storage.toUpperCase()}_PUBLIC_URL`;
                return {
                    asset_url: new url_1.URL(`/assets/${payload.id}`, process.env.PUBLIC_URL),
                    public_url: new url_1.URL(payload.filename_disk, process.env[publicKey]),
                };
            }
            // This is an non-existing column, so there isn't any data to save
            return undefined;
        });
    },
};
function processValues(operation, collection, payload) {
    return __awaiter(this, void 0, void 0, function* () {
        const processedPayload = Array.isArray(payload) ? lodash_1.clone(payload) : [lodash_1.clone(payload)];
        const specialFieldsInCollection = yield database_1.default
            .select('field', 'special')
            .from('directus_fields')
            .where({ collection: collection })
            .whereNotNull('special');
        yield Promise.all(processedPayload.map((record) => __awaiter(this, void 0, void 0, function* () {
            yield Promise.all(specialFieldsInCollection.map((field) => __awaiter(this, void 0, void 0, function* () {
                record[field.field] = yield processField(field, record, operation);
            })));
        })));
        if (['create', 'update'].includes(operation)) {
            processedPayload.forEach((record) => {
                for (const [key, value] of Object.entries(record)) {
                    if (Array.isArray(value) || (typeof value === 'object' && value !== null)) {
                        record[key] = JSON.stringify(value);
                    }
                }
            });
        }
        if (Array.isArray(payload)) {
            return processedPayload;
        }
        return processedPayload[0];
    });
}
exports.processValues = processValues;
function processField(field, payload, operation) {
    return __awaiter(this, void 0, void 0, function* () {
        if (!field.special)
            return payload[field.field];
        if (transformers.hasOwnProperty(field.special)) {
            return yield transformers[field.special](operation, payload[field.field], payload);
        }
        return payload[field.field];
    });
}
/**
 * Recursively checks for nested relational items, and saves them bottom up, to ensure we have IDs etc ready
 */
exports.processM2O = (collection, payload) => __awaiter(void 0, void 0, void 0, function* () {
    const payloadClone = lodash_1.clone(payload);
    const relations = yield database_1.default
        .select('*')
        .from('directus_relations')
        .where({ collection_many: collection });
    // Only process related records that are actually in the payload
    const relationsToProcess = relations.filter((relation) => {
        return (payloadClone.hasOwnProperty(relation.field_many) &&
            lodash_1.isObject(payloadClone[relation.field_many]));
    });
    // Save all nested m2o records
    yield Promise.all(relationsToProcess.map((relation) => __awaiter(void 0, void 0, void 0, function* () {
        const relatedRecord = payloadClone[relation.field_many];
        const hasPrimaryKey = relatedRecord.hasOwnProperty(relation.primary_one);
        let relatedPrimaryKey;
        if (hasPrimaryKey) {
            relatedPrimaryKey = relatedRecord[relation.primary_one];
            yield ItemsService.updateItem(relation.collection_one, relatedPrimaryKey, relatedRecord);
        }
        else {
            relatedPrimaryKey = yield ItemsService.createItem(relation.collection_one, relatedRecord);
        }
        // Overwrite the nested object with just the primary key, so the parent level can be saved correctly
        payloadClone[relation.field_many] = relatedPrimaryKey;
    })));
    return payloadClone;
});
exports.processO2M = (collection, payload) => __awaiter(void 0, void 0, void 0, function* () {
    const payloadClone = lodash_1.clone(payload);
    const relations = yield database_1.default
        .select('*')
        .from('directus_relations')
        .where({ collection_one: collection });
    // Only process related records that are actually in the payload
    const relationsToProcess = relations.filter((relation) => {
        return (payloadClone.hasOwnProperty(relation.field_one) &&
            Array.isArray(payloadClone[relation.field_one]));
    });
    // Save all nested o2m records
    yield Promise.all(relationsToProcess.map((relation) => __awaiter(void 0, void 0, void 0, function* () {
        const relatedRecords = payloadClone[relation.field_one];
        yield Promise.all(relatedRecords.map((relatedRecord, index) => __awaiter(void 0, void 0, void 0, function* () {
            relatedRecord[relation.field_many] = payloadClone[relation.primary_one];
            const hasPrimaryKey = relatedRecord.hasOwnProperty(relation.primary_many);
            let relatedPrimaryKey;
            if (hasPrimaryKey) {
                relatedPrimaryKey = relatedRecord[relation.primary_many];
                yield ItemsService.updateItem(relation.collection_many, relatedPrimaryKey, relatedRecord);
            }
            else {
                relatedPrimaryKey = yield ItemsService.createItem(relation.collection_many, relatedRecord);
            }
            relatedRecord[relation.primary_many] = relatedPrimaryKey;
            payloadClone[relation.field_one][index] = relatedRecord;
        })));
    })));
    return payloadClone;
});
