import { useEffect, useMemo, useRef, useState, type ClipboardEvent, type DragEvent, type KeyboardEvent } from "react";
import { fetchSlashCommands } from "../../api/commands";
import { useChatContext } from "../../context/ChatContext";
import type { SlashCommand } from "../../types/commands";
import type { ImageContent } from "../../types/message";
import { isTouchLike } from "../../utils/format";
import {
	clipboardItemsToImages,
	fileToImageContent,
	imageToDataUrl,
	MAX_CHAT_IMAGES,
} from "../../utils/images";
import { parseBashInput } from "../../utils/bash";
import {
	applySlashCompletion,
	filterSlashCommands,
	filterWebUiSlashCommands,
	parseSlashCommandInput,
} from "../../utils/slashCommands";
import { SlashCommandMenu } from "./SlashCommandMenu";
import styles from "./ChatInput.module.css";

export function ChatInput() {
	const {
		isStreaming,
		sendMessage,
		runBashCommand,
		addSystemMessage,
		abortStream,
		toggleToolsExpanded,
	} = useChatContext();
	const [value, setValue] = useState("");
	const [pendingImages, setPendingImages] = useState<ImageContent[]>([]);
	const [dragOver, setDragOver] = useState(false);
	const [slashCommands, setSlashCommands] = useState<SlashCommand[]>([]);
	const [selectedSlashIndex, setSelectedSlashIndex] = useState(0);
	const inputRef = useRef<HTMLTextAreaElement>(null);
	const fileInputRef = useRef<HTMLInputElement>(null);
	const touchLike = isTouchLike();

	useEffect(() => {
		let cancelled = false;
		void fetchSlashCommands()
			.then((data) => {
				if (!cancelled) setSlashCommands(filterWebUiSlashCommands(data.commands || []));
			})
			.catch((err) => {
				if (!cancelled) {
					addSystemMessage(err instanceof Error ? err.message : String(err));
				}
			});
		return () => {
			cancelled = true;
		};
	}, [addSystemMessage]);

	useEffect(() => {
		const onKeyDown = (e: globalThis.KeyboardEvent) => {
			if (!inputRef.current || document.activeElement !== inputRef.current) return;
			if (e.key === "Escape" && isStreaming) {
				e.preventDefault();
				void abortStream();
			}
			if (e.key === "o" && (e.ctrlKey || e.metaKey)) {
				e.preventDefault();
				toggleToolsExpanded();
			}
		};
		document.addEventListener("keydown", onKeyDown);
		return () => document.removeEventListener("keydown", onKeyDown);
	}, [isStreaming, abortStream, toggleToolsExpanded]);

	const slashContext = useMemo(() => parseSlashCommandInput(value), [value]);
	const filteredSlashCommands = useMemo(
		() => (slashContext ? filterSlashCommands(slashCommands, slashContext.query) : []),
		[slashCommands, slashContext],
	);
	const slashMenuOpen = Boolean(slashContext && filteredSlashCommands.length > 0);

	useEffect(() => {
		setSelectedSlashIndex(0);
	}, [value, slashMenuOpen]);

	const canSend = value.trim().length > 0 || pendingImages.length > 0;

	const applySlashSelection = (command: SlashCommand) => {
		const nextValue = applySlashCompletion(value, command.name);
		setValue(nextValue);
		requestAnimationFrame(() => {
			const el = inputRef.current;
			if (!el) return;
			el.focus();
			el.setSelectionRange(nextValue.length, nextValue.length);
			el.style.height = "auto";
			el.style.height = `${Math.min(el.scrollHeight, 150)}px`;
		});
	};

	const addImages = async (files: FileList | File[]) => {
		const list = Array.from(files);
		if (!list.length) return;

		const remaining = MAX_CHAT_IMAGES - pendingImages.length;
		if (remaining <= 0) {
			addSystemMessage(`最多只能附加 ${MAX_CHAT_IMAGES} 张图片`);
			return;
		}

		const next: ImageContent[] = [];
		for (const file of list.slice(0, remaining)) {
			try {
				next.push(await fileToImageContent(file));
			} catch (err) {
				addSystemMessage(err instanceof Error ? err.message : String(err));
			}
		}
		if (next.length) {
			setPendingImages((prev) => [...prev, ...next].slice(0, MAX_CHAT_IMAGES));
		}
	};

	const removePendingImage = (index: number) => {
		setPendingImages((prev) => prev.filter((_, i) => i !== index));
	};

	const handleSend = async (mode: "prompt" | "steer" | "follow_up" = "prompt") => {
		const text = value.trim();
		if (!text && pendingImages.length === 0) return;

		const images = pendingImages.length ? pendingImages : undefined;
		setValue("");
		setPendingImages([]);
		if (inputRef.current) inputRef.current.style.height = "auto";

		if (text.startsWith("!") && parseBashInput(text)) {
			await runBashCommand(text);
		} else {
			const sendMode = isStreaming && mode === "prompt" ? "steer" : mode;
			await sendMessage(text, images, { mode: sendMode });
		}

		if (touchLike) inputRef.current?.blur();
	};

	const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
		if (e.nativeEvent.isComposing) return;

		if (slashMenuOpen) {
			if (e.key === "ArrowDown") {
				e.preventDefault();
				setSelectedSlashIndex((prev) => (prev + 1) % filteredSlashCommands.length);
				return;
			}
			if (e.key === "ArrowUp") {
				e.preventDefault();
				setSelectedSlashIndex(
					(prev) => (prev - 1 + filteredSlashCommands.length) % filteredSlashCommands.length,
				);
				return;
			}
			if (e.key === "Tab") {
				e.preventDefault();
				const command = filteredSlashCommands[selectedSlashIndex];
				if (command) applySlashSelection(command);
				return;
			}
			if (e.key === "Escape") {
				e.preventDefault();
				setValue((prev) => {
					const trimmed = prev.trimStart();
					if (!trimmed.startsWith("/")) return prev;
					return prev.replace(/^\s*\//, "");
				});
				return;
			}
			if (e.key === "Enter" && !e.shiftKey) {
				e.preventDefault();
				const command = filteredSlashCommands[selectedSlashIndex];
				if (command) applySlashSelection(command);
				return;
			}
		}

		if (e.key === "Enter" && e.shiftKey) {
			e.preventDefault();
			void handleSend("follow_up");
			return;
		}

		if (e.key === "Enter" && !e.shiftKey) {
			e.preventDefault();
			void handleSend(isStreaming ? "steer" : "prompt");
		}
	};

	const handleBeforeInput = (e: React.FormEvent<HTMLTextAreaElement>) => {
		const native = e.nativeEvent as InputEvent;
		if (touchLike && native.inputType === "insertLineBreak") {
			e.preventDefault();
			void handleSend(isStreaming ? "steer" : "prompt");
		}
	};

	const handleInput = () => {
		const el = inputRef.current;
		if (!el) return;
		el.style.height = "auto";
		el.style.height = `${Math.min(el.scrollHeight, 150)}px`;
	};

	const handlePaste = (e: ClipboardEvent<HTMLElement>) => {
		const items = e.clipboardData?.items;
		if (!items) return;

		const hasImage = Array.from(items).some((item) => item.type.startsWith("image/"));
		if (!hasImage) return;

		e.preventDefault();
		void clipboardItemsToImages(items)
			.then((images) => {
				if (!images.length) return;
				setPendingImages((prev) => {
					const remaining = MAX_CHAT_IMAGES - prev.length;
					if (remaining <= 0) {
						addSystemMessage(`最多只能附加 ${MAX_CHAT_IMAGES} 张图片`);
						return prev;
					}
					return [...prev, ...images.slice(0, remaining)];
				});
			})
			.catch((err) => {
				addSystemMessage(err instanceof Error ? err.message : String(err));
			});
	};

	const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
		const files = e.target.files;
		if (files?.length) void addImages(files);
		e.target.value = "";
	};

	const handleDragOver = (e: DragEvent<HTMLDivElement>) => {
		if (!e.dataTransfer.types.includes("Files")) return;
		e.preventDefault();
		setDragOver(true);
	};

	const handleDragLeave = (e: DragEvent<HTMLDivElement>) => {
		if (e.currentTarget.contains(e.relatedTarget as Node)) return;
		setDragOver(false);
	};

	const handleDrop = (e: DragEvent<HTMLDivElement>) => {
		e.preventDefault();
		setDragOver(false);
		const files = Array.from(e.dataTransfer.files).filter((f) => f.type.startsWith("image/"));
		if (files.length) void addImages(files);
	};

	return (
		<div
			className={`${styles.inputArea} ${dragOver ? styles.inputAreaDragOver : ""}`}
			onPaste={handlePaste}
			onDragOver={handleDragOver}
			onDragLeave={handleDragLeave}
			onDrop={handleDrop}
		>
			{pendingImages.length > 0 ? (
				<div className={styles.attachments}>
					{pendingImages.map((img, index) => (
						<div key={`${img.mimeType}-${index}`} className={styles.attachmentItem}>
							<img className={styles.attachmentThumb} src={imageToDataUrl(img)} alt="" />
							<button
								type="button"
								className={styles.attachmentRemove}
								onClick={() => removePendingImage(index)}
								aria-label="移除图片"
							>
								×
							</button>
						</div>
					))}
				</div>
			) : null}
			{slashMenuOpen ? (
				<SlashCommandMenu
					commands={filteredSlashCommands}
					selectedIndex={selectedSlashIndex}
					onSelect={applySlashSelection}
				/>
			) : null}
			<div className={styles.wrapper}>
				<input
					ref={fileInputRef}
					type="file"
					accept="image/jpeg,image/png,image/gif,image/webp"
					multiple
					className={styles.fileInput}
					onChange={handleFileChange}
					tabIndex={-1}
					aria-hidden
				/>
				<button
					type="button"
					className={styles.attachBtn}
					onClick={() => fileInputRef.current?.click()}
					disabled={pendingImages.length >= MAX_CHAT_IMAGES}
					title="上传图片"
					aria-label="上传图片"
				>
					<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
						<rect x="3" y="3" width="18" height="18" rx="2" />
						<circle cx="8.5" cy="8.5" r="1.5" />
						<path d="M21 15l-5-5L5 21" />
					</svg>
				</button>
				<textarea
					ref={inputRef}
					className={styles.input}
					rows={1}
					value={value}
					onChange={(e) => setValue(e.target.value)}
					onInput={handleInput}
					onKeyDown={handleKeyDown}
					onBeforeInput={handleBeforeInput}
					placeholder={isStreaming ? "Enter 改方向，Shift+Enter 排队后续…" : "输入消息… (/ 命令, ! bash)"}
					aria-label="消息输入框"
					enterKeyHint="send"
					inputMode="text"
				/>
				<button
					className={styles.sendBtn}
					type="button"
					onClick={() => void handleSend(isStreaming ? "steer" : "prompt")}
					disabled={!canSend}
					aria-label="发送"
				>
					<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
						<path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z" />
					</svg>
				</button>
			</div>
		</div>
	);
}
