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 Moment from 'react-moment';
import TextField from '@mui/material/TextField';
import 'moment-timezone';
import equal from "fast-deep-equal";

import "./Chat.css";
import { PersonColor } from './PersonColor';
import { GlobalContext } from "./GlobalContext";

export type ChatData = {
  date: string,
  message: string
};

const Chat = () => {
  const [lastTop, setLastTop] = useState(0);
  const [autoScroll, setAutoScroll] = useState(true);
  const [latest, setLatest] = useState('');
  const [scrollTime, setScrollTime] = useState(0);
  const [chat, setChat] = useState<ChatData[]>([]);
  const [startTime, setStartTime] = useState(0);

  const { ws, name } = useContext(GlobalContext);
  const fields = useMemo(() => [
    'chat', 'startTime'
  ], []);
  const onWsMessage = (event: any) => {
    const data = JSON.parse(event.data);
    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;
    }
  };
  const refWsMessage = useRef(onWsMessage);
  useEffect(() => { refWsMessage.current = onWsMessage; });
  useEffect(() => {
    if (!ws) { return; }
    const cbMessage = (e: any) => refWsMessage.current(e);
    ws.addEventListener('message', cbMessage);
    return () => {
      ws.removeEventListener('message', cbMessage);
    }
  }, [ws, refWsMessage]);
  useEffect(() => {
    if (!ws) { return; }
    ws.send(JSON.stringify({ 
      type: 'get', 
      fields
    }));
  }, [ws, fields]);

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

      if (!ws) {
        return;
      }
      ws.send(JSON.stringify({ type: 'chat', message: event.target.value }));
      event.target.value = "";
    }
  }, [ws, setAutoScroll, autoScroll]);

  const chatScroll = (event: any) => {
    const chatList = event.target,
      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");
    if (!chatList) {
      return;
    }
    const 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: any, 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: any;
      if (punctuation) {
        period = punctuation[1];
      } else {
        period = '';
      }
      let lines = item.message.split('.');
      message = lines
      .filter((line: string) => line.trim() !== '')
      .map((line: string, index: number) => {
        let start = line, message;
        while (start) {
          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 && 
         <PersonColor color={item.color}/>
        }
        <ListItemText primary={message}
          secondary={item.color && <Moment fromNow trim date={item.date > Date.now() ?
              Date.now() : item.date} interval={1000}/>} />
      </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: <Moment tz={"Etc/GMT"} 
          format="h:mm:ss"
          trim 
          durationFromNow interval={1000}
          date={startTime}/></>}
        variant="outlined"/>
    </Paper>
  );
}

export { Chat };
