import DiscourseURL from 'discourse/lib/url';
import { keyDirty } from 'discourse/widgets/widget';
import MountWidget from 'discourse/components/mount-widget';
import { cloak, uncloak } from 'discourse/widgets/post-stream';
import { isWorkaroundActive } from 'discourse/lib/safari-hacks';
import offsetCalculator from 'discourse/lib/offset-calculator';

function findTopView($posts, viewportTop, min, max) {
  if (max < min) { return min; }

  while (max > min) {
    const mid = Math.floor((min + max) / 2);
    const $post = $($posts[mid]);
    const viewBottom = $post.position().top + $post.height();

    if (viewBottom > viewportTop) {
      max = mid-1;
    } else {
      min = mid+1;
    }
  }

  return min;
}

export default MountWidget.extend({
  widget: 'post-stream',
  _topVisible: null,
  _bottomVisible: null,
  _currentPost: null,
  _currentVisible: null,
  _currentPercent: null,

  buildArgs() {
    return this.getProperties('posts',
                              'canCreatePost',
                              'multiSelect',
                              'gaps',
                              'selectedQuery',
                              'selectedPostsCount',
                              'searchService');
  },

  beforePatch() {
    const $body = $(document);
    this.prevHeight = $body.height();
    this.prevScrollTop = $body.scrollTop();
  },

  afterPatch() {
    const $body = $(document);
    const height = $body.height();
    const scrollTop = $body.scrollTop();

    // This hack is for when swapping out many cloaked views at once
    // when using keyboard navigation. It could suddenly move the
    // scroll
    if (this.prevHeight === height && scrollTop !== this.prevScrollTop) {
      $body.scrollTop(this.prevScrollTop);
    }
  },

  scrolled() {
    if (this.isDestroyed || this.isDestroying) { return; }
    if (isWorkaroundActive()) { return; }

    const $w = $(window);
    const windowHeight = window.innerHeight ? window.innerHeight : $w.height();
    const slack = Math.round(windowHeight * 5);
    const onscreen = [];
    const nearby = [];

    const windowTop = $w.scrollTop();

    const $posts = this.$('.onscreen-post, .cloaked-post');
    const viewportTop = windowTop - slack;
    const topView = findTopView($posts, viewportTop, 0, $posts.length-1);

    let windowBottom = windowTop + windowHeight;
    let viewportBottom = windowBottom + slack;

    const bodyHeight = $('body').height();
    if (windowBottom > bodyHeight) { windowBottom = bodyHeight; }
    if (viewportBottom > bodyHeight) { viewportBottom = bodyHeight; }

    let currentPost = null;
    let percent = null;

    const offset = offsetCalculator();
    const topCheck = Math.ceil(windowTop + offset);

    // uncomment to debug the eyeline
    // $('.debug-eyeline').css({ height: '1px', width: '100%', backgroundColor: 'blue', position: 'absolute', top: `${topCheck}px` });

    let allAbove = true;
    let bottomView = topView;
    let lastBottom = 0;
    while (bottomView < $posts.length) {
      const post = $posts[bottomView];
      const $post = $(post);

      if (!$post) { break; }

      const viewTop = $post.offset().top;
      const postHeight = $post.height();
      const viewBottom = Math.ceil(viewTop + postHeight);

      allAbove = allAbove && (viewTop < topCheck);

      if (viewTop > viewportBottom) { break; }

      if (viewBottom >= windowTop && viewTop <= windowBottom) {
        onscreen.push(bottomView);
      }

      if ((currentPost === null) &&
          ((viewTop <= topCheck && viewBottom >= topCheck) ||
           (lastBottom <= topCheck && viewTop >= topCheck))) {
        percent = (topCheck - viewTop) / postHeight;
        currentPost = bottomView;
      }

      lastBottom = viewBottom;
      nearby.push(bottomView);
      bottomView++;
    }

    if (allAbove) {
      if (percent === null) { percent = 1.0; }
      if (currentPost === null) { currentPost = bottomView - 1; }
    }

    const posts = this.posts;
    const refresh = cb => this.queueRerender(cb);
    if (onscreen.length) {
      const first = posts.objectAt(onscreen[0]);
      if (this._topVisible !== first) {
        this._topVisible = first;
        const $body = $('body');
        const elem = $posts[onscreen[0]];
        const elemId = elem.id;
        const $elem = $(elem);
        const elemPos = $elem.position();
        const distToElement = elemPos ? $body.scrollTop() - elemPos.top : 0;

        const topRefresh = () => {
          refresh(() => {
            const $refreshedElem = $(`#${elemId}`);

            // Quickly going back might mean the element is destroyed
            const position = $refreshedElem.position();
            if (position && position.top) {
              $('html, body').scrollTop(position.top + distToElement);
            }
          });
        };
        this.sendAction('topVisibleChanged', { post: first, refresh: topRefresh });
      }

      const last = posts.objectAt(onscreen[onscreen.length-1]);
      if (this._bottomVisible !== last) {
        this._bottomVisible = last;
        this.sendAction('bottomVisibleChanged', { post: last, refresh });
      }

      const changedPost = this._currentPost !== currentPost;
      if (changedPost) {
        this._currentPost = currentPost;
        const post = posts.objectAt(currentPost);
        this.sendAction('currentPostChanged', { post });
      }

      if (percent !== null) {
        if (percent > 1.0) { percent = 1.0; }

        if (changedPost || (this._currentPercent !== percent)) {
          this._currentPercent = percent;
          this.sendAction('currentPostScrolled', { percent });
        }
      }

    } else {
      this._topVisible = null;
      this._bottomVisible = null;
      this._currentPost = null;
      this._currentPercent = null;
    }

    const onscreenPostNumbers = [];
    const prev = this._previouslyNearby;
    const newPrev = {};
    nearby.forEach(idx => {
      const post = posts.objectAt(idx);
      const postNumber = post.post_number;
      delete prev[postNumber];

      if (onscreen.indexOf(idx) !== -1) {
        onscreenPostNumbers.push(postNumber);
      }
      newPrev[postNumber] = post;
      uncloak(post, this);
    });

    Object.keys(prev).forEach(pn => cloak(prev[pn], this));

    this._previouslyNearby = newPrev;
    this.screenTrack.setOnscreen(onscreenPostNumbers);
  },

  _scrollTriggered() {
    Ember.run.scheduleOnce('afterRender', this, this.scrolled);
  },

  didInsertElement() {
    this._super();
    const debouncedScroll = () => Ember.run.debounce(this, this._scrollTriggered, 10);

    this._previouslyNearby = {};

    this.appEvents.on('post-stream:refresh', debouncedScroll);
    $(document).bind('touchmove.post-stream', debouncedScroll);
    $(window).bind('scroll.post-stream', debouncedScroll);
    this._scrollTriggered();

    this.appEvents.on('post-stream:posted', staged => {
      const disableJumpReply = this.currentUser.get('disable_jump_reply');

      this.queueRerender(() => {
        if (staged && !disableJumpReply) {
          const postNumber = staged.get('post_number');
          DiscourseURL.jumpToPost(postNumber, { skipIfOnScreen: true });
        }
      });
    });

    this.$().on('mouseenter.post-stream', 'button.widget-button', e => {
      $('button.widget-button').removeClass('d-hover');
      $(e.target).addClass('d-hover');
    });

    this.$().on('mouseleave.post-stream', 'button.widget-button', () => {
      $('button.widget-button').removeClass('d-hover');
    });

    this.appEvents.on('post-stream:refresh', args => {
      if (args) {
        if (args.id) {
          keyDirty(`post-${args.id}`);

          if (args.refreshLikes) {
            keyDirty(`post-menu-${args.id}`, { onRefresh: 'refreshLikes' });
          }
        } else if (args.force) {
          keyDirty(`*`);
        }
      }
      this.queueRerender();
    });
  },

  willDestroyElement() {
    this._super();
    $(document).unbind('touchmove.post-stream');
    $(window).unbind('scroll.post-stream');
    this.appEvents.off('post-stream:refresh');
    this.$().off('mouseenter.post-stream');
    this.$().off('mouseleave.post-stream');
    this.appEvents.off('post-stream:refresh');
    this.appEvents.off('post-stream:posted');
  }

});
