/**
 * JsonActionParser — 基于 JSON 输出格式的 VLM 动作解析器
 * 适配 volcengine / volces-uitars 等供应商
 *
 * 设计要点：
 *  - 永不抛出异常，失败一律返回 { type: 'failed', reason: 'parse-error: ...' }
 *  - 无内部状态，所有方法幂等
 *  - JSON 提取使用括号计数扫描（非 regex），正确处理嵌套对象
 */
import type { ActionStep, DesktopTask, AdapterContext } from '../types.js';
import type { Parser } from './parser.js';

/** 合法的 ActionStep type 集合 */
const VALID_TYPES = new Set([
  'click',
  'hotkey',
  'clipboard-write',
  'type',
  'wait',
  'mention',
  'finished',
  'failed',
]);

/**
 * 去除 markdown 代码围栏
 * 支持 ```json ... ``` 和 ``` ... ```
 */
function stripMarkdownFence(raw: string): string {
  const trimmed = raw.trim();
  // 匹配 ```json 或 ``` 开头的围栏
  const fenceMatch = trimmed.match(/^```(?:json)?\s*\n?([\s\S]*?)\n?```\s*$/);
  if (fenceMatch) {
    return fenceMatch[1].trim();
  }
  return trimmed;
}

/**
 * 括号计数扫描：从字符串中提取第一个完整 {...} 子串
 * 不使用正则，正确处理嵌套对象和字符串内的花括号
 * 返回提取到的子串，若未找到则返回 null
 */
function extractFirstJsonObject(text: string): string | null {
  let start = -1;
  let depth = 0;
  let inString = false;
  let escape = false;

  for (let i = 0; i < text.length; i++) {
    const ch = text[i];

    if (escape) {
      // 上一个字符是反斜杠，跳过转义字符
      escape = false;
      continue;
    }

    if (ch === '\\' && inString) {
      escape = true;
      continue;
    }

    if (ch === '"') {
      inString = !inString;
      continue;
    }

    // 在字符串内部时忽略括号
    if (inString) continue;

    if (ch === '{') {
      if (depth === 0) {
        start = i;
      }
      depth++;
    } else if (ch === '}') {
      if (depth > 0) {
        depth--;
        if (depth === 0 && start !== -1) {
          // 找到完整的 JSON 对象
          return text.slice(start, i + 1);
        }
      }
    }
  }

  return null;
}

/**
 * 尝试解析 JSON，先整体解析，失败则用括号扫描提取子串
 * 返回解析结果对象或 null
 */
function tryParseJson(raw: string): Record<string, unknown> | null {
  // 步骤1：先去掉 markdown 围栏
  const stripped = stripMarkdownFence(raw);

  // 步骤2：尝试直接解析
  try {
    const parsed = JSON.parse(stripped);
    if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
      return parsed as Record<string, unknown>;
    }
  } catch {
    // 解析失败，进入步骤3
  }

  // 步骤3：括号计数扫描提取第一个 JSON 对象
  const extracted = extractFirstJsonObject(stripped);
  if (extracted) {
    try {
      const parsed = JSON.parse(extracted);
      if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
        return parsed as Record<string, unknown>;
      }
    } catch {
      // 提取的子串仍无法解析
    }
  }

  // 也尝试从原始 raw 中扫描（避免 markdown strip 误删内容）
  const extractedFromRaw = extractFirstJsonObject(raw);
  if (extractedFromRaw && extractedFromRaw !== extracted) {
    try {
      const parsed = JSON.parse(extractedFromRaw);
      if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
        return parsed as Record<string, unknown>;
      }
    } catch {
      // 仍失败
    }
  }

  return null;
}

export class JsonActionParser implements Parser {
  /**
   * 构建系统提示词，告知模型输出 JSON 动作格式
   * 描述所有合法的 action type 及其字段要求
   */
  buildSystemPrompt(task: DesktopTask, _ctx?: AdapterContext): string {
    return `你是一个桌面自动化操作助手，控制应用 "${task.appId}" 完成任务：${task.actionType}。

每次只输出一个 JSON 对象，不要 markdown 代码块，不要任何额外文字。

可用的动作格式如下：

点击：{"type":"click","x":数字,"y":数字}
按键：{"type":"hotkey","key":"ctrl+v"}
输入文本：{"type":"type","text":"文本内容"}
写入剪贴板：{"type":"clipboard-write","text":"文本内容"}
等待：{"type":"wait","ms":毫秒数}
完成任务：{"type":"finished"}
任务失败：{"type":"failed","reason":"失败原因"}

可选字段 thought 可附加在任意动作中，用于说明操作意图。
只输出一个 JSON 对象，不要 markdown 代码块。`;
  }

  /**
   * 解析 VLM 原始输出为 ActionStep
   * 失败时返回 { type: 'failed', reason: 'parse-error: ...' }，永不抛出
   */
  parseAction(raw: string): ActionStep {
    // 解析失败的兜底函数
    const fail = (suffix: string): ActionStep => ({
      type: 'failed',
      reason: `parse-error: ${raw.slice(0, 200)}${suffix}`,
    });

    // 尝试解析 JSON
    const obj = tryParseJson(raw);
    if (!obj) {
      return fail('');
    }

    const type = obj['type'];

    // 验证 type 字段存在且为字符串
    if (typeof type !== 'string') {
      return fail('');
    }

    // 验证 type 是已知类型
    if (!VALID_TYPES.has(type)) {
      return {
        type: 'failed',
        reason: `unknown-type: ${type}`,
      };
    }

    // 按 type 进行字段验证并构造 ActionStep
    switch (type) {
      case 'click': {
        const x = obj['x'];
        const y = obj['y'];
        if (typeof x !== 'number' || typeof y !== 'number') {
          return { type: 'failed', reason: 'missing: click requires numeric x and y' };
        }
        const step: ActionStep = { type: 'click', x, y };
        if (typeof obj['thought'] === 'string') {
          (step as any).thought = obj['thought'];
        }
        return step;
      }

      case 'hotkey': {
        const key = obj['key'];
        if (typeof key !== 'string') {
          return { type: 'failed', reason: 'missing: hotkey requires string key' };
        }
        const step: ActionStep = { type: 'hotkey', key };
        if (typeof obj['thought'] === 'string') {
          (step as any).thought = obj['thought'];
        }
        return step;
      }

      case 'clipboard-write': {
        const text = obj['text'];
        if (typeof text !== 'string') {
          return { type: 'failed', reason: 'missing: clipboard-write requires string text' };
        }
        return { type: 'clipboard-write', text };
      }

      case 'type': {
        const text = obj['text'];
        if (typeof text !== 'string') {
          return { type: 'failed', reason: 'missing: type requires string text' };
        }
        const step: ActionStep = { type: 'type', text };
        if (typeof obj['thought'] === 'string') {
          (step as any).thought = obj['thought'];
        }
        return step;
      }

      case 'wait': {
        const ms = obj['ms'];
        if (typeof ms !== 'number') {
          return { type: 'failed', reason: 'missing: wait requires numeric ms' };
        }
        return { type: 'wait', ms };
      }

      case 'mention': {
        const wxid = obj['wxid'];
        if (typeof wxid !== 'string') {
          return { type: 'failed', reason: 'missing: mention requires string wxid' };
        }
        return { type: 'mention', wxid };
      }

      case 'finished': {
        const step: ActionStep = { type: 'finished' };
        if (typeof obj['thought'] === 'string') {
          (step as any).thought = obj['thought'];
        }
        return step;
      }

      case 'failed': {
        const reason = obj['reason'];
        if (typeof reason !== 'string') {
          return { type: 'failed', reason: 'parse-error: failed action missing reason' };
        }
        const step: ActionStep = { type: 'failed', reason };
        if (typeof obj['thought'] === 'string') {
          (step as any).thought = obj['thought'];
        }
        return step;
      }

      default:
        // 不应到达此处，但类型安全兜底
        return { type: 'failed', reason: `unknown-type: ${type}` };
    }
  }

  /**
   * 构建验证提示词，请模型确认某个屏幕状态
   */
  buildVerifyPrompt(question: string): string {
    return `请观察当前屏幕截图并回答以下问题：

${question}

只输出一个 JSON 对象，格式为：{"verified": true, "reason": "说明原因"} 或 {"verified": false, "reason": "说明原因"}
不要输出 markdown 代码块，不要额外文字。`;
  }

  /**
   * 解析验证回答为布尔值
   * 优先解析 JSON；失败则自然语言回退；彻底失败返回 false
   */
  parseVerify(raw: string): boolean {
    // 步骤1：尝试 JSON 解析
    const obj = tryParseJson(raw);
    if (obj && typeof obj['verified'] === 'boolean') {
      return obj['verified'];
    }

    // 步骤2：自然语言回退检测
    const lower = raw.toLowerCase();

    // 正向信号
    if (
      lower.includes('yes') ||
      raw.includes('是') ||
      lower.includes('"verified": true') ||
      lower.includes('"verified":true') ||
      lower.includes('true')
    ) {
      // 排除同时包含否定词的情况
      if (
        !lower.includes('no') &&
        !raw.includes('否') &&
        !raw.includes('不是') &&
        !raw.includes('不') &&
        !lower.includes('false') &&
        !lower.includes('"verified": false') &&
        !lower.includes('"verified":false')
      ) {
        return true;
      }
    }

    // 负向信号
    if (
      lower.includes('no') ||
      raw.includes('否') ||
      raw.includes('不是') ||
      lower.includes('false') ||
      lower.includes('"verified": false') ||
      lower.includes('"verified":false')
    ) {
      return false;
    }

    // "是" 单独判断（避免被上面的 no 条件干扰）
    if (raw.includes('是')) {
      if (!raw.includes('不是') && !raw.includes('否')) {
        return true;
      }
    }

    // 保守返回 false
    return false;
  }
}
