"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.mediaItemRepository = void 0;

var _lodash = _interopRequireDefault(require("lodash"));

var _repository = require("./repository");

var _items = require("../knex/queries/items");

var _dbconfig = require("../dbconfig");

var _details = require("../knex/queries/details");

var _tvepisode = require("../entity/tvepisode");

var _season = require("./season");

var _episode = require("./episode");

var _mediaItem = require("../entity/mediaItem");

var _image = require("./image");

var _image2 = require("../entity/image");

var _dateFns = require("date-fns");

var _slug = require("../slug");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

class MediaItemRepository extends (0, _repository.repository)({
  tableName: 'mediaItem',
  columnNames: _mediaItem.mediaItemColumns,
  primaryColumnName: 'id',
  booleanColumnNames: ['needsDetails']
}) {
  items(args) {
    return (0, _items.getItemsKnex)(args);
  }

  async details(params) {
    return (0, _details.getDetailsKnex)(params);
  }

  deserialize(value) {
    var _value$genres, _value$narrators, _value$authors;

    return super.deserialize({ ...value,
      genres: ((_value$genres = value.genres) === null || _value$genres === void 0 ? void 0 : _value$genres.split(',')) || null,
      narrators: ((_value$narrators = value.narrators) === null || _value$narrators === void 0 ? void 0 : _value$narrators.split(',')) || null,
      authors: ((_value$authors = value.authors) === null || _value$authors === void 0 ? void 0 : _value$authors.split(',')) || null,
      platform: value.platform ? JSON.parse(value.platform) : null
    });
  }

  serialize(value) {
    var _value$genres2, _value$authors2, _value$narrators2;

    return super.serialize({ ...value,
      genres: (_value$genres2 = value.genres) === null || _value$genres2 === void 0 ? void 0 : _value$genres2.join(','),
      authors: (_value$authors2 = value.authors) === null || _value$authors2 === void 0 ? void 0 : _value$authors2.join(','),
      narrators: (_value$narrators2 = value.narrators) === null || _value$narrators2 === void 0 ? void 0 : _value$narrators2.join(','),
      platform: value.platform ? JSON.stringify(value.platform) : null
    });
  }

  async delete(where) {
    const mediaItemId = where.id;
    return await _dbconfig.Database.knex.transaction(async trx => {
      await trx('image').delete().where('mediaItemId', mediaItemId);
      await trx('notificationsHistory').delete().where('mediaItemId', mediaItemId);
      await trx('episode').delete().where('tvShowId', mediaItemId);
      await trx('season').delete().where('tvShowId', mediaItemId);
      return await trx(this.tableName).delete().where('id', mediaItemId);
    });
  }

  async update(mediaItem) {
    if (!mediaItem.id) {
      throw new Error('mediaItem.id filed is required');
    }

    const slug = (0, _mediaItem.mediaItemSlug)(mediaItem);
    return await _dbconfig.Database.knex.transaction(async trx => {
      const result = { ..._lodash.default.cloneDeep(mediaItem),
        lastTimeUpdated: mediaItem.lastTimeUpdated ? mediaItem.lastTimeUpdated : new Date().getTime()
      };
      await trx(this.tableName).update({ ...this.serialize(this.stripValue(mediaItem)),
        slug: _dbconfig.Database.knex.raw(`(CASE 
              WHEN (
                ${_dbconfig.Database.knex('mediaItem').count().where('slug', slug).whereNot('id', mediaItem.id).toQuery()}) = 0 
                THEN '${slug}' 
              ELSE '${slug}-${(0, _slug.randomSlugId)()}' 
            END)`)
      }).where({
        id: mediaItem.id
      });

      if (!mediaItem.poster) {
        await trx(_image.imageRepository.tableName).delete().where({
          mediaItemId: mediaItem.id,
          seasonId: null,
          type: 'poster'
        });
      } else {
        const res = await trx(_image.imageRepository.tableName).where({
          mediaItemId: mediaItem.id,
          seasonId: null,
          type: 'poster'
        });

        if (res.length === 0) {
          const posterId = (0, _image2.getImageId)();
          await trx(_image.imageRepository.tableName).insert({
            id: (0, _image2.getImageId)(),
            mediaItemId: mediaItem.id,
            seasonId: null,
            type: 'poster'
          });
          result.poster = `/img/${posterId}`;
        }
      }

      if (!mediaItem.backdrop) {
        await trx(_image.imageRepository.tableName).delete().where({
          mediaItemId: mediaItem.id,
          seasonId: null,
          type: 'backdrop'
        });
      } else {
        const res = await trx(_image.imageRepository.tableName).where({
          mediaItemId: mediaItem.id,
          seasonId: null,
          type: 'backdrop'
        });

        if (res.length === 0) {
          await trx(_image.imageRepository.tableName).insert({
            id: (0, _image2.getImageId)(),
            mediaItemId: mediaItem.id,
            seasonId: null,
            type: 'backdrop'
          });
        }
      }

      if (result.seasons) {
        for (const season of result.seasons) {
          var _season$episodes;

          let updated = false;
          season.numberOfEpisodes = season.numberOfEpisodes || ((_season$episodes = season.episodes) === null || _season$episodes === void 0 ? void 0 : _season$episodes.length) || 0;
          season.tvShowId = mediaItem.id;
          const newSeason = (0, _repository.omitUndefinedValues)(_season.tvSeasonRepository.stripValue(_season.tvSeasonRepository.serialize(season)));

          if (season.id) {
            const res = await trx('season').update(newSeason).where({
              id: season.id
            });
            updated = res === 1;
          }

          if (!updated) {
            season.id = (await trx('season').insert(newSeason).returning('id')).at(0).id;
          }

          if (!season.poster) {
            await trx(_image.imageRepository.tableName).delete().where({
              mediaItemId: mediaItem.id,
              seasonId: season.id,
              type: 'poster'
            });
          } else {
            const res = await trx(_image.imageRepository.tableName).where({
              mediaItemId: mediaItem.id,
              seasonId: season.id,
              type: 'poster'
            });

            if (res.length === 0) {
              await trx(_image.imageRepository.tableName).insert({
                id: (0, _image2.getImageId)(),
                mediaItemId: mediaItem.id,
                seasonId: season.id,
                type: 'poster'
              });
            }
          }

          if (season.episodes) {
            for (const episode of season.episodes) {
              let updated = false;
              episode.seasonAndEpisodeNumber = episode.seasonNumber * 1000 + episode.episodeNumber;
              episode.seasonId = season.id;
              episode.tvShowId = mediaItem.id;
              const newEpisode = (0, _repository.omitUndefinedValues)(_episode.tvEpisodeRepository.stripValue(_episode.tvEpisodeRepository.serialize(episode)));

              if (episode.id) {
                const res = await trx('episode').update(newEpisode).where({
                  id: episode.id
                });
                updated = res === 1;
              }

              if (!updated) {
                episode.id = (await trx('episode').insert(newEpisode).returning('id')).at(0).id;
              }
            }
          }
        }
      }

      return result;
    });
  }

  async create(mediaItem) {
    const slug = (0, _mediaItem.mediaItemSlug)(mediaItem);
    return await _dbconfig.Database.knex.transaction(async trx => {
      var _result$seasons, _result$seasons2;

      const result = { ..._lodash.default.cloneDeep(mediaItem),
        lastTimeUpdated: mediaItem.lastTimeUpdated ? mediaItem.lastTimeUpdated : new Date().getTime()
      };
      const res = await trx(this.tableName).insert({ ...this.serialize((0, _repository.omitUndefinedValues)(this.stripValue(mediaItem))),
        slug: _dbconfig.Database.knex.raw(`(CASE 
              WHEN (
                ${_dbconfig.Database.knex('mediaItem').count().where('slug', slug).toQuery()}) = 0 
                THEN '${slug}' 
              ELSE '${slug}-${(0, _slug.randomSlugId)()}' 
            END)`)
      }).returning(this.primaryColumnName);
      result.id = res.at(0)[this.primaryColumnName];

      if (result.poster) {
        const imageId = (0, _image2.getImageId)();
        await trx(_image.imageRepository.tableName).insert({
          id: imageId,
          mediaItemId: result.id,
          type: 'poster'
        });
        result.poster = `/img/${imageId}`;
      }

      if (result.backdrop) {
        const imageId = (0, _image2.getImageId)();
        await trx(_image.imageRepository.tableName).insert({
          id: imageId,
          mediaItemId: result.id,
          type: 'backdrop'
        });
        result.backdrop = `/img/${imageId}`;
      }

      result.seasons = (_result$seasons = result.seasons) === null || _result$seasons === void 0 ? void 0 : _result$seasons.map(season => {
        var _season$episodes2;

        return { ...season,
          numberOfEpisodes: season.numberOfEpisodes || ((_season$episodes2 = season.episodes) === null || _season$episodes2 === void 0 ? void 0 : _season$episodes2.length) || 0,
          tvShowId: result.id
        };
      });

      if (((_result$seasons2 = result.seasons) === null || _result$seasons2 === void 0 ? void 0 : _result$seasons2.length) > 0) {
        const seasonsId = await _dbconfig.Database.knex.batchInsert('season', result.seasons.map(season => _lodash.default.omit(season, 'episodes')), 30).transacting(trx).returning('id');
        result.seasons = _lodash.default.merge(result.seasons, seasonsId);
        const seasonsWithPosters = result.seasons.filter(season => season.poster);

        if (seasonsWithPosters.length > 0) {
          await _dbconfig.Database.knex.batchInsert(_image.imageRepository.tableName, seasonsWithPosters.map(season => ({
            id: (0, _image2.getImageId)(),
            mediaItemId: result.id,
            seasonId: season.id,
            type: 'poster'
          })), 30).transacting(trx);
        }

        for (const season of result.seasons) {
          var _season$episodes3;

          if (((_season$episodes3 = season.episodes) === null || _season$episodes3 === void 0 ? void 0 : _season$episodes3.length) > 0) {
            var _season$episodes4;

            season.episodes = (_season$episodes4 = season.episodes) === null || _season$episodes4 === void 0 ? void 0 : _season$episodes4.map(episode => ({ ...episode,
              tvShowId: result.id,
              seasonId: season.id,
              seasonAndEpisodeNumber: episode.seasonNumber * 1000 + episode.episodeNumber
            }));
            const episodesId = await _dbconfig.Database.knex.batchInsert('episode', season.episodes, 30).transacting(trx).returning('id');
            season.episodes = _lodash.default.merge(season.episodes, episodesId);
          }
        }
      }

      return result;
    });
  }

  async createMany(mediaItem) {
    return await Promise.all(mediaItem.map(mediaItem => this.create(mediaItem)));
  }

  async seasonsWithEpisodes(mediaItem) {
    const seasons = await _season.tvSeasonRepository.find({
      tvShowId: Number(mediaItem.id)
    });
    const episodes = await _episode.tvEpisodeRepository.find({
      tvShowId: Number(mediaItem.id)
    });

    const groupedEpisodes = _lodash.default.groupBy(episodes, episode => episode.seasonId);

    seasons.forEach(season => season.episodes = groupedEpisodes[season.id]);
    return seasons;
  }

  async findByExternalIds(params) {
    return (await _dbconfig.Database.knex(this.tableName).where({
      mediaType: params.mediaType
    }).andWhere(qb => {
      if (params.tmdbId) {
        qb.orWhereIn('tmdbId', params.tmdbId);
      }

      if (params.imdbId) {
        qb.orWhereIn('imdbId', params.imdbId);
      }

      if (params.tvmazeId) {
        qb.orWhereIn('tvmazeId', params.tvmazeId);
      }

      if (params.igdbId) {
        qb.orWhereIn('igdbId', params.igdbId);
      }

      if (params.openlibraryId) {
        qb.orWhereIn('openlibraryId', params.openlibraryId);
      }

      if (params.audibleId) {
        qb.orWhereIn('audibleId', params.audibleId);
      }

      if (params.goodreadsId) {
        qb.orWhereIn('goodreadsId', params.goodreadsId);
      }

      if (params.traktId) {
        qb.orWhereIn('traktId', params.traktId);
      }

      if (params.tvdbId) {
        qb.orWhereIn('tvdbId', params.tvdbId);
      }
    })).map(item => this.deserialize(item));
  }

  async findByExternalId(params, mediaType) {
    const res = await _dbconfig.Database.knex(this.tableName).where({
      mediaType: mediaType
    }).andWhere(qb => {
      if (params.tmdbId) {
        qb.orWhere('tmdbId', params.tmdbId);
      }

      if (params.imdbId) {
        qb.orWhere('imdbId', params.imdbId);
      }

      if (params.tvmazeId) {
        qb.orWhere('tvmazeId', params.tvmazeId);
      }

      if (params.igdbId) {
        qb.orWhere('igdbId', params.igdbId);
      }

      if (params.openlibraryId) {
        qb.orWhere('openlibraryId', params.openlibraryId);
      }

      if (params.audibleId) {
        qb.orWhere('audibleId', params.audibleId);
      }

      if (params.goodreadsId) {
        qb.orWhere('goodreadsId', params.goodreadsId);
      }

      if (params.traktId) {
        qb.orWhere('traktId', params.traktId);
      }

      if (params.tvdbId) {
        qb.orWhere('tvdbId', params.tvdbId);
      }
    }).first();

    if (res) {
      return this.deserialize(res);
    }
  }

  async findByTitle(params) {
    if (typeof params.title !== 'string') {
      return;
    }

    const qb = _dbconfig.Database.knex(this.tableName).select('*', _dbconfig.Database.knex.raw(`LENGTH(title) - ${params.title.length} AS rank`)).where('mediaType', params.mediaType).where(qb => qb.where('title', 'LIKE', `%${params.title}%`).orWhere('originalTitle', 'LIKE', `%${params.title}%`)).orderBy('rank', 'asc');

    if (params.releaseYear) {
      qb.whereBetween('releaseDate', [new Date(params.releaseYear, 0, 1).toISOString(), new Date(params.releaseYear, 11, 31).toISOString()]);
    }

    return this.deserialize(await qb.first());
  }

  async findByExactTitle(params) {
    if (typeof params.title !== 'string') {
      return;
    }

    const qb = _dbconfig.Database.knex(this.tableName).where('mediaType', params.mediaType).where(qb => qb.where('title', 'LIKE', params.title).orWhere('originalTitle', 'LIKE', params.title));

    if (params.releaseYear) {
      qb.whereBetween('releaseDate', [new Date(params.releaseYear, 0, 1).toISOString(), new Date(params.releaseYear, 11, 31).toISOString()]);
    }

    return this.deserialize(await qb.first());
  }

  async itemsToPossiblyUpdate() {
    return await _dbconfig.Database.knex('mediaItem').select('mediaItem.*').leftJoin('seen', 'seen.mediaItemId', 'mediaItem.id').leftJoin('listItem', 'listItem.mediaItemId', 'mediaItem.id').leftJoin('userRating', 'userRating.mediaItemId', 'mediaItem.id').where(q => q.whereNotNull('seen.id').orWhereNotNull('listItem.id').orWhereNotNull('userRating.id')).whereNot('source', 'user').whereNot('source', 'goodreads').groupBy('mediaItem.id');
  }

  async itemsToNotify(from, to) {
    return await _dbconfig.Database.knex(this.tableName).select('mediaItem.*').select('notificationsHistory.mediaItemId').select('notificationsHistory.id AS notificationsHistory.id').leftJoin('notificationsHistory', 'notificationsHistory.mediaItemId', 'mediaItem.id').whereBetween('mediaItem.releaseDate', [from.toISOString(), to.toISOString()]).whereNot('mediaType', 'tv').whereNull('notificationsHistory.id');
  }

  async episodesToNotify(from, to) {
    const res = await _dbconfig.Database.knex('episode').select((0, _items.generateColumnNames)('episode', _tvepisode.tvEpisodeColumns)).select((0, _items.generateColumnNames)('mediaItem', _mediaItem.mediaItemColumns)).select('notificationsHistory.mediaItemId').select('notificationsHistory.id AS notificationsHistory.id').leftJoin('notificationsHistory', 'notificationsHistory.episodeId', 'episode.id').leftJoin('mediaItem', 'mediaItem.id', 'episode.tvShowId').whereBetween('episode.releaseDate', [from.toISOString(), to.toISOString()]).where('episode.isSpecialEpisode', false).whereNull('notificationsHistory.id');
    return res.map(row => (0, _lodash.default)(row).pickBy((value, column) => column.startsWith('episode.')).mapKeys((value, key) => key.substring('episode.'.length)).set('tvShow', (0, _lodash.default)(row).pickBy((value, column) => column.startsWith('mediaItem.')).mapKeys((value, key) => key.substring('mediaItem.'.length)).value()).value());
  }

  async lock(mediaItemId) {
    const res = await _dbconfig.Database.knex(this.tableName).update({
      lockedAt: new Date().getTime()
    }).where('id', mediaItemId).where('lockedAt', null);

    if (res === 0) {
      throw new Error(`MediaItem ${mediaItemId} is locked`);
    }
  }

  async unlock(mediaItemId) {
    await _dbconfig.Database.knex(this.tableName).update({
      lockedAt: null
    }).where('id', mediaItemId);
  }

  async mediaItemsWithMissingPosters(mediaItemIdsWithPoster) {
    return await _dbconfig.Database.knex(this.tableName).whereNotIn('id', mediaItemIdsWithPoster).whereNotNull('poster').whereNot('poster', '');
  }

  async mediaItemsWithMissingBackdrop(mediaItemIdsWithBackdrop) {
    return await _dbconfig.Database.knex(this.tableName).whereNotIn('id', mediaItemIdsWithBackdrop).whereNotNull('backdrop').whereNot('backdrop', '');
  }

  async mergeSearchResultWithExistingItems(searchResult, mediaType) {
    let idCounter = 0;

    const searchResultWithId = _lodash.default.cloneDeep(searchResult).map(item => ({ ...item,
      searchResultId: idCounter++
    }));

    const externalIds = (0, _lodash.default)(externalIdColumnNames).keyBy(id => id).mapValues(id => searchResultWithId.map(mediaItem => mediaItem[id]).filter(Boolean)).value();
    const searchResultsByExternalId = (0, _lodash.default)(externalIdColumnNames).keyBy().mapValues(value => (0, _lodash.default)(searchResultWithId).filter(item => Boolean(item[value])).keyBy(value).value()).value();
    return await _dbconfig.Database.knex.transaction(async trx => {
      const existingItems = (await trx(this.tableName).where({
        mediaType: mediaType
      }).andWhere(qb => {
        if (externalIds.tmdbId) {
          qb.orWhereIn('tmdbId', externalIds.tmdbId);
        }

        if (externalIds.imdbId) {
          qb.orWhereIn('imdbId', externalIds.imdbId);
        }

        if (externalIds.tvmazeId) {
          qb.orWhereIn('tvmazeId', externalIds.tvmazeId);
        }

        if (externalIds.igdbId) {
          qb.orWhereIn('igdbId', externalIds.igdbId);
        }

        if (externalIds.openlibraryId) {
          qb.orWhereIn('openlibraryId', externalIds.openlibraryId);
        }

        if (externalIds.audibleId) {
          qb.orWhereIn('audibleId', externalIds.audibleId);
        }

        if (externalIds.goodreadsId) {
          qb.orWhereIn('goodreadsId', externalIds.goodreadsId);
        }

        if (externalIds.traktId) {
          qb.orWhereIn('traktId', externalIds.traktId);
        }

        if (externalIds.tvdbId) {
          qb.orWhereIn('tvdbId', externalIds.tvdbId);
        }
      })).map(item => this.deserialize(item));
      const existingSearchResults = [];
      existingItems.forEach(item => {
        externalIdColumnNames.forEach(value => {
          const res = searchResultsByExternalId[value][item[value]];

          if (res) {
            existingSearchResults.push({ ...res,
              id: item.id
            });
            return;
          }
        });
      });
      const existingImages = (0, _lodash.default)(await trx(_image.imageRepository.tableName).where({
        seasonId: null
      }).whereIn('mediaItemId', existingSearchResults.map(value => value.id))).groupBy('type').mapValues(value => _lodash.default.keyBy(value, 'mediaItemId')).value();

      for (const item of existingSearchResults) {
        const slug = (0, _mediaItem.mediaItemSlug)(item);
        await trx(this.tableName).update({ ...this.serialize(this.stripValue(item)),
          slug: _dbconfig.Database.knex.raw(`(CASE 
              WHEN (
                ${_dbconfig.Database.knex('mediaItem').count().where('slug', slug).whereNot('id', item.id).toQuery()}) = 0 
                THEN '${slug}' 
              ELSE '${slug}-${(0, _slug.randomSlugId)()}' 
            END)`)
        }).where({
          id: item.id
        });
      }

      existingSearchResults.forEach(value => {
        var _existingImages$poste, _existingImages$backd;

        const posterId = existingImages.poster && ((_existingImages$poste = existingImages.poster[value.id]) === null || _existingImages$poste === void 0 ? void 0 : _existingImages$poste.id);
        const backdropId = existingImages.backdrop && ((_existingImages$backd = existingImages.backdrop[value.id]) === null || _existingImages$backd === void 0 ? void 0 : _existingImages$backd.id);
        value.poster = posterId ? `/img/${posterId}` : null;
        value.posterSmall = posterId ? `/img/${posterId}?size=small` : null;
        value.backdrop = backdropId ? `/img/${backdropId}` : null;
      });

      const newItems = _lodash.default.differenceBy(searchResultWithId, existingSearchResults, 'searchResultId');

      newItems.forEach(item => item.lastTimeUpdated = new Date().getTime());
      const newItemsId = await _dbconfig.Database.knex.batchInsert(this.tableName, newItems.map(item => ({ ...this.serialize(this.stripValue(item)),
        slug: _dbconfig.Database.knex.raw(`(CASE 
                  WHEN (
                    ${_dbconfig.Database.knex('mediaItem').count().where('slug', (0, _mediaItem.mediaItemSlug)(item)).toQuery()}) = 0 
                    THEN '${(0, _mediaItem.mediaItemSlug)(item)}' 
                  ELSE '${(0, _mediaItem.mediaItemSlug)(item)}-${(0, _slug.randomSlugId)()}' 
                END)`)
      })), 30).transacting(trx).returning('id');

      const newItemsWithId = _lodash.default.merge(newItems, newItemsId);

      await _dbconfig.Database.knex.batchInsert(_image.imageRepository.tableName, newItemsWithId.filter(item => item.poster).map(item => {
        const posterId = (0, _image2.getImageId)();
        item.poster = `/img/${posterId}`;
        item.posterSmall = `/img/${posterId}?size=small`;
        return {
          id: posterId,
          mediaItemId: item.id,
          seasonId: null,
          type: 'poster'
        };
      }), 30).transacting(trx).returning('id');
      await _dbconfig.Database.knex.batchInsert(_image.imageRepository.tableName, newItemsWithId.filter(item => item.backdrop).map(item => {
        const backdropId = (0, _image2.getImageId)();
        item.backdrop = `/img/${backdropId}`;
        return {
          id: backdropId,
          mediaItemId: item.id,
          seasonId: null,
          type: 'backdrop'
        };
      }), 30).transacting(trx).returning('id');
      return {
        newItems: _lodash.default.sortBy(newItemsWithId, 'searchResultId'),
        existingItems: _lodash.default.sortBy(existingSearchResults, 'searchResultId'),
        mergeWithSearchResult: existingItems => (0, _lodash.default)(_lodash.default.concat(newItemsWithId, existingItems || existingSearchResults)).sortBy('searchResultId').forEach(item => delete item.searchResultId).valueOf()
      };
    });
  }

  async unlockLockedMediaItems() {
    return await _dbconfig.Database.knex(this.tableName).update('lockedAt', null).where('lockedAt', '<=', (0, _dateFns.subDays)(new Date(), 1).getTime());
  }

}

const mediaItemRepository = new MediaItemRepository();
exports.mediaItemRepository = mediaItemRepository;
const externalIdColumnNames = ['openlibraryId', 'imdbId', 'tmdbId', 'igdbId', 'tvmazeId', 'audibleId', 'traktId', 'goodreadsId', 'tvdbId'];
//# sourceMappingURL=mediaItem.js.map