import React, { useState, useEffect, useContext, useRef, useCallback, useMemo } from "react";
import Paper from "@mui/material/Paper";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemText from "@mui/material/ListItemText";
import { formatDistanceToNow, formatDuration, intervalToDuration } from "date-fns";
import TextField from "@mui/material/TextField";
import equal from "fast-deep-equal";

import "./Chat.css";
import { PlayerColor } from "./PlayerColor";
import { Resource } from "./Resource";
import { Dice } from "./Dice";
import { GlobalContext } from "./GlobalContext";

interface ChatMessage {
  message: string;
  date: number;
  color?: string;
  normalChat?: boolean;
}

const Chat: React.FC = () => {
  const [lastTop, setLastTop] = useState<number>(0);
  const [autoScroll, setAutoScroll] = useState<boolean>(true);
  const [latest, setLatest] = useState<number>(0);
  const [scrollTime, setScrollTime] = useState<number>(0);
  const [chat, setChat] = useState<ChatMessage[]>([]);
  const [startTime, setStartTime] = useState<number>(0);
  const [now, setNow] = useState<number>(Date.now());

  useEffect(() => {
    const timer = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(timer);
  }, []);

  const { lastJsonMessage, name, sendJsonMessage } = useContext(GlobalContext);
  const fields = useMemo(() => ["chat", "startTime"], []);
  useEffect(() => {
    if (!lastJsonMessage) {
      return;
    }
    const data = lastJsonMessage;
    switch (data.type) {
      case "game-update":
        console.log(`chat - game update`);
        if (data.update.chat && !equal(data.update.chat, chat)) {
          console.log(`chat - game update - ${data.update.chat.length} lines`);
          setChat(data.update.chat);
        }
        if (data.update.startTime && data.update.startTime !== startTime) {
          setStartTime(data.update.startTime);
        }
        break;
      default:
        break;
    }
  }, [lastJsonMessage, chat, startTime]);
  useEffect(() => {
    sendJsonMessage({
      type: "get",
      fields,
    });
  }, [fields, sendJsonMessage]);

  const chatKeyPress = useCallback(
    (event: React.KeyboardEvent<HTMLInputElement>) => {
      if (event.key === "Enter") {
        if (!autoScroll) {
          setAutoScroll(true);
        }

        sendJsonMessage({ type: "chat", message: (event.target as HTMLInputElement).value });
        (event.target as HTMLInputElement).value = "";
      }
    },
    [setAutoScroll, autoScroll]
  );

  const chatScroll = (event: React.UIEvent<HTMLUListElement>) => {
    const chatList = event.target as HTMLUListElement,
      fromBottom = Math.round(Math.abs(chatList.scrollHeight - chatList.offsetHeight - chatList.scrollTop));

    /* If scroll is within 20 pixels of the bottom, turn on auto-scroll */
    const shouldAutoscroll = fromBottom < 20;

    if (shouldAutoscroll !== autoScroll) {
      setAutoScroll(shouldAutoscroll);
    }

    /* If the list should not auto scroll, then cache the current
     * top of the list and record when we did this so we honor
     * the auto-scroll for at least 500ms */
    if (!shouldAutoscroll) {
      const target = Math.round(chatList.scrollTop);
      if (target !== lastTop) {
        setLastTop(target);
        setScrollTime(Date.now());
      }
    }
  };

  useEffect(() => {
    const chatList = document.getElementById("ChatList") as HTMLUListElement,
      currentTop = Math.round(chatList.scrollTop);

    if (autoScroll) {
      /* Auto-scroll to the bottom of the chat window */
      const target = Math.round(chatList.scrollHeight - chatList.offsetHeight);
      if (currentTop !== target) {
        chatList.scrollTop = target;
      }
      return;
    }

    /* Maintain current position in scrolled view if the user hasn't
     * been scrolling in the past 0.5s */
    if (Date.now() - scrollTime > 500 && currentTop !== lastTop) {
      chatList.scrollTop = lastTop;
    }
  });

  const messages = chat.map((item, index) => {
    let message;
    /* Do not perform extra parsing on player-generated
     * messages */
    if (item.normalChat) {
      message = <div key={`line-${index}`}>{item.message}</div>;
    } else {
      const punctuation = item.message.match(/(\.+$)/);
      let period;
      if (punctuation) {
        period = punctuation[1];
      } else {
        period = "";
      }
      const lines = item.message.split(".");
      message = lines
        .filter((line) => line.trim() !== "")
        .map((line, index) => {
          /* If the date is in the future, set it to now */
          const dice = line.match(/^(.*rolled )([1-6])(, ([1-6]))?(.*)$/);
          if (dice) {
            if (dice[4]) {
              return (
                <div key={`line-${index}`}>
                  {dice[1]}
                  <Dice pips={dice[2]} />,
                  <Dice pips={dice[4]} />
                  {dice[5]}
                  {period}
                </div>
              );
            } else {
              return (
                <div key={`line-${index}`}>
                  {dice[1]}
                  <Dice pips={dice[2]} />
                  {dice[5]}
                  {period}
                </div>
              );
            }
          }

          let start = line,
            message;
          while (start) {
            const resource = start.match(/^(.*)(([0-9]+) (wood|sheep|wheat|stone|brick),?)(.*)$/);
            if (resource) {
              const count = resource[3] ? parseInt(resource[3]) : 1;
              message = (
                <>
                  <Resource label={true} count={count} type={resource[4]} disabled />
                  {resource[5]}
                  {message}
                </>
              );
              start = resource[1];
            } else {
              message = (
                <>
                  {start}
                  {message}
                </>
              );
              start = "";
            }
          }
          return (
            <div key={`line-${index}`}>
              {message}
              {period}
            </div>
          );
        });
    }

    return (
      <ListItem key={`msg-${item.date}-${index}`} className={item.color ? "" : "System"}>
        {item.color && <PlayerColor color={item.color} />}
        <ListItemText
          primary={message}
          secondary={item.color && formatDistanceToNow(new Date(item.date > now ? now : item.date))}
        />
      </ListItem>
    );
  });

  if (chat.length && chat[chat.length - 1].date !== latest) {
    setLatest(chat[chat.length - 1].date);
    setAutoScroll(true);
  }

  return (
    <Paper className="Chat">
      <List className="ChatList" id="ChatList" onScroll={chatScroll}>
        {messages}
      </List>
      <TextField
        className="ChatInput"
        disabled={!name}
        onKeyPress={chatKeyPress}
        label={
          startTime !== 0 && (
            <>
              Game duration:{" "}
              {(() => {
                const duration = intervalToDuration({ start: new Date(startTime), end: new Date(now) });
                const hours = duration.hours || 0;
                const minutes = duration.minutes || 0;
                const seconds = duration.seconds || 0;
                return `${hours}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
              })()}
            </>
          )
        }
        variant="outlined"
      />
    </Paper>
  );
};

export { Chat };
