"use strict";

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

var _core = require("@lingui/core");

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

var _chalk = _interopRequireDefault(require("chalk"));

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

var _tvseason = require("./entity/tvseason");

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

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

var _utils = require("./utils");

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

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

var _notifications = require("./notifications/notifications");

var _user = require("./repository/user");

var _lock = require("./lock");

var _logger = require("./logger");

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

const getItemsToDelete = (oldMediaItem, updatedMediaItem) => {
  var _oldMediaItem$seasons, _updatedMediaItem$sea, _oldMediaItem$seasons2;

  const oldSeasonsMap = _lodash.default.keyBy(oldMediaItem.seasons, season => season.seasonNumber);

  const newSeasonsMap = _lodash.default.keyBy(updatedMediaItem.seasons, season => season.seasonNumber);

  const oldSeasons = (_oldMediaItem$seasons = oldMediaItem.seasons) === null || _oldMediaItem$seasons === void 0 ? void 0 : _oldMediaItem$seasons.map(season => season.seasonNumber);
  const newSeasons = (_updatedMediaItem$sea = updatedMediaItem.seasons) === null || _updatedMediaItem$sea === void 0 ? void 0 : _updatedMediaItem$sea.map(season => season.seasonNumber);

  const seasonNumbersToDelete = _lodash.default.difference(oldSeasons, newSeasons);

  const seasonToDelete = seasonNumbersToDelete.map(seasonNumber => oldSeasonsMap[seasonNumber]);
  const episodesToDelete = (_oldMediaItem$seasons2 = oldMediaItem.seasons) === null || _oldMediaItem$seasons2 === void 0 ? void 0 : _oldMediaItem$seasons2.flatMap(season => {
    var _oldSeasonsMap$season, _season$episodes, _newSeasonsMap$season, _newSeasonsMap$season2;

    const episodesMap = _lodash.default.keyBy((_oldSeasonsMap$season = oldSeasonsMap[season.seasonNumber]) === null || _oldSeasonsMap$season === void 0 ? void 0 : _oldSeasonsMap$season.episodes, episode => episode.episodeNumber);

    const oldEpisodes = season === null || season === void 0 ? void 0 : (_season$episodes = season.episodes) === null || _season$episodes === void 0 ? void 0 : _season$episodes.map(episode => episode.episodeNumber);
    const newEpisodes = (_newSeasonsMap$season = newSeasonsMap[season.seasonNumber]) === null || _newSeasonsMap$season === void 0 ? void 0 : (_newSeasonsMap$season2 = _newSeasonsMap$season.episodes) === null || _newSeasonsMap$season2 === void 0 ? void 0 : _newSeasonsMap$season2.map(episode => episode.episodeNumber);

    const episodesNumbersToDelete = _lodash.default.difference(oldEpisodes, newEpisodes);

    return episodesNumbersToDelete.map(episodeNumber => episodesMap[episodeNumber]);
  });
  return [episodesToDelete, seasonToDelete];
};

const merge = (oldMediaItem, newMediaItem) => {
  var _newMediaItem$seasons;

  const seasonsMap = _lodash.default.keyBy(oldMediaItem.seasons, season => season.seasonNumber);

  const mediaItemId = oldMediaItem.id;
  return { ...newMediaItem,
    lastTimeUpdated: new Date().getTime(),
    id: mediaItemId,
    seasons: (_newMediaItem$seasons = newMediaItem.seasons) === null || _newMediaItem$seasons === void 0 ? void 0 : _newMediaItem$seasons.map(season => {
      var _seasonsMap$season$se, _seasonsMap$season$se2, _season$episodes2;

      const seasonId = (_seasonsMap$season$se = seasonsMap[season.seasonNumber]) === null || _seasonsMap$season$se === void 0 ? void 0 : _seasonsMap$season$se.id;

      const episodesMap = _lodash.default.keyBy((_seasonsMap$season$se2 = seasonsMap[season.seasonNumber]) === null || _seasonsMap$season$se2 === void 0 ? void 0 : _seasonsMap$season$se2.episodes, episode => episode.episodeNumber);

      return { ...season,
        id: seasonId,
        tvShowId: mediaItemId,
        episodes: (_season$episodes2 = season.episodes) === null || _season$episodes2 === void 0 ? void 0 : _season$episodes2.map(episode => {
          var _episodesMap$episode$;

          return { ...episode,
            id: (_episodesMap$episode$ = episodesMap[episode.episodeNumber]) === null || _episodesMap$episode$ === void 0 ? void 0 : _episodesMap$episode$.id,
            tvShowId: mediaItemId,
            seasonId: seasonId
          };
        })
      };
    })
  };
};

const downloadNewAssets = async (oldMediaItem, newMediaItem) => {
  var _newMediaItem$seasons2, _newMediaItem$seasons3;

  if (newMediaItem.poster && newMediaItem.poster !== oldMediaItem.poster) {
    await (0, _utils.updateAsset)({
      type: 'poster',
      mediaItemId: oldMediaItem.id,
      url: newMediaItem.poster
    });
  }

  if (newMediaItem.backdrop && newMediaItem.backdrop !== oldMediaItem.backdrop) {
    await (0, _utils.updateAsset)({
      type: 'backdrop',
      mediaItemId: oldMediaItem.id,
      url: newMediaItem.backdrop
    });
  }

  const newSeasonsMap = _lodash.default.keyBy(newMediaItem.seasons, season => season.seasonNumber);

  await Promise.all(((_newMediaItem$seasons2 = newMediaItem.seasons) === null || _newMediaItem$seasons2 === void 0 ? void 0 : (_newMediaItem$seasons3 = _newMediaItem$seasons2.filter(season => season.poster)) === null || _newMediaItem$seasons3 === void 0 ? void 0 : _newMediaItem$seasons3.filter(season => {
    var _newSeasonsMap$season3;

    return season.id && season.poster !== ((_newSeasonsMap$season3 = newSeasonsMap[season.id]) === null || _newSeasonsMap$season3 === void 0 ? void 0 : _newSeasonsMap$season3.poster);
  }).map(season => (0, _utils.updateAsset)({
    type: 'poster',
    mediaItemId: oldMediaItem.id,
    seasonId: season.id,
    url: season.poster
  }))) || []);
};

const sendNotifications = async (oldMediaItem, newMediaItem) => {
  const users = await _user.userRepository.usersWithMediaItemOnWatchlist(oldMediaItem.id);

  const send = async args => {
    await Promise.all(users.filter(args.filter).map(user => _notifications.Notifications.send({
      userId: user.id,
      message: args.message
    })));
  };

  if (newMediaItem.status !== oldMediaItem.status && !(!newMediaItem.status && !oldMediaItem.status)) {
    await send({
      message:
      /*i18n*/
      _core.i18n._("Status changed for {0}: \"{1}\"", {
        0: newMediaItem.title,
        1: newMediaItem.status
      }),
      filter: user => user.sendNotificationWhenStatusChanges
    });
  }

  if (newMediaItem.releaseDate !== oldMediaItem.releaseDate && (0, _dateFns.parseISO)(newMediaItem.releaseDate) > new Date()) {
    await send({
      message:
      /*i18n*/
      _core.i18n._("Release date changed for {0}: \"{1}\"", {
        0: newMediaItem.title,
        1: newMediaItem.releaseDate
      }),
      filter: user => user.sendNotificationWhenReleaseDateChanges
    });
  }

  if (newMediaItem.mediaType === 'tv') {
    const oldMediaItemNonSpecialSeasons = oldMediaItem.seasons.filter(_tvseason.TvSeasonFilters.nonSpecialSeason).sort(_tvseason.TvSeasonFilters.seasonNumber);
    const newMediaItemNonSpecialSeasons = newMediaItem.seasons.filter(_tvseason.TvSeasonFilters.nonSpecialSeason).sort(_tvseason.TvSeasonFilters.seasonNumber);

    if (newMediaItemNonSpecialSeasons.length < oldMediaItemNonSpecialSeasons.length) {
      const removedSeason = oldMediaItemNonSpecialSeasons[oldMediaItemNonSpecialSeasons.length + newMediaItemNonSpecialSeasons.length - oldMediaItemNonSpecialSeasons.length - 1];
      await send({
        message:
        /*i18n*/
        _core.i18n._("Season {0} of {1} has been canceled", {
          0: removedSeason.seasonNumber,
          1: newMediaItem.title
        }),
        filter: user => user.sendNotificationWhenNumberOfSeasonsChanges
      });
    } else if (newMediaItemNonSpecialSeasons.length > oldMediaItemNonSpecialSeasons.length) {
      const newSeason = newMediaItemNonSpecialSeasons[oldMediaItemNonSpecialSeasons.length + newMediaItemNonSpecialSeasons.length - oldMediaItemNonSpecialSeasons.length - 1];

      if (newSeason.releaseDate) {
        if ((0, _dateFns.parseISO)(newSeason.releaseDate) > new Date()) await send({
          message:
          /*i18n*/
          _core.i18n._("New season of {0} will be released at {1}", {
            0: newMediaItem.title,
            1: (0, _dateFns.parseISO)(newSeason.releaseDate).toLocaleDateString()
          }),
          filter: user => user.sendNotificationWhenNumberOfSeasonsChanges
        });
      } else {
        await send({
          message:
          /*i18n*/
          _core.i18n._("{0} got a new season", {
            0: newMediaItem.title
          }),
          filter: user => user.sendNotificationWhenNumberOfSeasonsChanges
        });
      }
    } else if (oldMediaItemNonSpecialSeasons.length > 0) {
      const oldMediaItemLastSeason = oldMediaItemNonSpecialSeasons[oldMediaItemNonSpecialSeasons.length - 1];
      const newMediaItemLastSeason = newMediaItemNonSpecialSeasons[newMediaItemNonSpecialSeasons.length - 1];

      if (oldMediaItemLastSeason.releaseDate !== newMediaItemLastSeason.releaseDate && (0, _dateFns.parseISO)(newMediaItemLastSeason.releaseDate) > new Date()) {
        await send({
          message:
          /*i18n*/
          _core.i18n._("Season {0} of {1} will be released at {2}", {
            0: newMediaItemLastSeason.seasonNumber,
            1: newMediaItem.title,
            2: (0, _dateFns.parseISO)(newMediaItemLastSeason.releaseDate).toLocaleDateString()
          }),
          filter: user => user.sendNotificationWhenNumberOfSeasonsChanges
        });
      }
    }
  }
};

const updateMediaItem = async oldMediaItem => {
  const title = oldMediaItem.title;

  if (oldMediaItem.lastTimeUpdated) {
    const date = _chalk.default.blue(new Date(oldMediaItem.lastTimeUpdated).toLocaleString());

    _logger.logger.info(
    /*i18n*/
    _core.i18n._("Updating: {title} (last updated at: {date})", {
      title: title,
      date: date
    }));
  } else {
    _logger.logger.info(
    /*i18n*/
    _core.i18n._("Updating: {title}", {
      title: title
    }));
  }

  if (!oldMediaItem) {
    return;
  }

  await _mediaItem.mediaItemRepository.lock(oldMediaItem.id);

  try {
    const metadataProvider = _metadataProviders.metadataProviders.get(oldMediaItem.mediaType, oldMediaItem.source);

    if (!metadataProvider) {
      throw new Error(`No metadata provider "${oldMediaItem.source}" for media type ${oldMediaItem.mediaType}`);
    }

    const newMediaItem = await metadataProvider.details(oldMediaItem);

    if (!newMediaItem) {
      throw new Error('No metadata');
    }

    if (newMediaItem.mediaType === 'tv') {
      oldMediaItem.seasons = await _mediaItem.mediaItemRepository.seasonsWithEpisodes(oldMediaItem);
    }

    let updatedMediaItem = merge(oldMediaItem, newMediaItem);

    if (newMediaItem.mediaType === 'tv') {
      const [episodesToDelete, seasonToDelete] = getItemsToDelete(oldMediaItem, updatedMediaItem);

      if (episodesToDelete.length > 0 || seasonToDelete.length > 0) {
        const episodesIdToDelete = [...episodesToDelete.map(episode => episode.id), ...seasonToDelete.flatMap(season => season.episodes.map(episode => episode.id))];
        const seasonsIdsToDelete = seasonToDelete.map(season => season.id);
        await _episode.tvEpisodeRepository.deleteManyById(episodesIdToDelete);
        await _season.tvSeasonRepository.deleteManyById(seasonsIdsToDelete);
      }
    }

    updatedMediaItem = await _mediaItem.mediaItemRepository.update(updatedMediaItem);
    await downloadNewAssets(oldMediaItem, updatedMediaItem);

    if (!oldMediaItem.needsDetails) {
      await sendNotifications(oldMediaItem, updatedMediaItem);
    }

    await _mediaItem.mediaItemRepository.unlock(oldMediaItem.id);
    return updatedMediaItem;
  } catch (error) {
    await _mediaItem.mediaItemRepository.unlock(oldMediaItem.id);
    throw error;
  }
};

exports.updateMediaItem = updateMediaItem;

const shouldUpdate = mediaItem => {
  const timePassed = new Date().getTime() - mediaItem.lastTimeUpdated;

  if (mediaItem.mediaType !== 'tv' && mediaItem.releaseDate && (0, _dateFns.parseISO)(mediaItem.releaseDate) < new Date() && (0, _dateFns.parseISO)(mediaItem.releaseDate) < new Date(mediaItem.lastTimeUpdated)) {
    return timePassed >= (0, _utils.durationToMilliseconds)({
      days: 30
    });
  }

  return timePassed >= (0, _utils.durationToMilliseconds)({
    hours: 24
  });
};

const updateMediaItems = async args => {
  const {
    mediaItems,
    cancellationToken,
    forceUpdate
  } = args;

  _logger.logger.info(_chalk.default.bold.green(
  /*i18n*/
  _core.i18n._("{0, plural, one {Updating metadata for # item} other {Updating metadata for # items}}", {
    0: mediaItems.length
  })));

  let numberOfUpdatedItems = 0;
  let numberOfFailures = 0;

  for (const mediaItem of mediaItems) {
    if (cancellationToken !== null && cancellationToken !== void 0 && cancellationToken.shouldCancel) {
      _logger.logger.info(_chalk.default.bold('Updating metadata canceled'));

      break;
    }

    const skip = forceUpdate ? false : !shouldUpdate(mediaItem);

    if (skip) {
      continue;
    }

    try {
      await updateMediaItem(mediaItem);
      numberOfUpdatedItems++;
    } catch (error) {
      _logger.logger.error(_chalk.default.red(error.toString()));

      numberOfFailures++;
    }
  }

  if (numberOfUpdatedItems === 0 && numberOfFailures === 0) {
    _logger.logger.info(_chalk.default.bold.green(
    /*i18n*/
    _core.i18n._("Everything up to date")));
  } else {
    const count = numberOfUpdatedItems;

    _logger.logger.info(_chalk.default.bold.green(
    /*i18n*/
    _core.i18n._("{count, plural, one {Updated 1 item} other {Updated # items}}", {
      count: count
    })));

    if (numberOfFailures > 0) {
      const count = numberOfFailures;

      _logger.logger.error(_chalk.default.bold.red(
      /*i18n*/
      _core.i18n._("{count, plural, one {Failed to update 1 item} other {Failed to update # items}}", {
        count: count
      })));
    }
  }

  cancellationToken === null || cancellationToken === void 0 ? void 0 : cancellationToken.complected();
};

exports.updateMediaItems = updateMediaItems;
const updateMetadata = (0, _lock.createLock)(async () => {
  await _mediaItem.mediaItemRepository.unlockLockedMediaItems();
  const mediaItems = await _mediaItem.mediaItemRepository.itemsToPossiblyUpdate();
  await updateMediaItems({
    mediaItems: mediaItems
  });
});
exports.updateMetadata = updateMetadata;
//# sourceMappingURL=updateMetadata.js.map