#!/usr/bin/env node
'use strict';

const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');

const DEFAULT_PROMPT = `你是专业汽车环车详细检测专家。以下是汽车视频中抽取的帧画面，请对每一帧进行全面的环车详细检测分析。

你需要同时完成以下所有检测项目：

## 一、车身旧伤检测
仔细检查每一帧画面中车身的所有可见部位，识别划痕、凹陷、裂纹、掉漆、锈蚀等损伤。

## 二、车辆关键信息识别
请在帧画面中识别车辆品牌型号、前后车牌号、挡风玻璃VIN、铭牌VIN、仪表盘总里程、4条胎胎压、4个轮胎纹理健康度、4个轮胎品牌型号规格。

要求：
1. 仔细检查每一帧画面，不要遗漏。
2. 不可见字段填null，不要猜测。
3. 部分遮挡字符用"?"。
4. 不要把反光、阴影、污渍、压缩噪声虚报为旧伤。
5. 只输出合法JSON。

返回格式：
{
  "has_damage": true,
  "damages": [
    {
      "time_second": 12.4,
      "location": "左前翼子板",
      "type": "划痕",
      "severity": "轻微",
      "description": "详细描述"
    }
  ],
  "vehicle_info": {
    "vehicle_brand_model": null,
    "front_plate": null,
    "rear_plate": null,
    "windshield_vin": null,
    "nameplate_vin": null,
    "odometer_km": null,
    "tire_pressure": {"left_front": null, "right_front": null, "left_rear": null, "right_rear": null},
    "tire_tread_health": {"left_front": null, "right_front": null, "left_rear": null, "right_rear": null},
    "tire_specs": {"left_front": null, "right_front": null, "left_rear": null, "right_rear": null}
  },
  "summary": "本批检测结果简述"
}`;

function emit(stage, message, status = 'running', extra = {}) {
  process.stderr.write(JSON.stringify({
    type: 'process_event',
    stage,
    message,
    status,
    timestamp: new Date().toISOString(),
    ...extra,
  }) + '\n');
}

function readInput() {
  const raw = String(process.argv[2] || process.env.SKILL_INPUT || process.env.AIFLOW_SKILL_INPUT || fs.readFileSync(0, 'utf8')).trim();
  return raw ? JSON.parse(raw) : {};
}

function clampNumber(value, min, max, fallback) {
  const number = Number(value);
  if (!Number.isFinite(number)) return fallback;
  return Math.max(min, Math.min(max, number));
}

function workspaceRoot(env) {
  if (env.VEHICLE_SCRATCH_WORKSPACE_ROOT) return path.resolve(env.VEHICLE_SCRATCH_WORKSPACE_ROOT);
  if (env.RZYX_AI_WORKSPACE_ROOT) {
    const root = path.resolve(env.RZYX_AI_WORKSPACE_ROOT);
    return path.basename(root) === 'vehicle-scratch-inspection' ? root : path.join(root, 'vehicle-scratch-inspection');
  }
  if (env.RZYX_AI_DATA_DIR) return path.join(path.resolve(env.RZYX_AI_DATA_DIR), 'workspace', 'vehicle-scratch-inspection');
  return path.join(os.tmpdir(), 'vehicle-scratch-inspection');
}

function workspaceFor(taskId, env) {
  const safe = String(taskId || '').trim();
  if (!/^[a-zA-Z0-9_-]{1,80}$/.test(safe)) throw new Error('taskId非法或为空');
  const workspacePath = path.join(workspaceRoot(env), safe);
  if (!fs.existsSync(workspacePath)) throw new Error(`workspace不存在: ${workspacePath}`);
  return { taskId: safe, workspacePath };
}

function readJson(filePath, fallback = null) {
  try {
    return JSON.parse(fs.readFileSync(filePath, 'utf8'));
  } catch {
    return fallback;
  }
}

function writeJson(filePath, value) {
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
  fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
}

function parseJsonLoose(value) {
  if (!value) return null;
  if (typeof value === 'object') return value;
  const text = String(value).trim();
  const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
  const body = fenced ? fenced[1].trim() : text;
  try {
    return JSON.parse(body);
  } catch {
    const start = body.indexOf('{');
    const end = body.lastIndexOf('}');
    if (start >= 0 && end > start) {
      try { return JSON.parse(body.slice(start, end + 1)); } catch { return null; }
    }
    return null;
  }
}

function chunkArray(items, size) {
  const out = [];
  for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
  return out;
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function runWithConcurrency(tasks, limit) {
  const results = new Array(tasks.length);
  let next = 0;
  async function worker() {
    while (next < tasks.length) {
      const index = next++;
      results[index] = await tasks[index]();
    }
  }
  await Promise.all(Array.from({ length: Math.min(limit, tasks.length) }, worker));
  return results;
}

function emptyVehicleInfo() {
  return {
    vehicleBrandModel: null,
    frontPlate: null,
    rearPlate: null,
    windshieldVin: null,
    nameplateVin: null,
    odometerKm: null,
    tirePressure: { leftFront: null, rightFront: null, leftRear: null, rightRear: null },
    tireTreadHealth: { leftFront: null, rightFront: null, leftRear: null, rightRear: null },
    tireSpecs: { leftFront: null, rightFront: null, leftRear: null, rightRear: null },
  };
}

const VEHICLE_KEY_MAP = {
  vehicle_brand_model: 'vehicleBrandModel',
  vehicleBrandModel: 'vehicleBrandModel',
  front_plate: 'frontPlate',
  frontPlate: 'frontPlate',
  rear_plate: 'rearPlate',
  rearPlate: 'rearPlate',
  windshield_vin: 'windshieldVin',
  windshieldVin: 'windshieldVin',
  nameplate_vin: 'nameplateVin',
  nameplateVin: 'nameplateVin',
  odometer_km: 'odometerKm',
  odometerKm: 'odometerKm',
};

const TIRE_GROUP_MAP = {
  tire_pressure: 'tirePressure',
  tirePressure: 'tirePressure',
  tire_tread_health: 'tireTreadHealth',
  tireTreadHealth: 'tireTreadHealth',
  tire_specs: 'tireSpecs',
  tireSpecs: 'tireSpecs',
};

const TIRE_POS_MAP = {
  left_front: 'leftFront',
  leftFront: 'leftFront',
  LF: 'leftFront',
  right_front: 'rightFront',
  rightFront: 'rightFront',
  RF: 'rightFront',
  left_rear: 'leftRear',
  leftRear: 'leftRear',
  LR: 'leftRear',
  right_rear: 'rightRear',
  rightRear: 'rightRear',
  RR: 'rightRear',
};

function mergeVehicleInfo(target, raw) {
  if (!raw || typeof raw !== 'object') return target;
  for (const [sourceKey, targetKey] of Object.entries(VEHICLE_KEY_MAP)) {
    const value = raw[sourceKey];
    if ((target[targetKey] === null || target[targetKey] === '' || target[targetKey] === undefined) && value !== null && value !== undefined && value !== '') {
      target[targetKey] = value;
    }
  }
  for (const [sourceGroup, targetGroup] of Object.entries(TIRE_GROUP_MAP)) {
    const group = raw[sourceGroup];
    if (!group || typeof group !== 'object') continue;
    for (const [sourcePos, targetPos] of Object.entries(TIRE_POS_MAP)) {
      const value = group[sourcePos];
      if (!target[targetGroup][targetPos] && value) target[targetGroup][targetPos] = value;
    }
  }
  return target;
}

function normalizeDamage(raw, batchNo, index) {
  const ts = Number(raw.time_second ?? raw.timeSecond ?? raw.timestamp ?? raw.time ?? 0);
  return {
    id: raw.id ? String(raw.id) : `damage_raw_${String(batchNo).padStart(2, '0')}_${String(index + 1).padStart(3, '0')}`,
    timeSecond: Number.isFinite(ts) ? Number(ts.toFixed(3)) : 0,
    location: String(raw.location || raw.part || raw.position || '未知部位').trim() || '未知部位',
    type: String(raw.type || raw.damageType || '旧伤').trim() || '旧伤',
    severity: String(raw.severity || '轻微').trim() || '轻微',
    description: String(raw.description || raw.detail || '').trim(),
    sourceBatch: batchNo,
    raw,
  };
}

function normalizeBatchResult(raw, batchNo) {
  const parsed = parseJsonLoose(raw) || {};
  const damages = Array.isArray(parsed.damages) ? parsed.damages.map((item, index) => normalizeDamage(item, batchNo, index)) : [];
  return {
    batch: batchNo,
    success: true,
    hasDamage: Boolean(parsed.has_damage ?? parsed.hasDamage ?? damages.length > 0),
    damages,
    vehicleInfo: parsed.vehicle_info || parsed.vehicleInfo || {},
    summary: parsed.summary || '',
    raw: parsed,
  };
}

function collectModelResults(input) {
  if (Array.isArray(input.batchResults)) return input.batchResults;
  const candidate = input.modelResult ?? input.detectResult ?? input.result ?? input.mockResult;
  if (!candidate) return null;
  const parsed = parseJsonLoose(candidate);
  if (Array.isArray(parsed)) return parsed;
  if (Array.isArray(parsed?.batches)) return parsed.batches;
  if (Array.isArray(parsed?.batchResults)) return parsed.batchResults;
  return [parsed || candidate];
}

function imageContent(filePath) {
  return {
    type: 'image_url',
    image_url: { url: `data:image/jpeg;base64,${fs.readFileSync(filePath).toString('base64')}` },
  };
}

async function callVisionApi({ env, content }) {
  const apiKey = env.ARK_API_KEY;
  if (!apiKey) throw new Error('缺少ARK_API_KEY');
  const apiUrl = env.ARK_API_URL || 'https://ark.cn-beijing.volces.com/api/v3/chat/completions';
  const model = env.DAMAGE_DETECT_MODEL || 'doubao-seed-2-0-pro-260215';
  for (let attempt = 1; attempt <= 4; attempt += 1) {
    emit('vision-api', '请求多模态检测模型', 'running', { model, attempt, totalAttempts: 4 });
    try {
      const response = await fetch(apiUrl, {
        method: 'POST',
        headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
        body: JSON.stringify({
          model,
          messages: [{ role: 'user', content }],
          max_tokens: 4096,
          temperature: 0.1,
        }),
        signal: AbortSignal.timeout(600000),
      });
      const text = await response.text();
      let body;
      try { body = JSON.parse(text); } catch { body = { raw: text }; }
      if (!response.ok) throw new Error(JSON.stringify(body).slice(0, 1000));
      return body.choices?.[0]?.message?.content || '';
    } catch (err) {
      const msg = err && err.message ? err.message : String(err);
      if (attempt < 4 && /429|TooManyRequests|timeout|ECONNRESET|ETIMEDOUT/i.test(msg)) {
        await sleep(3000 * attempt);
        continue;
      }
      throw err;
    }
  }
  throw new Error('多模态检测模型调用失败');
}

async function detectBatch({ batch, batchNo, totalBatches, env, prompt }) {
  emit('detect_damages', '开始检测批次', 'running', { batch: batchNo, totalBatches, frameCount: batch.length });
  const content = [];
  for (const frame of batch) {
    content.push({ type: 'text', text: `[${Number(frame.timestamp).toFixed(2)} second] frame ${frame.index}` });
    content.push(imageContent(frame.path));
  }
  content.push({ type: 'text', text: prompt });
  const modelText = await callVisionApi({ env, content });
  const result = normalizeBatchResult(modelText, batchNo);
  emit('detect_damages', '批次检测完成', 'running', { batch: batchNo, totalBatches, damageCount: result.damages.length });
  return result;
}

function buildVisionRequestOutput({ workspace, batches, prompt }) {
  return {
    success: false,
    needsModelVision: true,
    taskId: workspace.taskId,
    workspacePath: workspace.workspacePath,
    prompt,
    batches: batches.map((batch, index) => ({
      batch: index + 1,
      frames: batch.map(frame => ({
        index: frame.index,
        timestamp: frame.timestamp,
        path: frame.path,
        relativePath: frame.relativePath,
      })),
    })),
    error: '未配置ARK_API_KEY且未提供modelResult/batchResults。请让多模态模型按prompt分析每批图片，并把JSON结果作为batchResults传回本skill。',
  };
}

async function run(input, env = process.env) {
  if (!input?.taskId) throw new Error('taskId不能为空，请先使用video-frame-extractor抽帧');
  const workspace = workspaceFor(input.taskId, env);
  const videoInfoDoc = readJson(path.join(workspace.workspacePath, 'video_info.json'));
  if (!videoInfoDoc?.frames?.length) throw new Error('未找到video_info.json或frames为空，请先抽帧');
  const batchSize = Math.round(clampNumber(input.batchSize, 1, 100, 50));
  const batchDelay = clampNumber(input.batchDelay, 0, 30, 2);
  const concurrency = Math.round(clampNumber(input.concurrency, 1, 8, 5));
  const frames = videoInfoDoc.frames.map(frame => ({
    ...frame,
    path: path.isAbsolute(frame.path) ? frame.path : path.join(workspace.workspacePath, frame.relativePath || frame.path),
  }));
  const batches = chunkArray(frames, batchSize);
  const prompt = input.prompt ? String(input.prompt) : DEFAULT_PROMPT;

  let batchResults;
  const externalResults = collectModelResults(input);
  if (externalResults) {
    batchResults = externalResults.map((item, index) => normalizeBatchResult(item, index + 1));
  } else if (!env.ARK_API_KEY) {
    return buildVisionRequestOutput({ workspace, batches, prompt });
  } else {
    const tasks = batches.map((batch, index) => async () => {
      if (index > 0 && batchDelay > 0) await sleep(batchDelay * 1000);
      return detectBatch({ batch, batchNo: index + 1, totalBatches: batches.length, env, prompt });
    });
    batchResults = await runWithConcurrency(tasks, concurrency);
  }

  const vehicleInfo = emptyVehicleInfo();
  const damages = [];
  for (const result of batchResults) {
    mergeVehicleInfo(vehicleInfo, result.vehicleInfo);
    damages.push(...result.damages);
  }

  const outputDoc = {
    taskId: workspace.taskId,
    workspacePath: workspace.workspacePath,
    totalBatches: batches.length,
    successBatches: batchResults.filter(item => item.success).length,
    damages,
    vehicleInfo,
    batches: batchResults,
    generatedAt: new Date().toISOString(),
  };
  writeJson(path.join(workspace.workspacePath, 'damages.json'), outputDoc);
  emit('detect_damages', '检测完成并写入damages.json', 'completed', { damageCount: damages.length });
  return {
    success: true,
    damagesFound: damages.length,
    totalBatches: outputDoc.totalBatches,
    successBatches: outputDoc.successBatches,
    vehicleInfo,
  };
}

(async () => {
  try {
    process.stdout.write(JSON.stringify(await run(readInput())));
  } catch (err) {
    process.stdout.write(JSON.stringify({
      success: false,
      error: err && err.message ? err.message : String(err),
    }));
  }
})();
