import React, { useRef, useState } from "react";
import { Col, Row } from "antd";

import {
  EVENT_PRIORITY,
  Timeline,
  TimelineItem,
  TimestampEvent,
} from "../types/types";

import FeedItem from "./FeedItem";
import connection, {
  calculateRoomTime,
  useRoomRunningAndReadyChanged,
  useRoomTime,
  useTimeline,
} from "../lib/Connection";
import { useResize, useUpdateAfterDelay } from "../util/hooks";
import CSSTransition from "react-transition-group/CSSTransition";
import TransitionGroup from "react-transition-group/TransitionGroup";
import NextShot from "./NextShot";
import ShotsTaken from "./ShotsTaken";
import Player from "./Player";

import "../css/feed.css";
import FeedTicker from "./FeedTicker";

declare global {
  interface Window {
    __feedShakeDebug?: boolean;
  }
}

function getNextItemDelay(timeline: Timeline | null, defaultDelay = 500) {
  if (!timeline) {
    return defaultDelay;
  }

  const time = calculateRoomTime();
  const nextItem = timeline.itemAfterTime(time);

  if (!nextItem) {
    return defaultDelay;
  }

  const speedFactor = connection.room.get()?.speedFactor || 1;
  return (nextItem.timestamp * 1000 - time) / speedFactor;
}

/**
 * This map relates back an event to the index of the item displayed from
 * top to bottom. We can use this to calculate opacity.
 */
function calculateKeyToIndexMap(feed: TimelineItem[]) {
  const keyToIndex: Map<string, number> = new Map();

  let totalEvents = 0;
  for (const item of feed) {
    totalEvents += item.events.length;
  }

  let totalIdx = 0;
  for (const item of feed) {
    for (let eventIdx = 0; eventIdx < item.events.length; eventIdx++) {
      keyToIndex.set(`${item.timestamp}.${eventIdx}`, totalEvents - ++totalIdx);
    }
  }

  return keyToIndex;
}

/**
 * Makes sure 'shot' is always top-most within an timeline item
 */
function sortEvents(events: TimestampEvent[]) {
  return events.sort((a, b) => {
    return EVENT_PRIORITY.indexOf(b.type) - EVENT_PRIORITY.indexOf(a.type);
  });
}

const Feed = () => {
  const timeline = useTimeline();
  useRoomRunningAndReadyChanged();
  useResize();
  useRoomTime();

  const feedElement = useRef<HTMLDivElement>(null);
  const [lastShotId, setLastShotId] = useState<string | null>(null);

  useUpdateAfterDelay(getNextItemDelay(timeline));

  if (!timeline) {
    throw new TypeError("Feed without timeline.");
  }

  const time = calculateRoomTime();

  const liveFeed = timeline.feed.filter((item) => {
    return item.timestamp * 1000 <= time;
  });

  const keyToIndex: Map<string, number> = calculateKeyToIndexMap(liveFeed);

  const lastEvent = timeline.eventBeforeTime(time, "shot");
  if (lastEvent && lastShotId !== lastEvent?.id) {
    setLastShotId(lastEvent.id);

    if (feedElement.current) {
      // Let the browser first do the heavy dom stuff to avoid lagging our
      // animation.
      setTimeout(doSingleShake, 100);
    }
  }

  function doSingleShake() {
    if (!feedElement.current) return;

    if (feedElement.current.getAnimations().length > 0) return;

    if (feedElement.current.classList.contains("feed-shot-shake")) {
      feedElement.current.getAnimations().forEach((i) => i.finish());
      feedElement.current.classList.remove("feed-shot-shake");
      setTimeout(doSingleShake, 0);
      return;
    }

    feedElement.current.classList.add("feed-shot-shake");
    const el = feedElement.current;
    const listener = function () {
      el.classList.remove("feed-shot-shake");
      el.removeEventListener("animationend", listener);
    };
    el.addEventListener("animationend", listener);
  }

  function itemOpacity(itemKey: string) {
    // This is a bit of a hack but works better than trying to do this in css.
    // This fades out the elements the lower the element is on the screen.

    // Ticker is only visible on large screens, the ticker obstructs some of
    // the items. If it is not visible we simply take body height, otherwise
    // we use the height where the ticker starts.
    const tickerHeight = document
      .querySelector(".ticker-container")
      ?.getBoundingClientRect().y;
    const bodyHeight = document.body.clientHeight;
    const totalHeight = tickerHeight ? tickerHeight : bodyHeight;

    // Start at top most item, figure out which index is the first
    // that is NOT visible.
    const items = document.querySelectorAll(".feed-item-container");
    let i = items.length - 1;
    for (; i > 0; i--) {
      const rect = items[i].getBoundingClientRect();
      // If the bottom of this element is below the screen, we declare it
      // not-visible.
      if (rect.y + rect.height >= totalHeight) {
        break;
      }
    }
    const totalItemsOnScreen = items.length - i;
    const index = keyToIndex.get(itemKey) ?? 0;

    let x = index / (totalItemsOnScreen - 1.8);
    x = Math.min(0.9, Math.max(0, x));
    return 1 - Math.pow(x, 4);
  }

  const debug = true;
  if (debug) {
    if (!window["__feedShakeDebug"]) {
      window["__feedShakeDebug"] = true;
      window.document.documentElement.addEventListener("keydown", (e) => {
        if (e.keyCode === 67) {
          // c
          doSingleShake();
        }
      });
    }
  }

  return (
    <div className="feed" ref={feedElement}>
      <Row className="feed-items">
        <Col span={12} md={4} className="sider">
          <NextShot />
          <Player />
        </Col>
        <Col span={12} md={{ span: 4, push: 16 }} className="sider">
          <ShotsTaken />
        </Col>
        <Col span={24} md={{ span: 16, pull: 4 }}>
          <TransitionGroup className="feed-reverse">
            {liveFeed.map((item) =>
              sortEvents(item.events).map((event, j) => (
                <CSSTransition
                  timeout={300}
                  classNames="fade"
                  key={`${item.timestamp}.${j}`}
                >
                  <div
                    className="feed-item-container"
                    style={{ opacity: itemOpacity(`${item.timestamp}.${j}`) }}
                  >
                    <FeedItem item={event} />
                  </div>
                </CSSTransition>
              ))
            )}
          </TransitionGroup>
        </Col>
      </Row>
      <Row className="ticker">
        <FeedTicker />
      </Row>
    </div>
  );
};

export default Feed;
