"use strict";

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

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

var _trakttv = require("../../export/trakttv");

var _metadataProviders = require("../../metadata/metadataProviders");

var _listItemRepository = require("../../repository/listItemRepository");

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

var _seen = require("../../repository/seen");

var _userRating = require("../../repository/userRating");

var _updateMetadata = require("../../updateMetadata");

var _typescriptRoutesToOpenapiServer = require("typescript-routes-to-openapi-server");

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

/**
 * @openapi_tags TraktTvImport
 */
class TraktTvImportController {
  importState = new Map();
  /**
   * @openapi_operationId deviceToken
   */

  getUserCode = (0, _typescriptRoutesToOpenapiServer.createExpressRoute)(async (req, res) => {
    var _importState$deviceCo;

    const userId = Number(req.user);
    const importState = this.importState.get(userId);

    if (!importState || (importState === null || importState === void 0 ? void 0 : importState.state) === 'uninitialized' || (importState === null || importState === void 0 ? void 0 : (_importState$deviceCo = importState.deviceCode) === null || _importState$deviceCo === void 0 ? void 0 : _importState$deviceCo.expiresAt) <= new Date()) {
      const traktTvImport = new _trakttv.TraktTvExport();
      const deviceCode = await traktTvImport.authenticate(async userCode => {
        var _this$importState$get, _this$importState$get2, _this$importState$get3;

        if (userCode !== ((_this$importState$get = this.importState.get(userId)) === null || _this$importState$get === void 0 ? void 0 : (_this$importState$get2 = _this$importState$get.deviceCode) === null || _this$importState$get2 === void 0 ? void 0 : _this$importState$get2.userCode)) {
          return;
        }

        this.importState.set(userId, {
          state: 'exporting',
          deviceCode: (_this$importState$get3 = this.importState.get(userId)) === null || _this$importState$get3 === void 0 ? void 0 : _this$importState$get3.deviceCode
        });
        const exportedData = await traktTvImport.export();
        this.importState.set(userId, { ...this.importState.get(userId),
          state: 'updating-metadata',
          exportSummary: {
            watchlist: {
              movies: exportedData.watchlist.filter(traktTvMovieFilter).length,
              shows: exportedData.watchlist.filter(traktTvShowFilter).length,
              seasons: exportedData.watchlist.filter(traktTvSeasonFilter).length,
              episodes: exportedData.watchlist.filter(traktTvEpisodeFilter).length
            },
            seen: {
              movies: exportedData.history.filter(traktTvMovieFilter).length,
              episodes: exportedData.history.filter(traktTvEpisodeFilter).length
            },
            ratings: {
              movies: exportedData.rating.filter(traktTvMovieFilter).length,
              shows: exportedData.rating.filter(item => item.show && !item.season && !item.episode).length,
              seasons: exportedData.rating.filter(traktTvSeasonFilter).length,
              episodes: exportedData.rating.filter(traktTvEpisodeFilter).length
            }
          },
          progress: undefined
        });
        const mediaItemsMap = await updateMetadataForTraktTvImport(exportedData, progress => {
          this.importState.set(userId, { ...this.importState.get(userId),
            state: 'updating-metadata',
            progress: progress
          });
        });
        this.importState.set(userId, { ...this.importState.get(userId),
          state: 'importing',
          progress: undefined
        });

        const movieMetadata = item => {
          var _item$movie, _item$movie$ids;

          const res = mediaItemsMap[(_item$movie = item.movie) === null || _item$movie === void 0 ? void 0 : (_item$movie$ids = _item$movie.ids) === null || _item$movie$ids === void 0 ? void 0 : _item$movie$ids.tmdb];

          if (res) {
            return {
              mediaItem: res,
              item
            };
          }
        };

        const tvShowMetadata = item => {
          var _item$show, _item$show$ids;

          const res = mediaItemsMap[(_item$show = item.show) === null || _item$show === void 0 ? void 0 : (_item$show$ids = _item$show.ids) === null || _item$show$ids === void 0 ? void 0 : _item$show$ids.tmdb];

          if (res) {
            return {
              mediaItem: res,
              item
            };
          }
        };

        const watchlistMovies = exportedData.watchlist.filter(traktTvMovieFilter).map(movieMetadata).filter(Boolean).map(({
          mediaItem
        }) => ({
          mediaItemId: mediaItem.id
        }));
        const watchlistTvShows = exportedData.watchlist.filter(traktTvShowFilter).map(tvShowMetadata).filter(Boolean).map(({
          mediaItem
        }) => ({
          mediaItemId: mediaItem.id
        }));
        const watchlistSeasons = exportedData.watchlist.filter(traktTvSeasonFilter).map(tvShowMetadata).filter(Boolean).map(withSeason).filter(Boolean).map(({
          mediaItem,
          season
        }) => ({
          mediaItemId: mediaItem.id,
          seasonId: season.id
        }));
        const watchlistEpisodes = exportedData.watchlist.filter(traktTvEpisodeFilter).map(tvShowMetadata).filter(Boolean).map(withEpisode).filter(Boolean).map(({
          mediaItem,
          episode
        }) => ({
          mediaItemId: mediaItem.id,
          episodeId: episode.id
        }));
        const seenMovies = exportedData.history.filter(traktTvMovieFilter).map(movieMetadata).filter(Boolean).map(({
          mediaItem,
          item
        }) => ({
          userId: userId,
          mediaItemId: mediaItem.id,
          date: new Date(item.watched_at).getTime(),
          type: 'seen'
        }));
        const seenEpisodes = exportedData.history.filter(traktTvEpisodeFilter).map(tvShowMetadata).filter(Boolean).map(withEpisode).filter(Boolean).map(({
          mediaItem,
          item,
          episode
        }) => ({
          userId: userId,
          mediaItemId: mediaItem.id,
          date: new Date(item.watched_at).getTime(),
          episodeId: episode.id,
          type: 'seen'
        })).filter(Boolean);
        const ratedMovies = exportedData.rating.filter(traktTvMovieFilter).map(movieMetadata).filter(Boolean).map(({
          mediaItem,
          item
        }) => ({
          userId: userId,
          mediaItemId: mediaItem.id,
          rating: item.rating / 2,
          date: new Date(item.rated_at).getTime()
        }));
        const ratedTvShows = exportedData.rating.filter(item => item.show && !item.season && !item.episode).map(tvShowMetadata).filter(Boolean).map(({
          mediaItem,
          item
        }) => ({
          userId: userId,
          mediaItemId: mediaItem.id,
          rating: item.rating / 2,
          date: new Date(item.rated_at).getTime()
        }));
        const ratedSeasons = exportedData.rating.filter(traktTvSeasonFilter).map(tvShowMetadata).filter(Boolean).map(withSeason).filter(Boolean).map(({
          mediaItem,
          item,
          season
        }) => ({
          userId: userId,
          mediaItemId: mediaItem.id,
          rating: item.rating / 2,
          date: new Date(item.rated_at).getTime(),
          seasonId: season.id
        })).filter(Boolean);
        const ratedEpisodes = exportedData.rating.filter(traktTvEpisodeFilter).map(tvShowMetadata).filter(Boolean).map(withEpisode).filter(Boolean).map(({
          mediaItem,
          item,
          episode
        }) => ({
          userId: userId,
          mediaItemId: mediaItem.id,
          rating: item.rating / 2,
          date: new Date(item.rated_at).getTime(),
          episodeId: episode.id
        })).filter(Boolean);

        for (const item of [...watchlistMovies, ...watchlistTvShows, ...watchlistSeasons, ...watchlistEpisodes]) {
          await _listItemRepository.listItemRepository.addItem({
            userId: userId,
            watchlist: true,
            mediaItemId: item.mediaItemId,
            seasonId: item.seasonId || undefined,
            episodeId: item.episodeId || undefined
          });
        }

        const seenUniqueBy = seen => ({
          userId: seen.userId,
          mediaItemId: seen.mediaItemId,
          date: seen.date,
          type: seen.type,
          action: seen.action,
          process: seen.progress
        });

        await _seen.seenRepository.createManyUnique(seenMovies, seenUniqueBy);
        await _seen.seenRepository.createManyUnique(seenEpisodes, seenUniqueBy);
        await _userRating.userRatingRepository.createMany(ratedMovies);
        await _userRating.userRatingRepository.createMany(ratedTvShows);
        await _userRating.userRatingRepository.createMany(ratedSeasons);
        await _userRating.userRatingRepository.createMany(ratedEpisodes);
        this.importState.set(userId, { ...this.importState.get(userId),
          state: 'imported',
          importSummary: {
            watchlist: {
              movies: watchlistMovies.length,
              shows: watchlistTvShows.length,
              seasons: watchlistSeasons.length,
              episodes: watchlistEpisodes.length
            },
            seen: {
              movies: seenMovies.length,
              episodes: seenEpisodes.length
            },
            ratings: {
              movies: ratedMovies.length,
              shows: ratedTvShows.length,
              seasons: ratedSeasons.length,
              episodes: ratedEpisodes.length
            }
          }
        });
      });
      this.importState.set(userId, {
        state: 'waiting-for-authentication',
        deviceCode: deviceCode
      });
      res.send({
        userCode: deviceCode.userCode,
        verificationUrl: deviceCode.verificationUrl
      });
    } else {
      res.send({
        userCode: importState.deviceCode.userCode,
        verificationUrl: importState.deviceCode.verificationUrl
      });
    }
  });
  /**
   * @openapi_operationId state
   */

  state = (0, _typescriptRoutesToOpenapiServer.createExpressRoute)(async (req, res) => {
    const userId = Number(req.user);
    const importState = this.importState.get(userId);

    if (!importState) {
      res.send({
        state: 'uninitialized'
      });
      return;
    }

    res.send({
      state: importState.state,
      progress: importState.progress,
      exportSummary: importState.exportSummary,
      importSummary: importState.importSummary
    });
  });
}

exports.TraktTvImportController = TraktTvImportController;

const findEpisodeOrSeason = args => {
  var _mediaItem$seasons, _season$episodes;

  const {
    mediaItem,
    seasonNumber,
    episodeNumber
  } = args;
  const season = mediaItem === null || mediaItem === void 0 ? void 0 : (_mediaItem$seasons = mediaItem.seasons) === null || _mediaItem$seasons === void 0 ? void 0 : _mediaItem$seasons.find(season => season.seasonNumber === seasonNumber);
  const episode = episodeNumber ? season === null || season === void 0 ? void 0 : (_season$episodes = season.episodes) === null || _season$episodes === void 0 ? void 0 : _season$episodes.find(episode => episode.episodeNumber === episodeNumber) : undefined;
  return {
    season: season,
    episode: episode
  };
};

const getMediaItemsByTmdbIds = async (tmdbId, mediaType) => {
  const existingItems = await _mediaItem.mediaItemRepository.findByExternalIds({
    tmdbId: tmdbId,
    mediaType: mediaType
  });

  const existingItemsMapByTmdbId = _lodash.default.keyBy(existingItems, mediaItem => mediaItem.tmdbId);

  const missingItems = tmdbId.filter(tmdbId => !existingItemsMapByTmdbId[tmdbId]);
  await Promise.all(existingItems.filter(item => item.mediaType === 'tv').map(async item => {
    item.seasons = await _mediaItem.mediaItemRepository.seasonsWithEpisodes(item);
  }).map(errorHandler));
  return {
    existingItems: existingItems,
    missingItems: missingItems
  };
};

const errorHandler = async promise => {
  try {
    return await promise;
  } catch (error) {
    return null;
  }
};

const updateMetadataForTraktTvImport = async (exportedData, onProgress) => {
  const movies = await getMediaItemsByTmdbIds(_lodash.default.uniq([...exportedData.watchlist, ...exportedData.history, ...exportedData.rating].filter(item => item.type === 'movie').map(item => item.movie.ids.tmdb)), 'movie');
  const tvShows = await getMediaItemsByTmdbIds(_lodash.default.uniq([...exportedData.watchlist, ...exportedData.history, ...exportedData.rating].filter(item => item.type === 'show' || item.type === 'season' || item.type === 'episode').map(item => item.show.ids.tmdb)), 'tv');
  const tvShowsToUpdate = tvShows.existingItems.filter(item => item.needsDetails);
  const total = movies.missingItems.length + tvShows.missingItems.length + tvShowsToUpdate.length;
  let currentItem = 0;

  const movieMetadataProvider = _metadataProviders.metadataProviders.get('movie');

  const tvShowMetadataProvider = _metadataProviders.metadataProviders.get('tv');

  const updateProgress = async promise => {
    const res = await promise;
    currentItem++;
    onProgress(currentItem / total);
    return res;
  };

  const foundMovies = (await Promise.all(movies.missingItems.map(tmdbId => errorHandler(findMediaItemFromTmdbId(tmdbId, movieMetadataProvider))).map(updateProgress).map(errorHandler))).filter(Boolean);
  const foundTvShows = (await Promise.all(tvShows.missingItems.map(tmdbId => errorHandler(findMediaItemFromTmdbId(tmdbId, tvShowMetadataProvider))).map(updateProgress).map(errorHandler))).filter(Boolean);
  const updatedTvShows = await Promise.all(tvShowsToUpdate.map(_updateMetadata.updateMediaItem).map(updateProgress).map(errorHandler));
  return _lodash.default.keyBy([...movies.existingItems, ...foundMovies, ..._lodash.default.uniqBy([...updatedTvShows, ...tvShows.existingItems], item => item.id), ...foundTvShows], mediaItem => mediaItem.tmdbId);
};

const findMediaItemFromTmdbId = async (tmdbId, metadataProvider) => {
  const item = await metadataProvider.findByTmdbId(tmdbId);

  if (item) {
    return await _mediaItem.mediaItemRepository.create(item);
  }
};

const withEpisode = ({
  mediaItem,
  item
}) => {
  var _item$episode, _item$episode2;

  const res = findEpisodeOrSeason({
    mediaItem: mediaItem,
    seasonNumber: (_item$episode = item.episode) === null || _item$episode === void 0 ? void 0 : _item$episode.season,
    episodeNumber: (_item$episode2 = item.episode) === null || _item$episode2 === void 0 ? void 0 : _item$episode2.number
  });

  if (!(res !== null && res !== void 0 && res.episode)) {
    return;
  }

  return {
    mediaItem,
    item,
    episode: res.episode
  };
};

const withSeason = ({
  mediaItem,
  item
}) => {
  var _item$season;

  const res = findEpisodeOrSeason({
    mediaItem: mediaItem,
    seasonNumber: (_item$season = item.season) === null || _item$season === void 0 ? void 0 : _item$season.number
  });

  if (!(res !== null && res !== void 0 && res.season)) {
    return;
  }

  return {
    mediaItem,
    item,
    season: res.season
  };
};

const traktTvMovieFilter = item => item.movie;

const traktTvShowFilter = item => item.show && !item.season && !item.episode;

const traktTvSeasonFilter = item => item.show && item.season && !item.episode;

const traktTvEpisodeFilter = item => item.show && !item.season && item.episode;
//# sourceMappingURL=traktTv.js.map