import { XMarkIcon } from "@heroicons/react/24/outline";
import Markdown from "preact-markdown";
import { useState, useEffect, StateUpdater } from "preact/hooks";

import { Tr, langCodeContext, LANG_OPTIONS } from "@/translate";
import { ChatStore, ChatStoreMessage } from "@/types/chatstore";
import { calculate_token_length, getMessageText } from "@/chatgpt";
import TTSButton, { TTSPlay } from "@/tts";
import { MessageHide } from "@/messageHide";
import { MessageDetail } from "@/messageDetail";
import { MessageToolCall } from "@/messageToolCall";
import { MessageToolResp } from "@/messageToolResp";
import { EditMessage } from "@/editMessage";
import logprobToColor from "@/logprob";

export const isVailedJSON = (str: string): boolean => {
  try {
    JSON.parse(str);
  } catch (e) {
    return false;
  }
  return true;
};

interface Props {
  messageIndex: number;
  chatStore: ChatStore;
  setChatStore: (cs: ChatStore) => void;
}

export default function Message(props: Props) {
  const { chatStore, messageIndex, setChatStore } = props;
  const chat = chatStore.history[messageIndex];
  const [showEdit, setShowEdit] = useState(false);
  const [showCopiedHint, setShowCopiedHint] = useState(false);
  const [renderMarkdown, setRenderWorkdown] = useState(false);
  const [renderColor, setRenderColor] = useState(false);
  const DeleteIcon = () => (
    <button
      onClick={() => {
        chatStore.history[messageIndex].hide =
          !chatStore.history[messageIndex].hide;

        //chatStore.totalTokens =
        chatStore.totalTokens = 0;
        for (const i of chatStore.history
          .filter(({ hide }) => !hide)
          .slice(chatStore.postBeginIndex)
          .map(({ token }) => token)) {
          chatStore.totalTokens += i;
        }
        setChatStore({ ...chatStore });
      }}
    >
      Delete
    </button>
  );
  const CopiedHint = () => (
    <div role="alert" className="alert">
      <svg
        xmlns="http://www.w3.org/2000/svg"
        fill="none"
        viewBox="0 0 24 24"
        className="stroke-info h-6 w-6 shrink-0"
      >
        <path
          stroke-linecap="round"
          stroke-linejoin="round"
          stroke-width="2"
          d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
        ></path>
      </svg>
      <span>{Tr("Message copied to clipboard!")}</span>
    </div>
  );

  const copyToClipboard = (text: string) => {
    navigator.clipboard.writeText(text);
    setShowCopiedHint(true);
    setTimeout(() => setShowCopiedHint(false), 1000);
  };

  const CopyIcon = ({ textToCopy }: { textToCopy: string }) => {
    return (
      <>
        <button
          onClick={() => {
            copyToClipboard(textToCopy);
          }}
        >
          Copy
        </button>
      </>
    );
  };

  return (
    <>
      {chatStore.postBeginIndex !== 0 &&
        !chatStore.history[messageIndex].hide &&
        chatStore.postBeginIndex ===
          chatStore.history.slice(0, messageIndex).filter(({ hide }) => !hide)
            .length && (
          <div className="flex items-center relative justify-center">
            <hr className="w-full h-px my-4 border-0" />
            <span className="absolute px-3 rounded p-1">
              Above messages are "forgotten"
            </span>
          </div>
        )}
      <div
        className={`flex ${
          chat.role === "assistant" ? "justify-start" : "justify-end"
        }`}
      >
        <div className={`w-full`}>
          <div
            className={`chat min-w-16 p-2 my-2 ${
              chat.role === "assistant" ? "chat-start" : "chat-end"
            } ${chat.hide ? "opacity-50" : ""}`}
          >
            <div
              className={`chat-bubble max-w-full ${
                chat.role === "assistant"
                  ? renderColor
                    ? "chat-bubble-neutral"
                    : "chat-bubble-secondary"
                  : "chat-bubble-primary"
              }`}
            >
              {chat.hide ? (
                <MessageHide chat={chat} />
              ) : typeof chat.content !== "string" ? (
                <MessageDetail chat={chat} renderMarkdown={renderMarkdown} />
              ) : chat.tool_calls ? (
                <MessageToolCall
                  chat={chat}
                  copyToClipboard={copyToClipboard}
                />
              ) : chat.role === "tool" ? (
                <MessageToolResp
                  chat={chat}
                  copyToClipboard={copyToClipboard}
                />
              ) : renderMarkdown ? (
                // @ts-ignore
                <Markdown markdown={getMessageText(chat)} />
              ) : (
                <div className="message-content">
                  {
                    // only show when content is string or list of message
                    // this check is used to avoid rendering tool call
                    chat.content &&
                      (chat.logprobs && renderColor
                        ? chat.logprobs.content
                            .filter((c) => c.token)
                            .map((c) => (
                              <div
                                style={{
                                  backgroundColor: logprobToColor(c.logprob),
                                  display: "inline",
                                }}
                              >
                                {c.token}
                              </div>
                            ))
                        : getMessageText(chat))
                  }
                </div>
              )}
            </div>
            <div className="chat-footer opacity-50 flex gap-x-2">
              <DeleteIcon />
              <button onClick={() => setShowEdit(true)}>Edit</button>
              <CopyIcon textToCopy={getMessageText(chat)} />
              {chatStore.tts_api && chatStore.tts_key && (
                <TTSButton
                  chatStore={chatStore}
                  chat={chat}
                  setChatStore={setChatStore}
                />
              )}
              <TTSPlay chat={chat} />
            </div>
          </div>
          {showEdit && (
            <EditMessage
              setShowEdit={setShowEdit}
              chat={chat}
              chatStore={chatStore}
              setChatStore={setChatStore}
            />
          )}
          {showCopiedHint && <CopiedHint />}
          {chatStore.develop_mode && (
            <div
              className={`gap-1 chat-end flex ${
                chat.role === "assistant" ? "justify-start" : "justify-end"
              }`}
            >
              <span className="">token</span>
              <input
                value={chat.token}
                className="input input-bordered input-xs w-16"
                onChange={(event: any) => {
                  chat.token = parseInt(event.target.value);
                  setChatStore({ ...chatStore });
                }}
              />
              <button
                onClick={() => {
                  chatStore.history.splice(messageIndex, 1);
                  chatStore.postBeginIndex = Math.max(
                    chatStore.postBeginIndex - 1,
                    0,
                  );
                  //chatStore.totalTokens =
                  chatStore.totalTokens = 0;
                  for (const i of chatStore.history
                    .filter(({ hide }) => !hide)
                    .slice(chatStore.postBeginIndex)
                    .map(({ token }) => token)) {
                    chatStore.totalTokens += i;
                  }
                  setChatStore({ ...chatStore });
                }}
              >
                <XMarkIcon className="w-4 h-4" />
              </button>
              <span
                onClick={(event: any) => {
                  chat.example = !chat.example;
                  setChatStore({ ...chatStore });
                }}
              >
                <label className="">{Tr("example")}</label>
                <input type="checkbox" checked={chat.example} />
              </span>
              <span
                onClick={(event: any) => setRenderWorkdown(!renderMarkdown)}
              >
                <label className="">{Tr("render")}</label>
                <input type="checkbox" checked={renderMarkdown} />
              </span>
              <span onClick={(event: any) => setRenderColor(!renderColor)}>
                <label className="">{Tr("color")}</label>
                <input type="checkbox" checked={renderColor} />
              </span>
            </div>
          )}
        </div>
      </div>
    </>
  );
}
