import type { ComponentType } from "react";
import { useMemo } from "react";

import hljs from "highlight.js/lib/core";
import bash from "highlight.js/lib/languages/bash";
import javascript from "highlight.js/lib/languages/javascript";
import python from "highlight.js/lib/languages/python";
import typescript from "highlight.js/lib/languages/typescript";
import xml from "highlight.js/lib/languages/xml";
import MarkdownIt from "markdown-it";
import markdownLink from "markdown-it-link-attributes";

import IconThumbDown from "virtual:icons/mdi/thumb-down";
import IconThumbUp from "virtual:icons/mdi/thumb-up";

import ChatFile from "./ChatFile";
import { useChat, useI18n, useOptions } from "../composables";
import type {
	ChatMessage,
	ChatMessageComponent,
	ChatMessageText,
	FeedbackVote,
} from "../types";

import "./Message.scss";

hljs.registerLanguage("javascript", javascript);
hljs.registerLanguage("typescript", typescript);
hljs.registerLanguage("python", python);
hljs.registerLanguage("xml", xml);
hljs.registerLanguage("bash", bash);

interface MessageProps {
	message: ChatMessage;
	className?: string;
	renderBeforeMessage?: (message: ChatMessage) => React.ReactNode;
	children?: React.ReactNode;
}

export default function Message({
	message,
	className = "",
	renderBeforeMessage,
	children,
}: MessageProps) {
	const { options } = useOptions();
	const { sendFeedback, currentSessionId } = useChat();
	const { t } = useI18n();

	const messageText = (message as ChatMessageText).text || "<Empty response>";

	const markdown = useMemo(() => {
		const parser = new MarkdownIt({
			highlight(str: string, lang: string) {
				if (lang && hljs.getLanguage(lang)) {
					try {
						return hljs.highlight(str, { language: lang }).value;
					} catch {
						return "";
					}
				}

				return "";
			},
		});

		parser.use(markdownLink, {
			attrs: {
				target: "_blank",
				rel: "noopener",
			},
		});

		return parser;
	}, []);

	const html = useMemo(
		() => markdown.render(messageText),
		[markdown, messageText],
	);

	const classes = ["chat-message"];
	if (message.sender === "user") classes.push("chat-message-from-user");
	if (message.sender === "bot") classes.push("chat-message-from-bot");
	if (message.transparent) classes.push("chat-message-transparent");
	if (className) classes.push(className);

	const messageComponents = options.messageComponents ?? {};
	const CustomComponent =
		message.type === "component"
			? (messageComponents[
					(message as ChatMessageComponent).key
				] as ComponentType<ChatMessageComponent["arguments"]> | null)
			: null;

	const showFeedback =
		Boolean(options.enableFeedback) &&
		message.sender === "bot" &&
		currentSessionId &&
		"text" in message &&
		Boolean(message.canReceiveFeedback);
	const isSubmittingFeedback = Boolean(message.feedbackSubmitting);
	const currentFeedback = message.feedback ?? null;
	const upvoteLabel = t("feedbackUpvoteButton");
	const downvoteLabel = t("feedbackDownvoteButton");

	const handleFeedback = (vote: FeedbackVote) => {
		if (!showFeedback || isSubmittingFeedback) {
			return;
		}

		if (currentFeedback === vote) {
			return;
		}

		void sendFeedback(message.id, vote);
	};

	const content = (() => {
		if (children) return children;
		if (CustomComponent) {
			return (
				<CustomComponent
					{...(message as ChatMessageComponent).arguments}
				/>
			);
		}

		return (
			<div
				className="chat-message-markdown"
				dangerouslySetInnerHTML={{ __html: html }}
			/>
		);
	})();

	return (
		<div className={classes.join(" ").trim()} data-sender={message.sender}>
			{renderBeforeMessage && (
				<div className="chat-message-actions">
					{renderBeforeMessage(message)}
				</div>
			)}

			{content}

			{(message.files ?? []).length > 0 && (
				<div className="chat-message-files">
					{(message.files ?? []).map((file) => (
						<ChatFile
							key={file.name}
							file={file}
							isRemovable={false}
							isPreviewable
						/>
					))}
				</div>
			)}

			{showFeedback && (
				<div className="chat-message-feedback">
					<button
						type="button"
						className={`chat-message-feedback-button ${currentFeedback === "upvote" ? "is-active" : ""}`.trim()}
						title={upvoteLabel}
						aria-label={upvoteLabel}
						onClick={() => handleFeedback("upvote")}
						disabled={isSubmittingFeedback}
					>
						<IconThumbUp height="16" width="16" />
					</button>
					<button
						type="button"
						className={`chat-message-feedback-button ${currentFeedback === "downvote" ? "is-active" : ""}`.trim()}
						title={downvoteLabel}
						aria-label={downvoteLabel}
						onClick={() => handleFeedback("downvote")}
						disabled={isSubmittingFeedback}
					>
						<IconThumbDown height="16" width="16" />
					</button>
				</div>
			)}
		</div>
	);
}
