import {actionCreators as ac, actionTypes as at} from "common/Actions.jsm";
import {Card, PlaceholderCard} from "content-src/components/Card/Card";
import {FormattedMessage, injectIntl} from "react-intl";
import {CollapsibleSection} from "content-src/components/CollapsibleSection/CollapsibleSection";
import {ComponentPerfTimer} from "content-src/components/ComponentPerfTimer/ComponentPerfTimer";
import {connect} from "react-redux";
import {MoreRecommendations} from "content-src/components/MoreRecommendations/MoreRecommendations";
import {PocketLoggedInCta} from "content-src/components/PocketLoggedInCta/PocketLoggedInCta";
import React from "react";
import {Topics} from "content-src/components/Topics/Topics";
import {TopSites} from "content-src/components/TopSites/TopSites";

const VISIBLE = "visible";
const VISIBILITY_CHANGE_EVENT = "visibilitychange";
const CARDS_PER_ROW_DEFAULT = 3;
const CARDS_PER_ROW_COMPACT_WIDE = 4;

function getFormattedMessage(message) {
  return typeof message === "string" ? <span>{message}</span> : <FormattedMessage {...message} />;
}

export class Section extends React.PureComponent {
  get numRows() {
    const {rowsPref, maxRows, Prefs} = this.props;
    return rowsPref ? Prefs.values[rowsPref] : maxRows;
  }

  _dispatchImpressionStats() {
    const {props} = this;
    let cardsPerRow = CARDS_PER_ROW_DEFAULT;
    if (props.compactCards && global.matchMedia(`(min-width: 1072px)`).matches) {
      // If the section has compact cards and the viewport is wide enough, we show
      // 4 columns instead of 3.
      // $break-point-widest = 1072px (from _variables.scss)
      cardsPerRow = CARDS_PER_ROW_COMPACT_WIDE;
    }
    const maxCards = cardsPerRow * this.numRows;
    const cards = props.rows.slice(0, maxCards);

    if (this.needsImpressionStats(cards)) {
      props.dispatch(ac.ImpressionStats({
        source: props.eventSource,
        tiles: cards.map(link => ({id: link.guid})),
      }));
      this.impressionCardGuids = cards.map(link => link.guid);
    }
  }

  // This sends an event when a user sees a set of new content. If content
  // changes while the page is hidden (i.e. preloaded or on a hidden tab),
  // only send the event if the page becomes visible again.
  sendImpressionStatsOrAddListener() {
    const {props} = this;

    if (!props.shouldSendImpressionStats || !props.dispatch) {
      return;
    }

    if (props.document.visibilityState === VISIBLE) {
      this._dispatchImpressionStats();
    } else {
      // We should only ever send the latest impression stats ping, so remove any
      // older listeners.
      if (this._onVisibilityChange) {
        props.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
      }

      // When the page becomes visible, send the impression stats ping if the section isn't collapsed.
      this._onVisibilityChange = () => {
        if (props.document.visibilityState === VISIBLE) {
          if (!this.props.pref.collapsed) {
            this._dispatchImpressionStats();
          }
          props.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
        }
      };
      props.document.addEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
    }
  }

  componentWillMount() {
    this.sendNewTabRehydrated(this.props.initialized);
  }

  componentDidMount() {
    if (this.props.rows.length && !this.props.pref.collapsed) {
      this.sendImpressionStatsOrAddListener();
    }
  }

  componentDidUpdate(prevProps) {
    const {props} = this;
    const isCollapsed = props.pref.collapsed;
    const wasCollapsed = prevProps.pref.collapsed;
    if (
      // Don't send impression stats for the empty state
      props.rows.length &&
      (
        // We only want to send impression stats if the content of the cards has changed
        // and the section is not collapsed...
        (props.rows !== prevProps.rows && !isCollapsed) ||
        // or if we are expanding a section that was collapsed.
        (wasCollapsed && !isCollapsed)
      )
    ) {
      this.sendImpressionStatsOrAddListener();
    }
  }

  componentWillUpdate(nextProps) {
    this.sendNewTabRehydrated(nextProps.initialized);
  }

  componentWillUnmount() {
    if (this._onVisibilityChange) {
      this.props.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
    }
  }

  needsImpressionStats(cards) {
    if (!this.impressionCardGuids || (this.impressionCardGuids.length !== cards.length)) {
      return true;
    }

    for (let i = 0; i < cards.length; i++) {
      if (cards[i].guid !== this.impressionCardGuids[i]) {
        return true;
      }
    }

    return false;
  }

  // The NEW_TAB_REHYDRATED event is used to inform feeds that their
  // data has been consumed e.g. for counting the number of tabs that
  // have rendered that data.
  sendNewTabRehydrated(initialized) {
    if (initialized && !this.renderNotified) {
      this.props.dispatch(ac.AlsoToMain({type: at.NEW_TAB_REHYDRATED, data: {}}));
      this.renderNotified = true;
    }
  }

  render() {
    const {
      id, eventSource, title, icon, rows, Pocket, topics,
      emptyState, dispatch, compactCards, read_more_endpoint,
      contextMenuOptions, initialized, learnMore,
      pref, privacyNoticeURL, isFirst, isLast,
    } = this.props;

    const waitingForSpoc = id === "topstories" && this.props.Pocket.waitingForSpoc;
    const maxCardsPerRow = compactCards ? CARDS_PER_ROW_COMPACT_WIDE : CARDS_PER_ROW_DEFAULT;
    const {numRows} = this;
    const maxCards = maxCardsPerRow * numRows;
    const maxCardsOnNarrow = CARDS_PER_ROW_DEFAULT * numRows;

    const {pocketCta, isUserLoggedIn} = Pocket || {};
    const {useCta} = pocketCta || {};

    // Don't display anything until we have a definitve result from Pocket,
    // to avoid a flash of logged out state while we render.
    const isPocketLoggedInDefined = (isUserLoggedIn === true || isUserLoggedIn === false);

    const hasTopics = topics && topics.length > 0;

    const shouldShowPocketCta = (id === "topstories" &&
      useCta && isUserLoggedIn === false);

    // Show topics only for top stories and if it has loaded with topics.
    // The classs .top-stories-bottom-container ensures content doesn't shift as things load.
    const shouldShowTopics = (id === "topstories" && hasTopics &&
      ((useCta && isUserLoggedIn === true) || (!useCta && isPocketLoggedInDefined)));

    // We use topics to determine language support for read more.
    const shouldShowReadMore = read_more_endpoint && hasTopics;

    const realRows = rows.slice(0, maxCards);

    // The empty state should only be shown after we have initialized and there is no content.
    // Otherwise, we should show placeholders.
    const shouldShowEmptyState = initialized && !rows.length;

    const cards = [];
    if (!shouldShowEmptyState) {
      for (let i = 0; i < maxCards; i++) {
        const link = realRows[i];
        // On narrow viewports, we only show 3 cards per row. We'll mark the rest as
        // .hide-for-narrow to hide in CSS via @media query.
        const className = (i >= maxCardsOnNarrow) ? "hide-for-narrow" : "";
        let usePlaceholder = !link;
        // If we are in the third card and waiting for spoc,
        // use the placeholder.
        if (!usePlaceholder && i === 2 && waitingForSpoc) {
          usePlaceholder = true;
        }
        cards.push(!usePlaceholder ? (
          <Card key={i}
            index={i}
            className={className}
            dispatch={dispatch}
            link={link}
            contextMenuOptions={contextMenuOptions}
            eventSource={eventSource}
            shouldSendImpressionStats={this.props.shouldSendImpressionStats}
            isWebExtension={this.props.isWebExtension} />
        ) : (
          <PlaceholderCard key={i} className={className} />
        ));
      }
    }

    const sectionClassName = [
      "section",
      compactCards ? "compact-cards" : "normal-cards",
    ].join(" ");

    // <Section> <-- React component
    // <section> <-- HTML5 element
    return (<ComponentPerfTimer {...this.props}>
      <CollapsibleSection className={sectionClassName} icon={icon}
        title={title}
        id={id}
        eventSource={eventSource}
        collapsed={this.props.pref.collapsed}
        showPrefName={(pref && pref.feed) || id}
        privacyNoticeURL={privacyNoticeURL}
        Prefs={this.props.Prefs}
        isFirst={isFirst}
        isLast={isLast}
        learnMore={learnMore}
        dispatch={this.props.dispatch}
        isWebExtension={this.props.isWebExtension}>

        {!shouldShowEmptyState && (<ul className="section-list" style={{padding: 0}}>
          {cards}
        </ul>)}
        {shouldShowEmptyState &&
          <div className="section-empty-state">
            <div className="empty-state">
              {emptyState.icon && emptyState.icon.startsWith("moz-extension://") ?
                <span className="empty-state-icon icon" style={{"background-image": `url('${emptyState.icon}')`}} /> :
                <span className={`empty-state-icon icon icon-${emptyState.icon}`} />}
              <p className="empty-state-message">
                {getFormattedMessage(emptyState.message)}
              </p>
            </div>
          </div>}
        {id === "topstories" &&
          <div className="top-stories-bottom-container">
            {shouldShowTopics &&
              <div className="wrapper-topics">
                <Topics topics={this.props.topics} />
              </div>
            }

            {shouldShowPocketCta &&
              <div className="wrapper-cta">
                <PocketLoggedInCta />
              </div>
            }

            <div className="wrapper-more-recommendations">
              {shouldShowReadMore &&
                <MoreRecommendations read_more_endpoint={read_more_endpoint} />}
            </div>
          </div>}
      </CollapsibleSection>
    </ComponentPerfTimer>);
  }
}

Section.defaultProps = {
  document: global.document,
  rows: [],
  emptyState: {},
  pref: {},
  title: "",
};

export const SectionIntl = connect(state => ({Prefs: state.Prefs, Pocket: state.Pocket}))(injectIntl(Section));

export class _Sections extends React.PureComponent {
  renderSections() {
    const sections = [];
    const enabledSections = this.props.Sections.filter(section => section.enabled);
    const {sectionOrder, "feeds.topsites": showTopSites} = this.props.Prefs.values;
    // Enabled sections doesn't include Top Sites, so we add it if enabled.
    const expectedCount = enabledSections.length + ~~showTopSites;

    for (const sectionId of sectionOrder.split(",")) {
      const commonProps = {
        key: sectionId,
        isFirst: sections.length === 0,
        isLast: sections.length === expectedCount - 1,
      };
      if (sectionId === "topsites" && showTopSites) {
        sections.push(<TopSites {...commonProps} />);
      } else {
        const section = enabledSections.find(s => s.id === sectionId);
        if (section) {
          sections.push(<SectionIntl {...section} {...commonProps} />);
        }
      }
    }
    return sections;
  }

  render() {
    return (
      <div className="sections-list">
        {this.renderSections()}
      </div>
    );
  }
}

export const Sections = connect(state => ({Sections: state.Sections, Prefs: state.Prefs}))(_Sections);
