/**
 * ChatInput — 聊天输入框
 *
 * 附件系统：
 * - 图片(png/jpg/gif/webp)：缩略图预览，发送时转 base64 通过 images 字段传给 LLM
 * - 文本文件(txt/md/json/csv/ts/js/py等)：读取内容注入消息上下文
 * - 其他文件：显示文件名+大小卡片，提示 LLM 文件信息
 *
 * @see docs/MetonaAI-Desktop UI UX 设计集成方案.html — 输入框智能特性
 */

import { useState, useCallback, useRef, useEffect } from 'react';
import { Box, Typography, IconButton, Tooltip, Stack, Button, Paper, InputBase } from '@mui/material';
import { Send, Paperclip, Square, X, FileText, Image as ImageIcon } from 'lucide-react';
import { nanoid } from 'nanoid';
import { useAgentStore } from '@renderer/stores/agent-store';
import { useSessionStore } from '@renderer/stores/session-store';
import { useUIStore } from '@renderer/stores/ui-store';
import { formatTokens, formatFileSize } from '@renderer/lib/formatters';

const SLASH_COMMANDS = [
  { id: 'tool', label: '/tool', description: '选择工具' },
  { id: 'memory', label: '/memory', description: '搜索记忆' },
  { id: 'clear', label: '/clear', description: '清空会话' },
  { id: 'export', label: '/export', description: '导出会话' },
];

const IMAGE_TYPES = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];
const TEXT_EXTENSIONS = ['txt', 'md', 'json', 'csv', 'ts', 'tsx', 'js', 'jsx', 'py', 'rb', 'go', 'rs', 'java', 'c', 'cpp', 'h', 'css', 'html', 'xml', 'yaml', 'yml', 'toml', 'ini', 'sh', 'bash', 'zsh', 'fish', 'sql', 'env', 'gitignore', 'dockerfile', 'makefile', 'log'];

interface Attachment {
  id: string;
  file: File;
  type: 'image' | 'text' | 'other';
  preview?: string;      // 图片 base64 data URL
  textContent?: string;  // 文本文件内容
}

export function ChatInput(): React.JSX.Element {
  const [input, setInput] = useState('');
  const [attachments, setAttachments] = useState<Attachment[]>([]);
  const [showSlashMenu, setShowSlashMenu] = useState(false);
  const [slashFilter, setSlashFilter] = useState('');
  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const fileInputRef = useRef<HTMLInputElement>(null);
  const sendMessage = useAgentStore((s) => s.sendMessage);
  const abort = useAgentStore((s) => s.abort);
  const isStreaming = useAgentStore((s) => s.isStreaming);
  const configLoaded = useAgentStore((s) => s.configLoaded);
  // v0.3.18 修复: 工具未就绪时禁用发送按钮
  const toolsReady = useAgentStore((s) => s.toolsReady);
  const tokenUsage = useAgentStore((s) => s.tokenUsage);
  const currentSessionId = useSessionStore((s) => s.currentSessionId);
  const provider = useAgentStore((s) => s.provider);

  /** DeepSeek 不支持多模态图片 */
  const supportsImages = provider !== 'deepseek';

  // 草稿自动保存
  useEffect(() => { if (currentSessionId) { const d = sessionStorage.getItem(`draft-${currentSessionId}`); setInput(d ?? ''); } }, [currentSessionId]);
  useEffect(() => { if (currentSessionId && input) sessionStorage.setItem(`draft-${currentSessionId}`, input); }, [input, currentSessionId]);

  // ===== 附件处理 =====

  const classifyFile = useCallback((file: File): Attachment['type'] => {
    if (IMAGE_TYPES.includes(file.type)) return 'image';
    const ext = file.name.split('.').pop()?.toLowerCase() ?? '';
    if (TEXT_EXTENSIONS.includes(ext)) return 'text';
    return 'other';
  }, []);

  const processFile = useCallback(async (file: File): Promise<Attachment> => {
    const type = classifyFile(file);
    // L-10 修复: 统一使用 nanoid 生成附件 ID（与项目其他位置一致）
    const attachment: Attachment = { id: `att_${nanoid(6)}`, file, type };

    if (type === 'image') {
      // 图片转 base64 data URL → 压缩（限制 1024px, JPEG 0.7）
      const dataUri = await new Promise<string>((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => resolve(reader.result as string);
        reader.onerror = () => reject(new Error('图片读取失败'));
        reader.readAsDataURL(file);
      });
      attachment.preview = await compressImage(dataUri, 1024, 0.7);
    } else if (type === 'text') {
      // 文本文件读取内容
      attachment.textContent = await new Promise<string>((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => resolve(reader.result as string);
        reader.onerror = () => reject(new Error('文本文件读取失败'));
        reader.readAsText(file);
      });
    }

    return attachment;
  }, [classifyFile]);

  const addFiles = useCallback(async (files: FileList | File[]) => {
    const fileArray = Array.from(files).slice(0, 5); // 最多 5 个附件

    // DeepSeek 不支持多模态，过滤图片
    const filtered = supportsImages
      ? fileArray
      : fileArray.filter((f) => !IMAGE_TYPES.includes(f.type));

    if (filtered.length < fileArray.length && !supportsImages) {
      // v0.3.0: 用 Toast 提示用户 DeepSeek 不支持图片
      const skipped = fileArray.length - filtered.length;
      // v0.3.0 修复：记录 Toast 加载失败错误到控制台，而非静默吞掉
      import('@metona-team/metona-toast').then((mod) => {
        mod.default.warning(`DeepSeek 不支持图片，已自动跳过 ${skipped} 个图片文件`);
      }).catch((err) => {
        console.error('[ChatInput] Failed to load metona-toast for DeepSeek image warning:', err);
      });
    }

    if (filtered.length === 0) return;

    // v0.3.0 修复: 使用 allSettled 处理部分文件读取失败，避免一个失败导致全部丢失
    const results = await Promise.allSettled(filtered.map(processFile));
    const successful = results.filter(
      (r): r is PromiseFulfilledResult<Attachment> => r.status === 'fulfilled',
    ).map((r) => r.value);
    if (successful.length === 0) {
      import('@metona-team/metona-toast').then((mod) => {
        mod.default.error('文件读取失败，请检查文件是否损坏或被锁定');
      }).catch(() => {});
      return;
    }
    if (successful.length < filtered.length) {
      const failedCount = filtered.length - successful.length;
      import('@metona-team/metona-toast').then((mod) => {
        mod.default.warning(`${failedCount} 个文件读取失败，已跳过`);
      }).catch(() => {});
    }
    setAttachments((prev) => [...prev, ...successful]);
  }, [processFile, supportsImages]);

  const removeAttachment = useCallback((id: string) => {
    setAttachments((prev) => prev.filter((a) => a.id !== id));
  }, []);

  // 文件选择
  const handleFileSelect = useCallback(() => { fileInputRef.current?.click(); }, []);
  const handleFileChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    if (e.target.files) addFiles(e.target.files);
    e.target.value = '';
  }, [addFiles]);

  // 拖拽
  const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); }, []);
  const handleDrop = useCallback((e: React.DragEvent) => {
    e.preventDefault(); e.stopPropagation();
    if (e.dataTransfer.files.length) addFiles(e.dataTransfer.files);
  }, [addFiles]);

  // 粘贴
  const handlePaste = useCallback((e: React.ClipboardEvent) => {
    const items = Array.from(e.clipboardData.items);
    const files: File[] = [];
    for (const item of items) {
      if (item.kind === 'file') {
        const f = item.getAsFile();
        if (f) files.push(f);
      }
    }
    if (files.length) { e.preventDefault(); addFiles(files); }
  }, [addFiles]);

  // ===== 发送消息 =====

  const handleSend = useCallback(async () => {
    const trimmed = input.trim();
    if (!trimmed && attachments.length === 0) return;
    if (isStreaming) return;
    if (!configLoaded) return; // P1-6: 配置未加载完成时禁止发送
    if (!toolsReady) return; // v0.3.18: 工具未就绪时禁止发送

    // 处理 / 命令
    if (trimmed.startsWith('/')) {
      const cmd = trimmed.split(' ')[0].toLowerCase();
      if (cmd === '/clear') { useAgentStore.getState().clearMessages(); setInput(''); setAttachments([]); setShowSlashMenu(false); return; }
      if (cmd === '/export') {
        const blob = new Blob([JSON.stringify(useAgentStore.getState().messages, null, 2)], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = `session-${Date.now()}.json`;
        a.click();
        // v0.3.0 修复：释放 Blob URL，避免内存泄漏
        URL.revokeObjectURL(url);
        setInput(''); setShowSlashMenu(false); return;
      }
      // v0.3.0: /tool — 打开设置面板的工具管理 Tab
      if (cmd === '/tool') {
        useUIStore.getState().openSettings();
        setInput(''); setAttachments([]); setShowSlashMenu(false);
        return;
      }
      // v0.3.0: /memory — 切换到详情面板的 Memory 标签
      // v0.3.0 修复：确保详情面板可见，否则切换标签用户看不到
      if (cmd === '/memory') {
        const uiStore = useUIStore.getState();
        uiStore.setDetailTab('memory');
        // v0.3.0 修复: 专注模式下先退出专注模式（恢复快照），再确保详情面板可见
        if (uiStore.focusMode) {
          uiStore.toggleFocusMode();
        }
        // 退出专注模式后再次检查详情面板可见性
        if (!useUIStore.getState().detailVisible) {
          uiStore.toggleDetail();
        }
        setInput(''); setAttachments([]); setShowSlashMenu(false);
        return;
      }
    }

    // 构建用户可见内容（纯文本 + 附件描述隐藏）
    let messageContent = trimmed;
    const images: Array<{ url: string; detail?: 'low' | 'high' | 'auto' }> = [];

    // 附件元数据（用于 UI 渲染）
    const attachmentInfos = attachments.map((att) => ({
      id: att.id,
      name: att.file.name,
      type: att.type,
      size: att.file.size,
      preview: att.preview,
      textContent: att.type === 'text' ? att.textContent : undefined,
    }));

    // 图片加入 images 数组
    for (const att of attachments) {
      if (att.type === 'image' && att.preview) {
        images.push({ url: att.preview, detail: 'auto' });
      }
    }

    sendMessage(messageContent, images.length > 0 ? images : undefined, attachmentInfos.length > 0 ? attachmentInfos : undefined);
    setInput('');
    setAttachments([]);
    setShowSlashMenu(false);
    if (currentSessionId) sessionStorage.removeItem(`draft-${currentSessionId}`);
    if (textareaRef.current) textareaRef.current.style.height = 'auto';
  }, [input, attachments, isStreaming, sendMessage, currentSessionId]);

  const handleAbort = useCallback(() => { abort(); }, [abort]);

  const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
    // H-9 修复: 快捷键对齐规范
    // @see docs/MetonaAI-Desktop UI UX 设计集成方案.html — 快捷键规范
    // 规范要求: Cmd/Ctrl+Enter = 发送消息, Cmd/Ctrl+Shift+Enter = 换行
    // 之前代码是 Ctrl+Enter=换行, Enter=发送，与规范相反
    // 修复后:
    //   Cmd/Ctrl+Enter = 发送消息（规范要求）
    //   Cmd/Ctrl+Shift+Enter = 换行（规范要求）
    //   Enter = 发送消息（保持聊天应用习惯）
    //   Shift+Enter = 换行（textarea 默认行为，无需处理）

    // Cmd/Ctrl+Shift+Enter — 换行（规范要求）
    if (e.key === 'Enter' && (e.ctrlKey || e.metaKey) && e.shiftKey) {
      e.preventDefault();
      const t = e.currentTarget as HTMLTextAreaElement;
      const s = t.selectionStart;
      const en = t.selectionEnd;
      setInput((p) => p.slice(0, s) + '\n' + p.slice(en));
      requestAnimationFrame(() => { t.selectionStart = t.selectionEnd = s + 1; });
      return;
    }
    // Cmd/Ctrl+Enter — 发送消息（规范要求，优先于 Enter）
    if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
      e.preventDefault();
      handleSend();
      return;
    }
    // Enter（不带修饰键）— 发送消息（保持聊天应用习惯）
    if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); return; }
    if (e.key === 'Escape' && showSlashMenu) { setShowSlashMenu(false); return; }
  }, [handleSend, showSlashMenu]);

  // H-8 修复: 使用 InputBase 后，onChange 类型需兼容 HTMLInputElement | HTMLTextAreaElement
  const handleChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
    const v = e.target.value; setInput(v);
    if (v === '/') { setShowSlashMenu(true); setSlashFilter(''); }
    else if (v.startsWith('/') && !v.includes(' ')) { setShowSlashMenu(true); setSlashFilter(v.slice(1).toLowerCase()); }
    else { setShowSlashMenu(false); }
  }, []);

  const filteredCommands = SLASH_COMMANDS.filter((c) => c.label.toLowerCase().includes(`/${slashFilter}`));

  return (
    <Box sx={{ flexShrink: 0, px: 2, pb: 1.5 }}>
      <Paper sx={{ maxWidth: 768, mx: 'auto', borderRadius: 3, p: 1.5, bgcolor: 'background.paper', border: '1px solid', borderColor: 'divider', position: 'relative' }}
        onDragOver={handleDragOver} onDrop={handleDrop}
      >
        {/* 附件预览区 */}
        {attachments.length > 0 && (
          <Stack direction="row" spacing={1} sx={{ mb: 1, flexWrap: 'wrap', gap: 1 }}>
            {attachments.map((att) => (
              <AttachmentPreview key={att.id} attachment={att} onRemove={() => removeAttachment(att.id)} />
            ))}
          </Stack>
        )}

        {/* / 命令菜单 */}
        {showSlashMenu && filteredCommands.length > 0 && (
          <div style={{ position: 'absolute', bottom: '100%', left: 0, right: 0, marginBottom: 4, padding: '4px 0', background: 'var(--bg-secondary, #1a1d27)', border: '1px solid var(--border-color, #2a2d3a)', borderRadius: 8, boxShadow: '0 -4px 12px rgba(0,0,0,0.2)', zIndex: 10 }}>
            {filteredCommands.map((cmd) => (
              <div key={cmd.id} onClick={() => { setInput(cmd.label + ' '); setShowSlashMenu(false); textareaRef.current?.focus(); }}
                style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '6px 12px', fontSize: 12, cursor: 'pointer', color: 'var(--text-primary, #e1e4ed)' }}
                onMouseEnter={(e) => (e.currentTarget.style.background = 'rgba(255,255,255,0.05)')}
                onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}
              >
                <span style={{ color: '#818cf8', fontSize: 14, fontWeight: 700, flexShrink: 0 }}>/</span>
                <span style={{ fontFamily: 'monospace', fontSize: 12 }}>{cmd.label}</span>
                <span style={{ color: 'var(--text-secondary, #64748b)', fontSize: 11 }}>{cmd.description}</span>
              </div>
            ))}
          </div>
        )}

        <input ref={fileInputRef} type="file" multiple accept={supportsImages ? 'image/*,.txt,.md,.json,.csv,.ts,.tsx,.js,.jsx,.py,.rb,.go,.rs,.java,.c,.cpp,.h,.css,.html,.xml,.yaml,.yml,.toml,.ini,.sh,.sql,.log,.pdf' : '.txt,.md,.json,.csv,.ts,.tsx,.js,.jsx,.py,.rb,.go,.rs,.java,.c,.cpp,.h,.css,.html,.xml,.yaml,.yml,.toml,.ini,.sh,.sql,.log,.pdf'} style={{ display: 'none' }} onChange={handleFileChange} />

        {/* H-8 修复: 使用 MUI InputBase 替代原生 textarea — 遵循 MUI 强制使用规范 */}
        {/* @see standard/开发规范.md — MUI 强制使用、禁止自写 UI 组件 */}
        <InputBase
          inputRef={textareaRef}
          data-chat-input
          value={input}
          onChange={handleChange}
          onKeyDown={handleKeyDown}
          onPaste={handlePaste}
          placeholder={configLoaded ? (toolsReady ? '输入消息... (Cmd/Ctrl+Enter 发送, Cmd/Ctrl+Shift+Enter 换行, / 命令)' : '工具加载中...') : '正在加载配置...'}
          disabled={isStreaming || !configLoaded || !toolsReady}
          multiline
          rows={1}
          sx={{
            width: '100%',
            color: 'inherit',
            fontSize: 14,
            lineHeight: '28px',
            fontFamily: 'inherit',
            '& .MuiInputBase-input': {
              padding: 0,
              resize: 'none',
              minHeight: 42,
              maxHeight: 160,
            },
            '&::before, &::after': {
              display: 'none',
            },
          }}
        />

        <Stack direction="row" sx={{ mt: 1, minHeight: 32, justifyContent: 'space-between', alignItems: 'center' }}>
          {/* 左侧：附件按钮 */}
          <Tooltip title={supportsImages ? '附加文件（图片/文本/代码）' : '附加文件（文本/代码）— DeepSeek 不支持图片'}>
            <IconButton size="small" sx={{ color: 'text.secondary' }} onClick={handleFileSelect}>
              <Paperclip size={14} />
            </IconButton>
          </Tooltip>

          {/* 右侧：发送按钮 */}
          <Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
            {isStreaming ? (
              <Button variant="contained" color="error" size="small" onClick={handleAbort} sx={{ height: 28, fontSize: 12 }}>
                <Square size={12} style={{ marginRight: 6 }} /> 中断
              </Button>
            ) : (
              <Button variant="contained" size="small" onClick={handleSend} disabled={(!input.trim() && attachments.length === 0) || !configLoaded || !toolsReady} sx={{ height: 28, fontSize: 12, opacity: (input.trim() || attachments.length > 0) && configLoaded && toolsReady ? 1 : 0.5 }}>
                <Send size={12} style={{ marginRight: 6 }} /> 发送
              </Button>
            )}
          </Stack>
        </Stack>
      </Paper>
    </Box>
  );
}

// ===== 附件预览组件 =====

function AttachmentPreview({ attachment, onRemove }: { attachment: Attachment; onRemove: () => void }) {
  const { type, file, preview } = attachment;

  if (type === 'image' && preview) {
    return (
      <Box sx={{ position: 'relative', width: 64, height: 64, borderRadius: 1.5, overflow: 'hidden', border: '1px solid', borderColor: 'divider', flexShrink: 0 }}>
        <Box component="img" src={preview} alt={file.name} sx={{ width: '100%', height: '100%', objectFit: 'cover' }} />
        <IconButton size="small" onClick={onRemove} sx={{ position: 'absolute', top: 0, right: 0, width: 20, height: 20, bgcolor: 'rgba(0,0,0,0.6)', '&:hover': { bgcolor: 'rgba(0,0,0,0.8)' }, color: '#fff' }}>
          <X size={12} />
        </IconButton>
        <Typography variant="caption" sx={{ position: 'absolute', bottom: 0, left: 0, right: 0, bgcolor: 'rgba(0,0,0,0.6)', color: '#fff', fontSize: 9, textAlign: 'center', py: 0.25, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', px: 0.5 }}>
          {file.name}
        </Typography>
      </Box>
    );
  }

  // 文本文件 / 其他文件
  return (
    <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, px: 1.5, py: 1, borderRadius: 1.5, bgcolor: 'secondary.main', border: '1px solid', borderColor: 'divider', maxWidth: 200, flexShrink: 0 }}>
      {type === 'text' ? <FileText size={16} style={{ color: '#22d3ee', flexShrink: 0 }} /> : <ImageIcon size={16} style={{ color: '#8b8fa7', flexShrink: 0 }} />}
      <Box sx={{ minWidth: 0, flex: 1 }}>
        <Typography variant="caption" sx={{ display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontSize: 11, color: 'text.primary' }}>{file.name}</Typography>
        <Typography variant="caption" sx={{ fontSize: 9, color: 'text.secondary' }}>{formatFileSize(file.size)}</Typography>
      </Box>
      <IconButton size="small" onClick={onRemove} sx={{ width: 20, height: 20, flexShrink: 0, color: 'text.secondary' }}>
        <X size={12} />
      </IconButton>
    </Box>
  );
}

/**
 * 压缩图片：限制最大边长，输出 JPEG base64。
 * 小图(< 500KB 且尺寸未超标)直接返回原图，避免重复编码。
 *
 * @see AgnesAIDesktop — 已验证 Agnes AI 可正常处理压缩后的图片
 */
function compressImage(
  dataUri: string,
  maxSize: number,
  quality: number,
): Promise<string> {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => {
      let { width, height } = img;
      if (width <= maxSize && height <= maxSize && dataUri.length < 500 * 1024) {
        resolve(dataUri);
        return;
      }
      if (width > height) {
        if (width > maxSize) { height = Math.round((height * maxSize) / width); width = maxSize; }
      } else {
        if (height > maxSize) { width = Math.round((width * maxSize) / height); height = maxSize; }
      }
      const canvas = document.createElement('canvas');
      canvas.width = width;
      canvas.height = height;
      const ctx = canvas.getContext('2d')!;
      ctx.drawImage(img, 0, 0, width, height);
      resolve(canvas.toDataURL('image/jpeg', quality));
    };
    img.onerror = () => reject(new Error('图片加载失败'));
    img.src = dataUri;
  });
}
