import { addGraphQLResolvers, addGraphQLQuery } from 'meteor/vulcan:core';
import { Posts } from '../lib/collections/posts';
import { WeightedList } from './weightedList.js';

const scoreRelevantFields = {baseScore:1, curatedDate:1, frontpageDate:1};

const pipelineFilterUnread = ({currentUser}) => {
  if (!currentUser)
    return [];
  
  return [
    { $lookup: {
      from: "lwevents",
      let: { documentId: "$_id", },
      pipeline: [
        { $match: {
          name: "post-view",
          userId: currentUser._id,
        } },
        { $match: { $expr: {
          $and: [
            {$eq: ["$documentId", "$$documentId"]},
          ]
        } } },
        { $limit: 1},
      ],
      as: "views",
    } },
    
    { $match: {
      "views": {$size:0}
    } },
  ];
}

const defaultViewPostFilter = {
  status: Posts.config.STATUS_APPROVED,
  isFuture: false,
  draft: false,
  unlisted: false,
  authorIsUnreviewed: false,
  groupId: {$exists: false},
};

const recommendablePostFilter = {
  ...defaultViewPostFilter,
  
  // Only consider recommending posts if they have score>30. This has a big
  // effect on the size of the recommendable-post set, which needs to not be
  // too big for performance reasons.
  baseScore: {$gt: 30},
  
  // Don't recommend meta posts
  meta: false,
}

const topUnreadPosts = async ({count, currentUser, onlyUnread, scoreFn}) => {
  const unreadPostsMetadata = await Posts.aggregate([
    { $match: {
      ...recommendablePostFilter,
    } },
    
    ...(onlyUnread ? pipelineFilterUnread({currentUser}) : []),
    
    { $project: {_id:1, ...scoreRelevantFields} },
  ]).toArray();
  
  const unreadTopPosts = _.first(
    _.sortBy(unreadPostsMetadata, post => -scoreFn(post)),
    count);
  const unreadTopPostIds = _.map(unreadTopPosts, p=>p._id);
  
  return await Posts.find(
    { _id: {$in: unreadTopPostIds} },
    { sort: {baseScore: -1} }
  ).fetch();
}

const sampleUnreadPosts = async ({count, currentUser, onlyUnread, sampleWeightFn}) => {
  const unreadPostsMetadata = await Posts.aggregate([
    { $match: {
      ...recommendablePostFilter,
    } },
    
    ...(onlyUnread ? pipelineFilterUnread({currentUser}) : []),
    
    { $project: {_id:1, ...scoreRelevantFields} },
  ]).toArray();
  
  const sampledPosts = new WeightedList(
    _.map(unreadPostsMetadata, post => [post._id, sampleWeightFn(post)])
  ).pop(count);
  
  return await Posts.find(
    { _id: {$in: sampledPosts} },
  ).fetch();
}

const getRecommendations = async ({count, algorithm, currentUser}) => {
  const scoreFn = post => {
    const sectionModifier = post.curatedDate
      ? algorithm.curatedModifier
      : (post.frontpageDate
        ? algorithm.frontpageModifier
        : algorithm.personalBlogpostModifier);
    const weight = sectionModifier + Math.pow(post.baseScore - algorithm.scoreOffset, algorithm.scoreExponent)
    return Math.max(0, weight);
  }
  
  // Cases here should match recommendationAlgorithms in RecommendationsAlgorithmPicker.jsx
  switch(algorithm.method) {
    case "top": {
      return await topUnreadPosts({
        count, currentUser,
        onlyUnread: algorithm.onlyUnread,
        scoreFn
      });
    }
    case "sample": {
      return await sampleUnreadPosts({
        count, currentUser,
        onlyUnread: algorithm.onlyUnread,
        sampleWeightFn: scoreFn,
      });
    }
    default: {
      throw new Error(`Unrecognized recommendation algorithm: ${algorithm.method}`);
    }
  }
};

addGraphQLResolvers({
  Query: {
    async Recommendations(root, {count,algorithm}, {currentUser}) {
      return getRecommendations({count, algorithm, currentUser})
    }
  }
});

addGraphQLQuery("Recommendations(count: Int, algorithm: JSON): [Post!]");

@@ -790,27 +790,29 @@
 %0A%0Aconst 
-defaultView
+recommendable
 PostFilt
@@ -824,221 +824,42 @@
 %7B%0A  
-status: Posts.config.STATUS_APPROVED,%0A  isFuture: false,%0A  draft: false,%0A  unlisted: false,%0A  authorIsUnreviewed: false,%0A  groupId: %7B$exists: false%7D,%0A%7D;%0A%0Aconst recommendablePostFilter = %7B%0A  ...defaultViewPostFilte
+...Posts.getParameters(%7B%7D).selecto
 r,%0A 

Use actual default view for recommendations default-view selector