import _ from 'lodash';
import { AKAPIException } from '../../exceptions/api_exception.js';
import { PG_CODE_UNIQUENESS_VIOLATION } from '../../exceptions/http_exception_handler.js';
import { AKModelService } from './index.js';
export const AKModelServiceUpdate = (superclass) => {
    return class extends superclass {
        static updateUniquenessViolationException = AKAPIException.alreadyExist;
        async preUpdate(_item, payload) {
            return payload;
        }
        async update(id, payload) {
            const item = await this.findById(id);
            if (!item)
                throw AKAPIException.doesNotExist;
            const previousItemSerialized = item.serialize();
            const preUpdatedPayload = await this.preUpdate(item, payload);
            try {
                await item.merge(preUpdatedPayload).save();
            }
            catch (e) {
                if (e.code === PG_CODE_UNIQUENESS_VIOLATION) {
                    throw this.constructor.updateUniquenessViolationException;
                }
                else
                    throw e;
            }
            await this.updateRelations(item, preUpdatedPayload);
            const fetchedUpdated = (await this.findById(id));
            return await this.postUpdate(fetchedUpdated, preUpdatedPayload, previousItemSerialized);
        }
        async postUpdate(item, _payload, _previousItemSerialized) {
            return item;
        }
        async updateRelations(item, payload) {
            for (const relation of this.model.$relationsDefinitions.entries()) {
                const [relationKey, relationDef] = relation;
                let relatedPayload = payload[relationKey];
                if (relatedPayload === undefined)
                    continue;
                const relationOption = _.find(this.constructor.enabledRelations, (relation) => relation.name === relationKey);
                await relationOption?.makeService();
                const relatedService = relationOption?.service || new (AKModelService(relationDef.relatedModel()))();
                switch (relationDef.type) {
                    case 'hasOne':
                    case 'belongsTo':
                        await this.#updateSingleRelation(item, relationKey, relationDef, relatedService, relatedPayload);
                        break;
                    case 'hasMany':
                    case 'manyToMany':
                        await this.#updateManyRelation(item, relationKey, relatedService, relatedPayload);
                        break;
                    case 'hasManyThrough':
                }
            }
        }
        async #updateSingleRelation(item, relationKey, relationDef, relatedService, relatedPayload) {
            if ((_.isObject(relatedPayload) && _.isEmpty(relatedPayload)) ||
                (!_.isObject(relatedPayload) && !_.isNull(relatedPayload)))
                return;
            if (relationDef.type !== 'belongsTo' || item[relationDef.foreignKey])
                await item.load(relationKey);
            let related = item[relationKey];
            if (relatedPayload === null) {
                if (related)
                    await item.related(relationKey).dissociate();
            }
            else {
                let changed = false;
                for (const relatedPk of relatedService.model.keys) {
                    const relatedPkValue = relatedPayload[relatedPk];
                    if (relatedPkValue && (!related || related[relatedPk] !== relatedPkValue)) {
                        related = await relatedService.findBy(relatedPk, relatedPkValue);
                        if (related) {
                            await item.related(relationKey).associate(related);
                            await related.merge(_.omit(relatedPayload, relatedService.model.keys)).save();
                            changed = true;
                        }
                    }
                }
                if (!changed) {
                    if (!related && _.some(relatedService.model.keys, (k) => relatedPayload[k])) {
                        related = await relatedService.create(relatedPayload);
                        await item.related(relationKey).associate(related);
                    }
                    else if (!_.isNil(related)) {
                        const updateData = _.omit(relatedPayload, relatedService.model.keys);
                        await related.merge(updateData).save();
                    }
                }
            }
        }
        async #updateManyRelation(item, relationKey, relatedService, relatedPayload) {
            if (relatedPayload === null)
                relatedPayload = [];
            if (!_.isArray(relatedPayload))
                return;
            const relatedPayloadIds = await Promise.all(_.map(relatedPayload, async (p) => {
                const item = await relatedService.findOrCreate(p);
                return item[relatedService.model.primaryKey];
            }));
            await item.related(relationKey).sync(_.uniq(relatedPayloadIds));
        }
    };
};
//# sourceMappingURL=update.js.map