import { ajax } from 'discourse/lib/ajax';
import { flushMap } from 'discourse/models/store';
import RestModel from 'discourse/models/rest';
import { propertyEqual } from 'discourse/lib/computed';
import { longDate } from 'discourse/lib/formatter';
import computed from 'ember-addons/ember-computed-decorators';
import ActionSummary from 'discourse/models/action-summary';
import { popupAjaxError } from 'discourse/lib/ajax-error';
import { censor } from 'pretty-text/censored-words';
import { emojiUnescape } from 'discourse/lib/text';
import PreloadStore from 'preload-store';

export function loadTopicView(topic, args) {
  const topicId = topic.get('id');
  const data = _.merge({}, args);
  const url = Discourse.getURL("/t/") + topicId;
  const jsonUrl = (data.nearPost ? `${url}/${data.nearPost}` : url) + '.json';

  delete data.nearPost;
  delete data.__type;
  delete data.store;

  return PreloadStore.getAndRemove(`topic_${topicId}`, () => {
    return ajax(jsonUrl, {data});
  }).then(json => {
    topic.updateFromJson(json);
    return json;
  });
}

const Topic = RestModel.extend({
  message: null,
  errorLoading: false,

  @computed('posters.firstObject')
  creator(poster){
    return poster && poster.user;
  },

  @computed('posters.[]')
  lastPoster(posters) {
    let user;
    if (posters && posters.length > 0) {
      const latest = posters.filter(p => p.extras && p.extras.indexOf("latest") >= 0)[0];
      user = latest && latest.user;
    }
    return user || this.get("creator");
  },

  @computed('fancy_title')
  fancyTitle(title) {
    // TODO: `siteSettings` should always be present, but there are places in the code
    // that call Discourse.Topic.create instead of using the store.
    // When the store is used, remove this.
    const siteSettings = this.siteSettings || Discourse.SiteSettings;
    return censor(emojiUnescape(title || ""), siteSettings.censored_words);
  },

  // returns createdAt if there's no bumped date
  bumpedAt: function() {
    const bumpedAt = this.get('bumped_at');
    if (bumpedAt) {
      return new Date(bumpedAt);
    } else {
      return this.get('createdAt');
    }
  }.property('bumped_at', 'createdAt'),

  bumpedAtTitle: function() {
    return I18n.t('first_post') + ": " + longDate(this.get('createdAt')) + "\n" +
           I18n.t('last_post') + ": " + longDate(this.get('bumpedAt'));
  }.property('bumpedAt'),

  createdAt: function() {
    return new Date(this.get('created_at'));
  }.property('created_at'),

  postStream: function() {
    return this.store.createRecord('postStream', {id: this.get('id'), topic: this});
  }.property(),

  @computed('tags')
  visibleListTags(tags) {
    if (!tags || !Discourse.SiteSettings.suppress_overlapping_tags_in_list) {
      return tags;
    }

    const title = this.get('title');
    const newTags = [];

    tags.forEach(function(tag){
      if (title.toLowerCase().indexOf(tag) === -1 || Discourse.SiteSettings.staff_tags.indexOf(tag) !== -1) {
        newTags.push(tag);
      }
    });

    return newTags;
  },

  replyCount: function() {
    return this.get('posts_count') - 1;
  }.property('posts_count'),

  details: function() {
    return this.store.createRecord('topicDetails', {id: this.get('id'), topic: this});
  }.property(),

  invisible: Em.computed.not('visible'),
  deleted: Em.computed.notEmpty('deleted_at'),

  searchContext: function() {
    return ({ type: 'topic', id: this.get('id') });
  }.property('id'),

  _categoryIdChanged: function() {
    this.set('category', Discourse.Category.findById(this.get('category_id')));
  }.observes('category_id').on('init'),

  _categoryNameChanged: function() {
    const categoryName = this.get('categoryName');
    let category;
    if (categoryName) {
      category = Discourse.Category.list().findProperty('name', categoryName);
    }
    this.set('category', category);
  }.observes('categoryName'),

  categoryClass: function() {
    return 'category-' + this.get('category.fullSlug');
  }.property('category.fullSlug'),

  shareUrl: function(){
    const user = Discourse.User.current();
    return this.get('url') + (user ? '?u=' + user.get('username_lower') : '');
  }.property('url'),

  url: function() {
    let slug = this.get('slug') || '';
    if (slug.trim().length === 0) {
      slug = "topic";
    }
    return Discourse.getURL("/t/") + slug + "/" + (this.get('id'));
  }.property('id', 'slug'),

  // Helper to build a Url with a post number
  urlForPostNumber(postNumber) {
    let url = this.get('url');
    if (postNumber && (postNumber > 0)) {
      url += "/" + postNumber;
    }
    return url;
  },

  totalUnread: function() {
    const count = (this.get('unread') || 0) + (this.get('new_posts') || 0);
    return count > 0 ? count : null;
  }.property('new_posts', 'unread'),

  lastReadUrl: function() {
    return this.urlForPostNumber(this.get('last_read_post_number'));
  }.property('url', 'last_read_post_number'),

  lastUnreadUrl: function() {
    const postNumber = Math.min(this.get('last_read_post_number') + 1, this.get('highest_post_number'));
    return this.urlForPostNumber(postNumber);
  }.property('url', 'last_read_post_number', 'highest_post_number'),

  lastPostUrl: function() {
    return this.urlForPostNumber(this.get('highest_post_number'));
  }.property('url', 'highest_post_number'),

  firstPostUrl: function () {
    return this.urlForPostNumber(1);
  }.property('url'),

  summaryUrl: function () {
    return this.urlForPostNumber(1) + (this.get('has_summary') ? "?filter=summary" : "");
  }.property('url'),

  lastPosterUrl: function() {
    return Discourse.getURL("/users/") + this.get("last_poster.username");
  }.property('last_poster'),

  // The amount of new posts to display. It might be different than what the server
  // tells us if we are still asynchronously flushing our "recently read" data.
  // So take what the browser has seen into consideration.
  displayNewPosts: function() {
    const highestSeen = Discourse.Session.currentProp('highestSeenByTopic')[this.get('id')];
    if (highestSeen) {
      let delta = highestSeen - this.get('last_read_post_number');
      if (delta > 0) {
        let result = this.get('new_posts') - delta;
        if (result < 0) {
          result = 0;
        }
        return result;
      }
    }
    return this.get('new_posts');
  }.property('new_posts', 'id'),

  viewsHeat: function() {
    const v = this.get('views');
    if( v >= Discourse.SiteSettings.topic_views_heat_high )   return 'heatmap-high';
    if( v >= Discourse.SiteSettings.topic_views_heat_medium ) return 'heatmap-med';
    if( v >= Discourse.SiteSettings.topic_views_heat_low )    return 'heatmap-low';
    return null;
  }.property('views'),

  archetypeObject: function() {
    return Discourse.Site.currentProp('archetypes').findProperty('id', this.get('archetype'));
  }.property('archetype'),

  isPrivateMessage: Em.computed.equal('archetype', 'private_message'),
  isBanner: Em.computed.equal('archetype', 'banner'),

  toggleStatus(property) {
    this.toggleProperty(property);
    this.saveStatus(property, !!this.get(property));
  },

  saveStatus(property, value, until) {
    if (property === 'closed') {
      this.incrementProperty('posts_count');

      if (value === true) {
        this.set('details.auto_close_at', null);
      }
    }
    return ajax(this.get('url') + "/status", {
      type: 'PUT',
      data: {
        status: property,
        enabled: !!value,
        until: until
      }
    });
  },

  makeBanner() {
    const self = this;
    return ajax('/t/' + this.get('id') + '/make-banner', { type: 'PUT' })
           .then(function () { self.set('archetype', 'banner'); });
  },

  removeBanner() {
    const self = this;
    return ajax('/t/' + this.get('id') + '/remove-banner', { type: 'PUT' })
           .then(function () { self.set('archetype', 'regular'); });
  },

  toggleBookmark() {
    if (this.get('bookmarking')) { return Ember.RSVP.Promise.resolve(); }
    this.set("bookmarking", true);

    const stream = this.get('postStream');
    const posts = Em.get(stream, 'posts');
    const firstPost = posts && posts[0] && posts[0].get('post_number') === 1 && posts[0];
    const bookmark = !this.get('bookmarked');
    const path = bookmark ? '/bookmark' : '/remove_bookmarks';

    const toggleBookmarkOnServer = () => {
      return ajax(`/t/${this.get('id')}${path}`, { type: 'PUT' }).then(() => {
        this.toggleProperty('bookmarked');
        if (bookmark && firstPost) {
          firstPost.set('bookmarked', true);
          return [firstPost.id];
        }
        if (!bookmark && posts) {

          const updated = [];
          posts.forEach(post => {
            if (post.get('bookmarked')) {
              post.set('bookmarked', false);
              updated.push(post.get('id'));
            }
          });
          return updated;
        }

        return [];
      }).catch(error => {
        let showGenericError = true;
        if (error && error.responseText) {
          try {
            bootbox.alert($.parseJSON(error.responseText).errors);
            showGenericError = false;
          } catch(e) { }
        }

        if (showGenericError) {
          bootbox.alert(I18n.t('generic_error'));
        }

        throw error;
      }).finally(() => this.set('bookmarking', false));
    };

    const unbookmarkedPosts = [];
    if (!bookmark && posts) {
      posts.forEach(post => post.get('bookmarked') && unbookmarkedPosts.push(post));
    }

    return new Ember.RSVP.Promise(resolve => {
      if (unbookmarkedPosts.length > 1) {
        bootbox.confirm(
          I18n.t("bookmarks.confirm_clear"),
          I18n.t("no_value"),
          I18n.t("yes_value"),
          confirmed => confirmed ? toggleBookmarkOnServer().then(resolve) : resolve()
        );
      } else {
        toggleBookmarkOnServer().then(resolve);
      }
    });
  },

  createGroupInvite(group) {
    return ajax("/t/" + this.get('id') + "/invite-group", {
      type: 'POST',
      data: { group }
    });
  },

  createInvite(user, group_names, custom_message) {
    return ajax("/t/" + this.get('id') + "/invite", {
      type: 'POST',
      data: { user, group_names, custom_message }
    });
  },

  generateInviteLink: function(email, groupNames, topicId) {
    return ajax('/invites/link', {
      type: 'POST',
      data: {email: email, group_names: groupNames, topic_id: topicId}
    });
  },

  // Delete this topic
  destroy(deleted_by) {
    this.setProperties({
      deleted_at: new Date(),
      deleted_by: deleted_by,
      'details.can_delete': false,
      'details.can_recover': true
    });
    return ajax("/t/" + this.get('id'), {
      data: { context: window.location.pathname },
      type: 'DELETE'
    });
  },

  // Recover this topic if deleted
  recover() {
    this.setProperties({
      deleted_at: null,
      deleted_by: null,
      'details.can_delete': true,
      'details.can_recover': false
    });
    return ajax("/t/" + this.get('id') + "/recover", { type: 'PUT' });
  },

  // Update our attributes from a JSON result
  updateFromJson(json) {
    this.get('details').updateFromJson(json.details);

    const keys = Object.keys(json);
    keys.removeObject('details');
    keys.removeObject('post_stream');

    keys.forEach(key => this.set(key, json[key]));
  },

  reload() {
    const self = this;
    return ajax('/t/' + this.get('id'), { type: 'GET' }).then(function(topic_json) {
      self.updateFromJson(topic_json);
    });
  },

  isPinnedUncategorized: function() {
    return this.get('pinned') && this.get('category.isUncategorizedCategory');
  }.property('pinned', 'category.isUncategorizedCategory'),

  clearPin() {
    const topic = this;

    // Clear the pin optimistically from the object
    topic.set('pinned', false);
    topic.set('unpinned', true);

    ajax("/t/" + this.get('id') + "/clear-pin", {
      type: 'PUT'
    }).then(null, function() {
      // On error, put the pin back
      topic.set('pinned', true);
      topic.set('unpinned', false);
    });
  },

  togglePinnedForUser() {
    if (this.get('pinned')) {
      this.clearPin();
    } else {
      this.rePin();
    }
  },

  rePin() {
    const topic = this;

    // Clear the pin optimistically from the object
    topic.set('pinned', true);
    topic.set('unpinned', false);

    ajax("/t/" + this.get('id') + "/re-pin", {
      type: 'PUT'
    }).then(null, function() {
      // On error, put the pin back
      topic.set('pinned', true);
      topic.set('unpinned', false);
    });
  },


  hasExcerpt: Em.computed.notEmpty('excerpt'),

  excerptTruncated: function() {
    const e = this.get('excerpt');
    return( e && e.substr(e.length - 8,8) === '&hellip;' );
  }.property('excerpt'),

  readLastPost: propertyEqual('last_read_post_number', 'highest_post_number'),
  canClearPin: Em.computed.and('pinned', 'readLastPost'),

  archiveMessage() {
    this.set("archiving", true);
    var promise = ajax(`/t/${this.get('id')}/archive-message`, {type: 'PUT'});

    promise.then((msg)=> {
      this.set('message_archived', true);
      if (msg && msg.group_name) {
        this.set('inboxGroupName', msg.group_name);
      }
    }).finally(()=>this.set('archiving', false));

    return promise;
  },

  moveToInbox() {
    this.set("archiving", true);
    var promise = ajax(`/t/${this.get('id')}/move-to-inbox`, {type: 'PUT'});

    promise.then((msg)=> {
      this.set('message_archived', false);
      if (msg && msg.group_name) {
        this.set('inboxGroupName', msg.group_name);
      }
    }).finally(()=>this.set('archiving', false));

    return promise;
  },

  convertTopic(type) {
    return ajax(`/t/${this.get('id')}/convert-topic/${type}`, {type: 'PUT'}).then(() => {
      window.location.reload();
    }).catch(popupAjaxError);
  }
});

Topic.reopenClass({
  NotificationLevel: {
    WATCHING: 3,
    TRACKING: 2,
    REGULAR: 1,
    MUTED: 0
  },

  createActionSummary(result) {
    if (result.actions_summary) {
      const lookup = Em.Object.create();
      result.actions_summary = result.actions_summary.map(function(a) {
        a.post = result;
        a.actionType = Discourse.Site.current().postActionTypeById(a.id);
        const actionSummary = ActionSummary.create(a);
        lookup.set(a.actionType.get('name_key'), actionSummary);
        return actionSummary;
      });
      result.set('actionByName', lookup);
    }
  },

  update(topic, props) {
    props = JSON.parse(JSON.stringify(props)) || {};

    // We support `category_id` and `categoryId` for compatibility
    if (typeof props.categoryId !== "undefined") {
      props.category_id = props.categoryId;
      delete props.categoryId;
    }

    // Make sure we never change the category for private messages
    if (topic.get("isPrivateMessage")) { delete props.category_id; }

    // Annoyingly, empty arrays are not sent across the wire. This
    // allows us to make a distinction between arrays that were not
    // sent and arrays that we specifically want to be empty.
    Object.keys(props).forEach(function(k) {
      const v = props[k];
      if (v instanceof Array && v.length === 0) {
        props[k + '_empty_array'] = true;
      }
    });

    return ajax(topic.get('url'), { type: 'PUT', data: props }).then(function(result) {
      // The title can be cleaned up server side
      props.title = result.basic_topic.title;
      props.fancy_title = result.basic_topic.fancy_title;
      topic.setProperties(props);
    });
  },

  create() {
    const result = this._super.apply(this, arguments);
    this.createActionSummary(result);
    return result;
  },

  // Load a topic, but accepts a set of filters
  find(topicId, opts) {
    let url = Discourse.getURL("/t/") + topicId;
    if (opts.nearPost) {
      url += "/" + opts.nearPost;
    }

    const data = {};
    if (opts.postsAfter) {
      data.posts_after = opts.postsAfter;
    }
    if (opts.postsBefore) {
      data.posts_before = opts.postsBefore;
    }
    if (opts.trackVisit) {
      data.track_visit = true;
    }

    // Add username filters if we have them
    if (opts.userFilters && opts.userFilters.length > 0) {
      data.username_filters = [];
      opts.userFilters.forEach(function(username) {
        data.username_filters.push(username);
      });
      data.show_deleted = true;
    }

    // Add the summary of filter if we have it
    if (opts.summary === true) {
      data.summary = true;
    }

    // Check the preload store. If not, load it via JSON
    return ajax(url + ".json", {data: data});
  },

  changeOwners(topicId, opts) {
    const promise = ajax("/t/" + topicId + "/change-owner", {
      type: 'POST',
      data: opts
    }).then(function (result) {
      if (result.success) return result;
      promise.reject(new Error("error changing ownership of posts"));
    });
    return promise;
  },

  changeTimestamp(topicId, timestamp) {
    const promise = ajax("/t/" + topicId + '/change-timestamp', {
      type: 'PUT',
      data: { timestamp: timestamp },
    }).then(function(result) {
      if (result.success) return result;
      promise.reject(new Error("error updating timestamp of topic"));
    });
    return promise;
  },

  bulkOperation(topics, operation) {
    return ajax("/topics/bulk", {
      type: 'PUT',
      data: {
        topic_ids: topics.map(function(t) { return t.get('id'); }),
        operation: operation
      }
    });
  },

  bulkOperationByFilter(filter, operation, categoryId) {
    const data = { filter: filter, operation: operation };
    if (categoryId) data['category_id'] = categoryId;
    return ajax("/topics/bulk", {
      type: 'PUT',
      data: data
    });
  },

  resetNew() {
    return ajax("/topics/reset-new", {type: 'PUT'});
  },

  idForSlug(slug) {
    return ajax("/t/id_for/" + slug);
  }
});

function moveResult(result) {
  if (result.success) {
    // We should be hesitant to flush the map but moving ids is one rare case
    flushMap();
    return result;
  }
  throw "error moving posts topic";
}

export function movePosts(topicId, data) {
  return ajax("/t/" + topicId + "/move-posts", { type: 'POST', data }).then(moveResult);
}

export function mergeTopic(topicId, destinationTopicId) {
  return ajax("/t/" + topicId + "/merge-topic", {
    type: 'POST',
    data: {destination_topic_id: destinationTopicId}
  }).then(moveResult);
}

export default Topic;
