// ============================================================
// ModelInputForm — 模型输入表单（纯布局外壳）
//
// 布局：Header（模型信息+meta） → Body（fields.map 渲染） → Footer（标签+重置+提交）
//
// 与旧版的区别：
//   - 控件渲染逻辑已提取到 controls/ 组件体系
//   - param_schema 解析已提取到 useModelUI Hook
//   - 字段间条件依赖（constraints_config.requires）自动处理禁用
//   - 本组件只负责：布局、Form 容器、提交逻辑、依赖禁用
// ============================================================

import {useState, useLayoutEffect, useMemo, useCallback, useRef, useEffect} from 'react';
import {
   Form,
   Input,
   Button,
   Typography,
   Empty,
   Tag,
   Space,
   theme as antTheme,
   message,
   Modal,
   Dropdown,
} from 'antd';
import type { FormInstance } from 'antd';
import { SendOutlined, ClockCircleOutlined, ReloadOutlined } from '@ant-design/icons';
import type { UploadFile } from 'antd/es/upload';

import { useHomeContext } from '@/modules/home/stores/HomeContext';
import { useAppContext } from '@/app/providers/AppProvider';
import { useSubmitTask } from '@/shared/hooks/use-api';
import { emit, EVENTS } from '@/shared/utils/bus';
import { OutputTypeMap } from '@/modules/home/controllers/task-api';
import type { OutputTypeString, TaskInfoItemResponseBody } from '@/modules/home/controllers/task-api';
import { useModelUI, isValueEmpty, type UIFieldConfig } from '../../hooks/useModelUI';
import {
   calculateCost,
   formatYuan,
   type ModelDefinition,
   type ModelPricingConfig,
} from '@/shared/utils/pricing';
import { getTextInputMenuItems } from '../../controllers/menus';
import type { ContextMenuItems } from '@/shared/components/ContextMenu';
import {
   execUndo, execRedo, execCut, execCopy, execPaste,
   execClear, execSelectAll, execIncreaseFont, execDecreaseFont,
   execResetFont, hasTextSelection, isInputEmpty, getFontSizeLevel,
} from '../../controllers/menus/text-input-ops';

const { Text, Title } = Typography;

// ---------- 工具 ----------

/**
 * Form.Item 的 getValueFromEvent，兼容两种 onChange 参数形态：
 * 1. Upload 组件的 DOM 事件：{ fileList: UploadFile[] } → 提取 fileList
 * 2. enable_switch 的裸值：undefined / [] / [{...}] → 直接透传
 */
function handleFileWidgetValue(e: unknown): unknown {
   if (e && typeof e === 'object' && 'fileList' in e) {
      return (e as { fileList: unknown }).fileList;
   }
   return e;
}

// ---------- 依赖字段包装组件 ----------

/**
 * DependentFormItem — 为单个字段处理条件依赖禁用。
 *
 * 通过 Form.useWatch 监听 dependsOn 列表中的字段值，
 * 任一依赖为空时自动禁用本字段（注入 disabled: true 到 widgetConfig）。
 *
 * 每个 DependentFormItem 实例的 dependsOn 长度固定，
 * 切换模型时通过外层 key 变化自动重新挂载，满足 React hooks 规则。
 */
function DependentFormItem({
   field,
   form,
   value,
   onChange,
   disabled: formItemDisabled,
}: {
   field: UIFieldConfig;
   form: FormInstance;
   /** Form.Item 通过 React.cloneElement 注入，必须透传给 widget */
   value?: unknown;
   onChange?: (value: unknown) => void;
   disabled?: boolean;
}) {
   // Form.useWatch 必须在组件顶层无条件调用（不能放 .map() 或条件分支中）。
   // dependsOn 长度由 param_schema 约束（当前 API 最多 2 个），
   // 预留 3 个固定槽位覆盖未来扩展；无依赖时 watch 空串 → 返回 undefined。
   const deps = field.dependsOn || [];
   const d0 = Form.useWatch(deps[0] || '', form);
   const d1 = Form.useWatch(deps[1] || '', form);
   const d2 = Form.useWatch(deps[2] || '', form);
   const depValues = [d0, d1, d2].slice(0, deps.length);

   const isDepDisabled =
      deps.some((_, i) => isValueEmpty(depValues[i]));

   const effectiveDisabled = formItemDisabled || isDepDisabled;
   const effectiveConfig = effectiveDisabled
      ? { ...field.widgetConfig, disabled: true }
      : field.widgetConfig;

   const Component = field.component;
   return (
      <Component
         value={value}
         onChange={onChange}
         disabled={effectiveDisabled}
         config={effectiveConfig}
      />
   );
}

/** 稳定空数组引用，避免 useMemo 重建 */
const EMPTY_ARRAY: never[] = [];
/** 稳定空对象引用，避免每次渲染创建新 {} 导致 useMemo 失效 */
const EMPTY_OBJ: Record<string, unknown> = {};

/**
 * 从自由格式 JSON 中安全读取数字（兼容 int / float / string）
 */
function safeNum(v: unknown): number | null {
   if (v === null || v === undefined) return null;
   if (typeof v === 'number' && !Number.isNaN(v)) return v;
   if (typeof v === 'string') {
      const n = Number(v.trim());
      return Number.isNaN(n) ? null : n;
   }
   return null;
}

// ---------- 主组件 ----------

export function ModelInputForm() {
   const { token } = antTheme.useToken();
   const { isLoggedIn } = useAppContext();
   const { selectedModel } = useHomeContext();
   const [form] = Form.useForm();
   const [taskTag, setTaskTag] = useState('');

   // 跟踪表单当前值（用于动态计价，每次字段变化时更新）
   const [currentValues, setCurrentValues] = useState<Record<string, unknown>>({});

   // 提交突变 Hook
   const { execute: submitTask, loading: submitting, error: submitError, errorMessage } = useSubmitTask({
      onSuccess: useCallback((_data: TaskInfoItemResponseBody) => {
         void message.success('任务已提交');
         emit(EVENTS.TASK_SUBMITTED);
      }, []),
   });

   // -------- 提交防抖 & 二次确认 Ref --------

   /** 上次点击提交按钮的时间戳（毫秒），用于 2s 防抖节流 */
   const lastClickTimeRef = useRef(0);
   /** 已成功提交次数，用于判断是否需要"再次提交确认"弹窗 */
   const submitCountRef = useRef(0);
   /** 最新错误信息的 ref（避免 doSubmit useCallback 闭包过期） */
   const errorMessageRef = useRef(errorMessage);
   useEffect(() => {
      errorMessageRef.current = errorMessage;
   }, [errorMessage]);

   /** 最新 Error 对象的 ref（用于检查 error.displayed 标记） */
   const submitErrorRef = useRef(submitError);
   useEffect(() => {
      submitErrorRef.current = submitError;
   }, [submitError]);

   // ======== useModelUI：param_schema → UIFieldConfig[] ========

   const fields: UIFieldConfig[] = useModelUI(
      selectedModel?.param_schema || EMPTY_ARRAY,
      (selectedModel?.constraints_config as Record<string, unknown>) || EMPTY_OBJ,
   );

   // 构建初始值（用作 setFieldsValue 的数据源，
   // antd Form 在有外部 form 实例时会忽略 initialValues prop）
   const initialValues = useMemo(() => {
      const iv: Record<string, unknown> = {};
      fields.forEach((f) => {
         if (f.defaultValue !== undefined && f.defaultValue !== null) {
            iv[f.name] = f.defaultValue;
         }
      });
      return iv;
   }, [fields]);


   // 选中模型时加载默认值（useLayoutEffect 在 DOM 更新后、浏览器绘制前执行，避免闪烁）
   // antd 有外部 form 实例时 initialValues prop 被忽略，必须手动 setFieldsValue
   useLayoutEffect(() => {
      if (selectedModel && Object.keys(initialValues).length > 0) {
         form.setFieldsValue(initialValues);
         setTaskTag('');
         // 同步价格计算：切换模型时立即用默认值更新预估
         setCurrentValues(initialValues);
      } else if (!selectedModel) {
         setCurrentValues({});
      }
   }, [selectedModel?.id, initialValues, selectedModel, form]);

   // 重置参数（回到默认值 + 清空标签）
   const handleReset = useCallback(() => {
      form.resetFields();
      form.setFieldsValue(initialValues);
      setTaskTag('');
      setCurrentValues(initialValues);
   }, [form, initialValues]);

   // ======== 提交 ========

   /**
    * 实际提交逻辑（在防抖检查和二次确认之后执行）。
    * 用 useCallback 稳定化，避免 handleSubmit 因闭包过期而读到旧 fields/values。
    */
   const doSubmit = useCallback(async () => {
      if (!selectedModel) return;

      let values: Record<string, unknown>;
      try {
         values = await form.validateFields();
      } catch {
         // 表单校验失败 → antd 自动展示字段错误，静默返回
         return;
      }

      const params: Record<string, string> = {};

      for (const field of fields) {
         const value = values[field.name];
         if (value === undefined || value === null) continue;

         if (field.isFileWidget) {
            const fileList = value as UploadFile[];
            const urls = fileList
               .filter((f) => f.status === 'done')
               .map((f) => f.response?.url || f.url || f.name)
               .filter(Boolean) as string[];
            if (urls.length > 0) {
               params[field.name] = urls.length === 1 ? urls[0] : JSON.stringify(urls);
            }
            continue;
         }

         // seedance2Content：将内部 JSON 值序列化为 API 要求的 ContentItem[]
         if (field.widgetName === 'seedance2Content') {
            try {
               const cv = JSON.parse(value as string) as {
                  text?: string;
                  imageUrls?: string[];
                  videoUrls?: string[];
                  audioUrls?: string[];
               };
               const items: Record<string, unknown>[] = [];
               if (cv.text) {
                  items.push({ type: 'text', text: cv.text });
               }
               for (const url of (cv.imageUrls || [])) {
                  items.push({ type: 'image_url', role: 'reference_image', image_url: { url } });
               }
               for (const url of (cv.videoUrls || [])) {
                  items.push({ type: 'video_url', role: 'reference_video', video_url: { url } });
               }
               for (const url of (cv.audioUrls || [])) {
                  items.push({ type: 'audio_url', role: 'reference_audio', audio_url: { url } });
               }
               if (items.length > 0) {
                  params[field.name] = JSON.stringify(items);
               }
            } catch { /* ignore parse error */ }
            continue;
         }

         if (typeof value === 'boolean') {
            params[field.name] = String(value);
            continue;
         }

         params[field.name] = String(value);
      }

      // useAsyncMutation.execute 内部 catch 所有错误，返回 undefined 表示失败
      const result = await submitTask({
         model_id: selectedModel.id,
         params,
         ...(taskTag.trim() ? { tags: [taskTag.trim()] } : {}),
      });

      if (result !== undefined) {
         submitCountRef.current += 1;
      } else {
         // 余额不足 / 软件到期等错误已由 request.ts 拦截器 → 事件总线 → Modal 展示，
         // 此处检查 error.displayed 标记，避免 message.error toast 重复提示
         const errObj = submitErrorRef.current;
         const displayed = errObj != null && (errObj as unknown as Record<string, unknown>).displayed === true;
         if (!displayed) {
            message.error(errorMessageRef.current || '任务提交失败，请重试');
         }
      }
   }, [selectedModel, form, fields, taskTag, submitTask]);

   /** 提交按钮点击入口：登录校验 → 防抖节流 → 二次确认 → 执行提交 */
   const handleSubmit = useCallback(() => {
      if (!isLoggedIn) {
         message.warning('请先登录后再提交任务');
         return;
      }
      if (!selectedModel) return;

      // ---- 防抖节流：2s 内重复点击 ----
      const now = Date.now();
      if (now - lastClickTimeRef.current < 2000) {
         message.warning('你点击的太快了');
         return;
      }
      lastClickTimeRef.current = now;

      // ---- 已提交过 → 二次确认弹窗 ----
      if (submitCountRef.current > 0) {
         Modal.confirm({
            title: '确认再次提交',
            content: '您已提交，确认要再次提交吗？',
            okText: '确认提交',
            cancelText: '取消',
            onOk: () => {
               // 弹窗确认后重置节流时间戳，确保确认提交不被防抖拦截
               lastClickTimeRef.current = 0;
               void doSubmit();
            },
         });
         return;
      }

      void doSubmit();
   }, [isLoggedIn, selectedModel, doSubmit]);

   // ======== 动态预估消费金额（必须在所有提前返回之前） ========

   const estimatedCostDisplay: string = useMemo(() => {
      const rawCfg = selectedModel?.pricing_config;
      if (!rawCfg) return '消费';

      const cfg = rawCfg as unknown as ModelPricingConfig;

      // ═══ 第一层：完整动态计价（覆盖全部 6 种模式） ═══
      const modelDef: ModelDefinition = {
         id: selectedModel.id,
         name: selectedModel.name,
         pricing_config: cfg,
      };

      const cost = calculateCost(modelDef, currentValues);
      if (cost !== null && cost !== undefined) {
         if (cost === 0) return '消费 ¥0（免费）';
         return `消费 ¥${formatYuan(cost)}`;
      }

      // ═══ 第二层：兜底提取基础价格（calculateCost 失败时） ═══
      const rawObj = cfg.raw as Record<string, unknown> | undefined;

      const fallbackPrice =
         safeNum(cfg.price) ??
         safeNum(cfg.price_cent != null ? cfg.price_cent / 100 : null) ??
         safeNum(rawObj?.price) ??
         safeNum(rawObj?.base_price);

      const unitName =
         (rawObj?.unit_name as string) ||
         (rawObj?.unit as string) ||
         undefined;

      if (fallbackPrice !== null) {
         const formatted = formatYuan(fallbackPrice);
         if (unitName) return `消费 ¥${formatted}/${unitName}`;
         if (fallbackPrice > 0) return `消费约 ¥${formatted}`;
         return `消费 ¥${formatted}`;
      }

      if (cfg.price_per_k != null) return `消费 ¥${formatYuan(cfg.price_per_k)}/千字符`;
      if (cfg.price_per_k_cent != null) return `消费 ¥${formatYuan(cfg.price_per_k_cent / 100)}/千字符`;

      const modeLabel = cfg.mode || cfg.price_type || cfg.billing_mode;
      if (modeLabel) return `消费（${modeLabel}）`;

      return '消费';
   }, [selectedModel, currentValues]);

   // ── 文本输入框右键菜单（手动控制，仅 TEXTAREA / input[type=text] / contentEditable 上触发）──

   const [textMenu, setTextMenu] = useState<{ x: number; y: number } | null>(null);

   function isTextEditor(el: HTMLElement): boolean {
       const tag = el.tagName;
       if (tag === 'TEXTAREA' || el.isContentEditable) return true;
       if (tag === 'INPUT') {
           const type = (el as HTMLInputElement).type;
           return type === 'text' || type === '';
       }
       return false;
   }

   const handleBodyContextMenu = useCallback((e: React.MouseEvent) => {
       const target = e.target as HTMLElement;
       if (!isTextEditor(target)) return;
       e.preventDefault();
       e.stopPropagation();
       setTextMenu({ x: e.clientX, y: e.clientY });
   }, []);

   // 点击菜单外部关闭
   useEffect(() => {
       if (!textMenu) return;
       const handler = (e: MouseEvent) => {
           const menuEl = document.querySelector('.ant-dropdown');
           if (menuEl && !menuEl.contains(e.target as Node)) {
               setTextMenu(null);
           }
       };
       const timer = setTimeout(() => {
           document.addEventListener('mousedown', handler, true);
       }, 0);
       return () => {
           clearTimeout(timer);
           document.removeEventListener('mousedown', handler, true);
       };
   }, [textMenu]);

   /** ContextMenuItems → AntD MenuProps['items'] */
   const toAntdMenu = useCallback((items: ContextMenuItems) =>
       items.map((item) => {
           if (item === 'divider') return { type: 'divider' as const };
           return { key: item.key, label: item.label, onClick: item.onClick, disabled: item.disabled, danger: item.danger };
       }), []);

   const textMenuAntdItems = toAntdMenu(getTextInputMenuItems({
       hasSelection: hasTextSelection(),
       isEmpty: isInputEmpty(),
       fontSizeLevel: getFontSizeLevel(),
       onUndo: execUndo, onRedo: execRedo, onCut: execCut, onCopy: execCopy,
       onPaste: execPaste, onClear: execClear, onSelectAll: execSelectAll,
       onIncreaseFont: execIncreaseFont, onDecreaseFont: execDecreaseFont, onResetFont: execResetFont,
   }));

   // ======== 未选择模型 ========

   if (!selectedModel) {
      return (
         <div
            style={{
               display: 'flex',
               alignItems: 'center',
               justifyContent: 'center',
               height: '100%',
            }}
         >
            <Empty description="请在左侧选择一个模型" image={Empty.PRESENTED_IMAGE_SIMPLE} />
         </div>
      );
   }

   // ======== 提取 meta 信息 ========

   const metaConfig = (selectedModel.meta_config || {}) as Record<string, unknown>;
   const uiConfig = (selectedModel.ui_config || {}) as Record<string, unknown>;

   const outputType = metaConfig.output_type as OutputTypeString | undefined;
   const estimatedDuration = metaConfig.estimated_duration_seconds as number | undefined;
   const badge = uiConfig.badge as string | undefined;
   const group = uiConfig.group as string | undefined;
   const helpUrl = metaConfig.help_url as string | undefined;


   return (
      <div
         style={{
            height: '100%',
            display: 'flex',
            flexDirection: 'column',
            overflow: 'hidden',
         }}
      >
         {/* ======== Header：模型信息 ======== */}
         <div
            style={{
               padding: '16px 20px',
               borderBottom: `1px solid ${token.colorBorderSecondary}`,
               flexShrink: 0,
            }}
         >
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
               {badge && <span style={{ fontSize: 20 }}>{badge}</span>}
               <Title level={4} style={{ margin: 0, fontSize: 18 }}>
                  {selectedModel.name}
               </Title>
            </div>

            <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
               <Text type="secondary" style={{ fontSize: 12 }}>
                  {selectedModel.provider_name}
                  {group
                     ? ` · ${group}`
                     : selectedModel.category_name
                        ? ` · ${selectedModel.category_name}`
                        : ''}
               </Text>
            </div>

            <Space size={4} style={{ marginTop: 8 }} wrap>
               {outputType && (
                  <Tag color="blue" style={{ fontSize: 11, lineHeight: '18px' }}>
                     {OutputTypeMap[outputType] || outputType}
                  </Tag>
               )}
               {estimatedDuration !== undefined && (
                  <Tag
                     icon={<ClockCircleOutlined />}
                     color="default"
                     style={{ fontSize: 11, lineHeight: '18px' }}
                  >
                     约 {estimatedDuration} 秒
                  </Tag>
               )}
               {helpUrl && (
                  <a
                     href={helpUrl}
                     target="_blank"
                     rel="noopener noreferrer"
                     style={{ fontSize: 11 }}
                  >
                     使用帮助 ↗
                  </a>
               )}
            </Space>
         </div>

         {/* ======== Body：参数列表（useModelUI 驱动） ======== */}
         <div
            style={{ flex: 1, overflow: 'auto', padding: '16px 20px' }}
            onContextMenu={handleBodyContextMenu}
         >
            {fields.length === 0 ? (
               <Empty
                  description="该模型无需额外参数"
                  image={Empty.PRESENTED_IMAGE_SIMPLE}
                  style={{ marginTop: 24 }}
               />
            ) : (
               <Form
                  form={form}
                  layout="vertical"
                  onValuesChange={(_, allValues) => setCurrentValues(allValues)}
               >
                  {fields.map((field) => (
                     <Form.Item
                        key={`${selectedModel.id}-${field.name}`}
                        name={field.name}
                        label={
                           <span>
                              <Text strong={field.must}>{field.label}</Text>
                              {field.tips && (
                                 <Text
                                    type="secondary"
                                    style={{ fontSize: 12, marginLeft: 6 }}
                                 >
                                    {field.tips.length > 60
                                       ? `${field.tips.slice(0, 60)}...`
                                       : field.tips}
                                 </Text>
                              )}
                           </span>
                        }
                        rules={
                           field.must
                              ? [{ required: true, message: `请输入${field.label}` }]
                              : undefined
                        }
                        valuePropName={field.valuePropName}
                        {...(field.isFileWidget
                           ? { getValueFromEvent: handleFileWidgetValue }
                           : {})}
                     >
                        <DependentFormItem field={field} form={form} />
                     </Form.Item>
                  ))}
               </Form>
            )}
         </div>

         {/* ======== Footer：标签 + 重置 + 提交 ======== */}
         <div
            style={{
               padding: '12px 20px',
               borderTop: `1px solid ${token.colorBorderSecondary}`,
               flexShrink: 0,
               display: 'flex',
               alignItems: 'center',
               gap: 8,
            }}
         >
            <Input
               placeholder="自定义任务标签（可选）"
               value={taskTag}
               onChange={(e) => setTaskTag(e.target.value)}
               allowClear
               maxLength={50}
               style={{ flex: 1, minWidth: 0 }}
            />

            <Button
               icon={<ReloadOutlined />}
               onClick={handleReset}
               disabled={!selectedModel}
               title="重置参数"
            />

            <Button
               type="primary"
               icon={<SendOutlined />}
               onClick={() => void handleSubmit()}
               loading={submitting}
               disabled={!isLoggedIn || !selectedModel}
               size="large"
               style={{
                  background: token.colorPrimary,
                  height: 40,
                  fontSize: 14,
                  whiteSpace: 'nowrap',
                  flexShrink: 0,
               }}
            >
               {estimatedCostDisplay}
            </Button>
         </div>

         {/* 文本输入框悬浮右键菜单（仅 input/textarea 上触发） */}
         <Dropdown
            open={!!textMenu}
            onOpenChange={(open) => { if (!open) setTextMenu(null); }}
            menu={{ items: textMenuAntdItems, onClick: () => setTextMenu(null) }}
            trigger={[]}
         >
            <div
                onContextMenu={(e) => { e.preventDefault(); setTextMenu(null); }}
                style={{ position: 'fixed', left: textMenu?.x ?? -9999, top: textMenu?.y ?? -9999, width: 1, height: 1 }}
            />
         </Dropdown>
      </div>
   );
}
