const { chunkArray, runWithConcurrency, parseJsonLoose, normalizePartName } = require('./json_utils.cjs');
const { callVisionApi, imageContent } = require('./vision_client.cjs');
const { emitEvent, writeJson } = require('./workspace.cjs');

function normalizeVehicleInfo(target, source) {
  if (!source || typeof source !== 'object') return target;
  const keys = [
    'vehicle_brand_model',
    'front_plate',
    'rear_plate',
    'windshield_vin',
    'nameplate_vin',
    'odometer_km',
  ];
  for (const key of keys) {
    if ((target[key] === null || target[key] === undefined || target[key] === '') && source[key]) {
      target[key] = source[key];
    }
  }
  for (const key of ['tire_pressure', 'tire_tread_health', 'tire_specs']) {
    target[key] = target[key] || {};
    const incoming = source[key] || {};
    for (const sub of ['left_front', 'right_front', 'left_rear', 'right_rear']) {
      if (!target[key][sub] && incoming[sub]) target[key][sub] = incoming[sub];
    }
  }
  return target;
}

function emptyVehicleInfo() {
  return {
    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 },
  };
}

function normalizeCandidate(raw, batchIndex, candidateIndex) {
  const timestamp = Number(raw.time_second ?? raw.timestamp ?? raw.timeSecond ?? 0);
  return {
    id: `cand_${String(batchIndex + 1).padStart(2, '0')}_${String(candidateIndex + 1).padStart(3, '0')}`,
    timestamp: Number.isFinite(timestamp) ? timestamp : 0,
    part: normalizePartName(raw.part || raw.location),
    type: String(raw.type || '旧伤'),
    severity: String(raw.severity || '轻微'),
    description: String(raw.description || ''),
    confidence: raw.confidence || 'medium',
    uncertain: Boolean(raw.uncertain),
    uncertainReason: raw.uncertainReason || raw.uncertain_reason || null,
    sourceBatch: batchIndex + 1,
  };
}

function shouldKeepCandidate(candidate) {
  if (!candidate.description && candidate.confidence === 'low' && !candidate.uncertain) return false;
  return true;
}

function candidateKey(candidate) {
  const bucket = Math.round(Number(candidate.timestamp || 0) / 4);
  return [
    normalizePartName(candidate.part),
    String(candidate.type || '').trim(),
    bucket,
  ].join('|');
}

function dedupeCandidates(candidates) {
  const map = new Map();
  for (const candidate of candidates) {
    const key = candidateKey(candidate);
    const existing = map.get(key);
    if (!existing) {
      map.set(key, candidate);
      continue;
    }
    const existingScore = scoreCandidate(existing);
    const score = scoreCandidate(candidate);
    if (score > existingScore) {
      map.set(key, {
        ...candidate,
        duplicateOf: existing.id,
        relatedCandidateIds: [...(existing.relatedCandidateIds || []), existing.id],
      });
    } else {
      existing.relatedCandidateIds = [...(existing.relatedCandidateIds || []), candidate.id];
    }
  }
  return Array.from(map.values()).sort((a, b) => a.timestamp - b.timestamp);
}

function scoreCandidate(candidate) {
  let score = 0;
  if (candidate.description) score += Math.min(30, candidate.description.length);
  if (candidate.confidence === 'high') score += 40;
  if (candidate.confidence === 'medium') score += 20;
  if (candidate.uncertain) score -= 5;
  return score;
}

async function detectBatch({ batch, batchIndex, totalBatches, modelConfig, prompt }) {
  emitEvent('detect', 'batch started', {
    batch: batchIndex + 1,
    totalBatches,
    frameStart: batch[0]?.index,
    frameEnd: batch[batch.length - 1]?.index,
    frameCount: batch.length,
  });
  const content = [];
  for (const frame of batch) {
    content.push({ type: 'text', text: `[${frame.timestamp.toFixed(2)} second] frame ${frame.index}` });
    content.push(imageContent(frame.path));
  }
  content.push({ type: 'text', text: prompt });
  const result = await callVisionApi({
    config: modelConfig,
    model: modelConfig.detectModel,
    messages: [{ role: 'user', content }],
    maxTokens: 4096,
    temperature: 0.1,
  });
  if (!result.ok) {
    emitEvent('detect', 'batch failed', { batch: batchIndex + 1, totalBatches, level: 'warning', error: result.error });
    return { batch: batchIndex + 1, candidates: [], vehicleInfo: null, error: result.error };
  }
  const parsed = parseJsonLoose(result.content) || {};
  const rawDamages = Array.isArray(parsed.damages) ? parsed.damages : [];
  const candidates = rawDamages.map((item, index) => normalizeCandidate(item, batchIndex, index)).filter(shouldKeepCandidate);
  emitEvent('detect', 'batch completed', { batch: batchIndex + 1, totalBatches, damageCount: candidates.length });
  return {
    batch: batchIndex + 1,
    candidates,
    vehicleInfo: parsed.vehicle_info || parsed.vehicleInfo || null,
    raw: parsed,
  };
}

async function detectDamageCandidates({ workspace, frames, modelConfig, prompt, batchSize = 30, concurrency = 5 }) {
  const batches = chunkArray(frames, batchSize);
  emitEvent('detect', 'detecting damage candidates', {
    frameCount: frames.length,
    batchSize,
    concurrency,
    model: modelConfig.detectModel,
  });
  const tasks = batches.map((batch, index) => () => detectBatch({
    batch,
    batchIndex: index,
    totalBatches: batches.length,
    modelConfig,
    prompt,
  }));
  const results = await runWithConcurrency(tasks, concurrency);
  const vehicleInfo = emptyVehicleInfo();
  const candidates = [];
  for (const result of results) {
    normalizeVehicleInfo(vehicleInfo, result.vehicleInfo);
    candidates.push(...result.candidates);
  }
  const dedupedCandidates = dedupeCandidates(candidates);
  const output = { totalFrames: frames.length, batches: results, vehicleInfo, candidates: dedupedCandidates, rawCandidateCount: candidates.length };
  writeJson(workspace, 'damage_candidates.json', output);
  emitEvent('detect', 'damage candidate detection complete', { candidateCount: dedupedCandidates.length, rawCandidateCount: candidates.length });
  return output;
}

module.exports = {
  emptyVehicleInfo,
  normalizeVehicleInfo,
  normalizeCandidate,
  dedupeCandidates,
  detectDamageCandidates,
};
