/**
 * groupHandler.js — WhatsApp group message orchestrator
 *
 * Phases:
 *   waiting   → group created, waiting for coach + client to join
 *   form      → onboarding form in progress
 *   cvmaster  → form done, generating Career Plan
 *   active    → fully onboarded; bot responds to @mentions and replies only
 *
 * One group = one mentee. Linked via mentees/{slug}/group.json → groupJid
 */

import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { syncWrite } from './sync.js';
import { handleFormMessage, startForm, loadForm, saveForm, QUESTIONS } from './groupForm.js';
import { generateCvMaster, formatCvMasterSummary } from './groupCvmaster.js';
import { journeyPhaseStart, journeyPhaseComplete, journeyEvent } from './journey.js';
import { handleJobMessage } from './groupJobResponse.js';
import { dispatchJobsToGroup } from './groupJobs.js';
import { transcribeWhatsAppAudio } from './transcription.js';
import { downloadDocument, extractText, getDocMsg } from './documents.js';

const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
const GROUP_TRAINING = readFileSync(
  join(SCRIPT_DIR, '..', 'training', 'group.txt'),
  'utf8'
).trim();

// ── Group ↔ Mentee lookup ─────────────────────────────────────────────────────

function findMenteeByGroup(scriptDir, groupJid) {
  const menteesDir = join(scriptDir, 'mentees');
  if (!existsSync(menteesDir)) return null;
  for (const slug of readdirSync(menteesDir)) {
    const gp = join(menteesDir, slug, 'group.json');
    if (!existsSync(gp)) continue;
    try {
      const g = JSON.parse(readFileSync(gp, 'utf8'));
      if (g.groupJid === groupJid) return { slug, group: g };
    } catch {}
  }
  return null;
}

// ── Group state helpers ───────────────────────────────────────────────────────

function loadGroup(scriptDir, slug) {
  const p = join(scriptDir, 'mentees', slug, 'group.json');
  return JSON.parse(readFileSync(p, 'utf8'));
}

function saveGroup(scriptDir, botDir, slug, group) {
  const p = join(scriptDir, 'mentees', slug, 'group.json');
  writeFileSync(p, JSON.stringify(group, null, 2));
  syncWrite(botDir, slug, 'group', group).catch(e =>
    console.warn('[group] sync failed:', e.message)
  );
}

// ── Presence detection ────────────────────────────────────────────────────────

async function checkPresence(sock, groupJid, numbers) {
  try {
    const meta     = await sock.groupMetadata(groupJid);
    const staffSet = new Set(numbers.map(n => n.number));

    const botIds = new Set();
    if (sock.user?.id)  botIds.add(sock.user.id.replace(/:.*/, '').replace(/@.*/, ''));
    if (sock.user?.lid) botIds.add(String(sock.user.lid).replace(/:.*/, '').replace(/@.*/, ''));

    const members = meta.participants.map(p => p.id.replace(/:.*/, '').replace(/@.*/, ''));
    const others  = members.filter(n => !botIds.has(n));
    const coaches = others.filter(n => staffSet.has(n));
    const clients = others.filter(n => !staffSet.has(n));

    console.log(`[group] presence — all: [${members.join(', ')}] | botIds: [${[...botIds].join(', ')}] | coaches: [${coaches.join(', ')}] | clients: [${clients.join(', ')}]`);

    return {
      complete:    coaches.length > 0 && clients.length > 0,
      clientPhone: clients[0] || null,
      coachPhone:  coaches[0] || null,
    };
  } catch (e) {
    console.error('[group] groupMetadata error:', e.message);
    return { complete: false, clientPhone: null, coachPhone: null };
  }
}

// ── @mention / reply-to-bot detection ────────────────────────────────────────

function isBotAddressed(msg, botPhone, botLid) {
  const ctx = msg.message?.extendedTextMessage?.contextInfo;
  if (!ctx) return false;

  const bare   = jid => (jid || '').replace(/:.*/, '').replace(/@.*/, '');
  const botIds = new Set([botPhone]);
  if (botLid) botIds.add(botLid);

  const mentioned = ctx.mentionedJid || [];
  if (mentioned.some(jid => botIds.has(bare(jid)))) return true;
  if (mentioned.length > 0) return false;
  if (ctx.participant && botIds.has(bare(ctx.participant))) return true;
  return false;
}

// ── Language detection ────────────────────────────────────────────────────────

function detectLang(text) {
  if (!text) return 'English';
  const t = text.toLowerCase();
  if (/\b(bonjour|merci|comment|je|vous|nous|salut|bien)\b/.test(t)) return 'French';
  if (/\b(olá|obrigad|como|você|tudo|bom|boa|quero|preciso)\b/.test(t)) return 'Portuguese';
  if (/\b(hola|gracias|cómo|estás|soy|quiero|buenos|mucho)\b/.test(t)) return 'Spanish';
  return 'English';
}

// ── Group chat log ────────────────────────────────────────────────────────────

function appendGroupMsg(scriptDir, slug, role, text, senderNum = null) {
  const p = join(scriptDir, 'mentees', slug, 'group-chat.json');
  let msgs = [];
  if (existsSync(p)) {
    try { msgs = JSON.parse(readFileSync(p, 'utf8')); } catch {}
  }
  const entry = { role, text, ts: new Date().toISOString() };
  if (senderNum) entry.senderNum = senderNum;
  msgs.push(entry);
  writeFileSync(p, JSON.stringify(msgs, null, 2));
}

async function groupSend(sock, groupJid, scriptDir, slug, text, quotedMsg = null) {
  const opts = quotedMsg ? { quoted: quotedMsg } : {};
  await sock.sendMessage(groupJid, { text }, opts);
  appendGroupMsg(scriptDir, slug, 'assistant', text);
}

// ── Coach notification ────────────────────────────────────────────────────────

async function notifyCoach(sock, group, message) {
  const coachPhone = group.coachPhone;
  if (!coachPhone) return;
  try {
    await sock.sendMessage(`${coachPhone}@s.whatsapp.net`, { text: message });
  } catch (e) {
    console.warn('[group] coach notification failed:', e.message);
  }
}

// ── CV prefill extraction ─────────────────────────────────────────────────────

async function extractPrefillFromCv(scriptDir, slug, clientPhone, aiFn) {
  const prefill = {};

  // Phone fallback from group.json clientPhone
  if (clientPhone) prefill.phone = clientPhone;

  // Try to extract from existing CV
  const cvPath = join(scriptDir, 'mentees', slug, 'cv-personal.pdf');
  if (!existsSync(cvPath)) return prefill;

  try {
    const { PDFParse } = await import('pdf-parse');
    const buffer = readFileSync(cvPath);
    const parser = new PDFParse({ data: buffer });
    const result = await parser.getText();
    const cvText = (result.text || '').trim();
    if (!cvText) return prefill;

    const raw = await aiFn(
      'You are a precise data extractor. Return only valid JSON.',
      [{
        role: 'user',
        content: `Extract the following fields from this CV text. Return ONLY a JSON object with these keys (use null if not found):
{"phone": null, "address": null, "date_of_birth": null, "linkedin_url": null}

CV text:
${cvText.slice(0, 3000)}`,
      }],
      'gpt-4.1-mini'
    );

    const parsed = JSON.parse(raw.trim());
    for (const [key, val] of Object.entries(parsed)) {
      if (val && val !== 'null') prefill[key] = String(val).trim();
    }
  } catch (e) {
    console.warn('[group] CV prefill extraction failed:', e.message);
  }

  return prefill;
}

// ── Phase handlers ────────────────────────────────────────────────────────────

async function handleWaiting(sock, msg, scriptDir, botDir, slug, group, numbers, aiFn, botPhone) {
  const groupJid = msg.key.remoteJid;
  const text     = msg.message?.conversation ||
                   msg.message?.extendedTextMessage?.text || '';
  const lang     = detectLang(text);

  const presence = await checkPresence(sock, groupJid, numbers);

  if (!presence.complete) {
    const waitMsg = await aiFn(
      GROUP_TRAINING,
      [{
        role: 'user',
        content: `Someone messaged the group but not all members have joined yet. Reply warmly in ${lang}, explain we are waiting for everyone before starting. Be brief.`,
      }],
      'gpt-4.1-mini'
    );
    await groupSend(sock, groupJid, scriptDir, slug, waitMsg);
    return;
  }

  // All present → transition to form phase
  group.phase        = 'form';
  group.clientPhone  = presence.clientPhone;
  group.coachPhone   = presence.coachPhone;
  group.allPresentAt = new Date().toISOString();
  saveGroup(scriptDir, botDir, slug, group);
  journeyPhaseComplete(scriptDir, slug, 'waiting', { clientPhone: presence.clientPhone });
  journeyPhaseStart(scriptDir, slug, 'form');

  const clientName = group.leadName?.split(' ')[0] || 'there';
  const welcomeLang = 'English';

  const welcome = await aiFn(
    GROUP_TRAINING,
    [{
      role: 'user',
      content: `Generate a welcome message for a new client joining their EcoBe career group. Client's first name: "${clientName}". Write in ${welcomeLang}.
Include:
- Warm greeting
- Brief intro: EcoBe is their career assistant to help them find the right job
- You (the bot) are the main point of contact — you'll handle everything; a human mentor is available if needed but the process runs autonomously
- You'll ask a few questions to build their career profile, then start the job search
- Keep it under 100 words, warm and direct. Do NOT ask any question yet.`,
    }],
    'gpt-4.1-mini'
  );

  await groupSend(sock, groupJid, scriptDir, slug, welcome);

  // Extract prefill data from CV
  const prefill = await extractPrefillFromCv(scriptDir, slug, presence.clientPhone, aiFn);

  await startForm(sock, groupJid, scriptDir, slug, welcomeLang, aiFn, GROUP_TRAINING, prefill);
}

async function handleFormPhase(sock, msg, scriptDir, botDir, slug, group, numbers, senderNum, aiFn) {
  const groupJid = msg.key.remoteJid;
  const isCoach  = numbers.some(n => n.number === senderNum);

  // ── Handle document upload in form phase — inject as text so form can process it ─
  const docMsg = getDocMsg(msg);
  let text = msg.message?.conversation || msg.message?.extendedTextMessage?.text || '';

  if (docMsg) {
    try {
      const { buffer, ext } = await downloadDocument(docMsg);
      const docText = await extractText(buffer, ext);
      const filename = docMsg.fileName || `document.${ext}`;
      if (docText) {
        // Inject full document content as the incoming text — form handler reads it naturally
        text = `[Document: ${filename}]\n\n${docText.slice(0, 4000)}`;
        appendGroupMsg(scriptDir, slug, 'user', text, senderNum);
      } else {
        // Could not extract text — just acknowledge and skip
        appendGroupMsg(scriptDir, slug, 'user', `[Document: ${filename}]`, senderNum);
        return;
      }
    } catch (e) {
      console.warn('[group] doc handling in form phase:', e.message);
      return;
    }
  }

  // Coach messages during form — acknowledge only, do NOT advance form
  if (isCoach) {
    const coachNote = await aiFn(
      GROUP_TRAINING,
      [{
        role: 'user',
        content: `The coach just said: "${text}". The client is filling in the onboarding form. Acknowledge the coach briefly without disrupting the client's flow. Reply in ${detectLang(text)}.`,
      }],
      'gpt-4.1-mini'
    );
    await groupSend(sock, groupJid, scriptDir, slug, coachNote);
    return;
  }

  // Client message → advance form
  const lang   = detectLang(text);
  const result = await handleFormMessage(
    sock, groupJid, scriptDir, slug, text, lang, aiFn, GROUP_TRAINING
  );

  if (result === 'complete') {
    group.phase           = 'cvmaster';
    group.formCompletedAt = new Date().toISOString();
    saveGroup(scriptDir, botDir, slug, group);
    journeyPhaseComplete(scriptDir, slug, 'form', { totalAnswers: QUESTIONS.length });
    journeyPhaseStart(scriptDir, slug, 'cvmaster');

    const completeMsg = await aiFn(
      GROUP_TRAINING,
      [{
        role: 'user',
        content: `The client just finished the onboarding questionnaire. Send a warm congratulations message and tell them you are now building their Career Plan. Write in ${lang}. Under 60 words, enthusiastic.`,
      }],
      'gpt-4.1-mini'
    );
    await groupSend(sock, groupJid, scriptDir, slug, completeMsg);

    generateCareerPlanAndNotify(sock, groupJid, scriptDir, botDir, slug, group, lang, aiFn);
  }
}

async function generateCareerPlanAndNotify(sock, groupJid, scriptDir, botDir, slug, group, lang, aiFn) {
  try {
    const cv = await generateCvMaster(scriptDir, botDir, slug, aiFn);

    group.phase          = 'active';
    group.cvMasterDoneAt = new Date().toISOString();
    saveGroup(scriptDir, botDir, slug, group);
    journeyPhaseComplete(scriptDir, slug, 'cvmaster');
    journeyPhaseStart(scriptDir, slug, 'active');

    // Send Career Plan summary
    const summary = formatCvMasterSummary(cv);
    await groupSend(sock, groupJid, scriptDir, slug, summary);

    // Invite client to review and suggest adjustments
    const reviewMsg = await aiFn(
      GROUP_TRAINING,
      [{
        role: 'user',
        content: `The client's Career Plan is ready (shown above). Write a short message in ${lang} inviting them to review it and suggest any adjustments — make it feel collaborative and exciting. Then ask if they'd like to start the job search now. Under 80 words.`,
      }],
      'gpt-4.1-mini'
    );
    await groupSend(sock, groupJid, scriptDir, slug, reviewMsg);

    // Stay responsive: bot answers freely until job search is confirmed
    group.awaitingJobSearchConfirm = true;
    group.awaitingCareerPlanReview = true;
    saveGroup(scriptDir, botDir, slug, group);

  } catch (e) {
    console.error('[cvmaster]', e.message);
    await groupSend(sock, groupJid, scriptDir, slug, '⚠️ There was an issue generating your Career Plan. The team has been notified and will fix it shortly.');
    await notifyCoach(sock, group, `⚠️ Career Plan generation failed for ${group.leadName}: ${e.message}`);
    group.phase = 'form';
    saveGroup(scriptDir, botDir, slug, group);
  }
}

async function handleCvMasterPhase(sock, msg, scriptDir, slug) {
  const groupJid = msg.key.remoteJid;
  await groupSend(sock, groupJid, scriptDir, slug, '⏳ Building your Career Plan — almost there!');
}

async function handleActivePhase(sock, msg, scriptDir, botDir, slug, group, senderNum, numbers, botPhone, aiFn, botLid = '') {
  const groupJid = msg.key.remoteJid;
  const isCoach  = numbers.some(n => n.number === senderNum);

  // ── Handle document upload in active phase ────────────────────────────────
  const docMsg = getDocMsg(msg);
  if (docMsg) {
    try {
      const { buffer, ext } = await downloadDocument(docMsg);
      const docText = await extractText(buffer, ext);
      if (docText) {
        appendGroupMsg(scriptDir, slug, 'user', `[Document: ${docMsg.fileName || ext}]\n${docText.slice(0, 500)}`, senderNum);
        const ack = await aiFn(GROUP_TRAINING, [{
          role: 'user',
          content: `The client sent a document (${docMsg.fileName || ext}). Acknowledge it briefly and warmly. Reply in ${detectLang('')}.`,
        }], 'gpt-4.1-mini');
        await groupSend(sock, groupJid, scriptDir, slug, ack);
      }
    } catch (e) {
      console.warn('[group] doc in active phase:', e.message);
    }
    return;
  }

  const text = msg.message?.conversation ||
               msg.message?.extendedTextMessage?.text || '';
  if (!text.trim()) return;

  const lang = detectLang(text);

  const appendFn   = (role, txt) => appendGroupMsg(scriptDir, slug, role, txt);
  const saveGroupFn = (g) => saveGroup(scriptDir, botDir, slug, g);

  // ── 1. Career Plan review period — bot stays fully responsive ───────────
  if (group.awaitingCareerPlanReview && !isCoach) {
    // Check if client is confirming/declining job search start
    const isJobSearchIntent = await aiFn(
      'Classify as YES, NO, or FEEDBACK.',
      [{ role: 'user', content: `Context: bot just presented a Career Plan and asked if the client wants to start the job search now. Client replied: "${text}". Reply only YES (wants to start), NO (not yet), or FEEDBACK (commenting/asking about the plan, not answering the job search question).` }],
      'gpt-4.1-mini'
    );
    const intent = isJobSearchIntent?.trim().toUpperCase();

    if (intent === 'YES') {
      group.awaitingJobSearchConfirm  = false;
      group.awaitingCareerPlanReview  = false;
      saveGroup(scriptDir, botDir, slug, group);
      const searchMsg = await aiFn(GROUP_TRAINING, [{
        role: 'user',
        content: `Client wants to start the job search now. Acknowledge enthusiastically and say you are searching. In ${lang}. 1-2 sentences.`
      }], 'gpt-4.1-mini');
      await groupSend(sock, groupJid, scriptDir, slug, searchMsg);
      dispatchJobsToGroup(sock, scriptDir, botDir, slug, group, aiFn).catch(e =>
        console.error('[groupHandler] job search failed:', e.message)
      );
      return;
    }

    if (intent === 'NO') {
      group.awaitingJobSearchConfirm  = false;
      group.awaitingCareerPlanReview  = false;
      saveGroup(scriptDir, botDir, slug, group);
      const laterMsg = await aiFn(GROUP_TRAINING, [{
        role: 'user',
        content: `Client prefers not to start the job search right now. Acknowledge warmly and tell them jobs will arrive every morning at 6am. In ${lang}. 1-2 sentences.`
      }], 'gpt-4.1-mini');
      await groupSend(sock, groupJid, scriptDir, slug, laterMsg);
      return;
    }

    // FEEDBACK — client is commenting on / adjusting the Career Plan
    // Load context and respond normally
    let context = `Group: EcoBe | ${group.leadName}\nPhase: Career Plan review\n`;
    try {
      const cs = JSON.parse(readFileSync(join(scriptDir, 'mentees', slug, 'career-strategy.json'), 'utf8'));
      context += `Positioning: ${cs.targetPositioning || ''}\nPrimary roles: ${(cs.primaryRoles || []).join(', ')}\n`;
    } catch {}
    try {
      const cv = JSON.parse(readFileSync(join(scriptDir, 'mentees', slug, 'cvmaster.json'), 'utf8'));
      context += `Headline: ${cv.headline}\nTarget roles: ${cv.targetRoles?.join(', ')}\n`;
    } catch {}

    const chatPath = join(scriptDir, 'mentees', slug, 'group-chat.json');
    let history = [];
    if (existsSync(chatPath)) { try { history = JSON.parse(readFileSync(chatPath, 'utf8')); } catch {} }
    const historyMsgs = history.slice(-20).map(m => ({ role: m.role === 'assistant' ? 'assistant' : 'user', content: m.text }));

    let reply = await aiFn(
      GROUP_TRAINING,
      [{ role: 'system', content: context }, ...historyMsgs, { role: 'user', content: text }],
      'gpt-4.1-mini'
    );

    // Handle UPDATEDATA if emitted
    const mUpdate = reply.match(/UPDATEDATA\((\{[\s\S]*?\})\)/i);
    if (mUpdate) {
      try {
        const payload   = JSON.parse(mUpdate[1]);
        const stratPath = join(scriptDir, 'mentees', slug, 'career-strategy.json');
        const existing  = existsSync(stratPath) ? JSON.parse(readFileSync(stratPath, 'utf8')) : {};
        writeFileSync(stratPath, JSON.stringify({ ...existing, ...payload }, null, 2));
        syncWrite(botDir, slug, 'career-strategy', { ...existing, ...payload }).catch(() => {});
      } catch {}
      reply = reply.replace(/UPDATEDATA\([\s\S]*?\)\s*/i, '').trim();
    }

    if (reply) await groupSend(sock, groupJid, scriptDir, slug, reply, msg);
    return;
  }

  // ── 2. Job flow ───────────────────────────────────────────────────────────
  const jobHandled = await handleJobMessage(
    sock, groupJid, scriptDir, botDir, slug, group, text,
    senderNum, numbers, aiFn, GROUP_TRAINING, appendFn, saveGroupFn
  );
  if (jobHandled) return;

  // ── 3. @mention / reply-to-bot ────────────────────────────────────────────
  const addressed    = isBotAddressed(msg, botPhone, botLid);
  const inActiveFlow = !!(group.cvReview || group.awaitingJobConfirm || group.awaitingCoverLetterConfirm);
  if (!addressed && !inActiveFlow) return;

  let context = `Group: EcoBe | ${group.leadName}\nPhase: active\n`;
  try {
    const cs = JSON.parse(readFileSync(join(scriptDir, 'mentees', slug, 'career-strategy.json'), 'utf8'));
    context += `Positioning: ${cs.targetPositioning || ''}\nPrimary roles: ${(cs.primaryRoles || []).join(', ')}\n`;
    context += `Location: ${cs.location || ''}\nSalary target: ${cs.salaryTarget || ''}\n`;
  } catch {}
  try {
    const cv = JSON.parse(readFileSync(join(scriptDir, 'mentees', slug, 'cvmaster.json'), 'utf8'));
    context += `Headline: ${cv.headline}\nTarget roles: ${cv.targetRoles?.join(', ')}\n`;
  } catch {}

  const chatPath = join(scriptDir, 'mentees', slug, 'group-chat.json');
  let history = [];
  if (existsSync(chatPath)) {
    try { history = JSON.parse(readFileSync(chatPath, 'utf8')); } catch {}
  }
  const historyMsgs = history.slice(-30).map(m => ({
    role: m.role === 'assistant' ? 'assistant' : 'user',
    content: m.text,
  }));

  let reply = await aiFn(
    GROUP_TRAINING,
    [{ role: 'system', content: context }, ...historyMsgs, { role: 'user', content: text }],
    'gpt-4.1-mini'
  );

  // Process UPDATEDATA command
  const mUpdate = reply.match(/UPDATEDATA\((\{[\s\S]*?\})\)/i);
  if (mUpdate) {
    try {
      const payload   = JSON.parse(mUpdate[1]);
      const stratPath = join(scriptDir, 'mentees', slug, 'career-strategy.json');
      const existing  = existsSync(stratPath) ? JSON.parse(readFileSync(stratPath, 'utf8')) : {};
      const merged    = { ...existing, ...payload };
      writeFileSync(stratPath, JSON.stringify(merged, null, 2));
      syncWrite(botDir, slug, 'career-strategy', merged).catch(e =>
        console.warn('[groupHandler] profile sync failed:', e.message)
      );
    } catch (e) {
      console.error('[groupHandler] UPDATEDATA failed:', e.message);
    }
    reply = reply.replace(/UPDATEDATA\([\s\S]*?\)\s*/i, '').trim();
  }

  if (reply) await groupSend(sock, groupJid, scriptDir, slug, reply, msg);
}

// ── Main entry point ──────────────────────────────────────────────────────────

export async function handleGroupMsg(sock, msg, scriptDir, botDir, numbers, aiFn, botPhone, botLid = '') {
  const groupJid = msg.key.remoteJid;
  if (msg.key.fromMe) return;

  const found = findMenteeByGroup(scriptDir, groupJid);
  if (!found) return;

  const { slug } = found;
  const group    = loadGroup(scriptDir, slug);

  // ── Silver tier branch ────────────────────────────────────────────────────
  if (group.tier === 'silver') {
    const senderJid = msg.key.participant || '';
    const senderNum = senderJid.replace(/:.*/, '').replace(/@.*/, '');
    const text = msg.message?.conversation
              || msg.message?.extendedTextMessage?.text
              || '';
    if (!text) return;
    const { handleSilverMessage } = await import('./silverCourse.js');
    return handleSilverMessage(scriptDir, slug, { text, fromNum: senderNum });
  }

  const rawPhase = group.phase || 'waiting';
  const phase    = rawPhase === 'onboarding' ? 'waiting' : rawPhase;

  // Sender number — strip device suffix AND domain
  const senderJid = msg.key.participant || '';
  const senderNum = senderJid.replace(/:.*/, '').replace(/@.*/, '');

  console.log(`[group] ${phase} | ${slug} | sender: ${senderNum}`);

  // ── Handle audio messages ─────────────────────────────────────────────────
  const audioMsg = msg.message?.audioMessage;
  if (audioMsg) {
    const lang        = detectLang(''); // use English as fallback; we'll refine below
    const chatPath    = join(scriptDir, 'mentees', slug, 'group-chat.json');
    let chatLang      = 'English';
    try {
      const chat = JSON.parse(readFileSync(chatPath, 'utf8'));
      // Detect language from recent messages
      const recent = chat.slice(-5).map(m => m.text || '').join(' ');
      chatLang = detectLang(recent);
    } catch {}

    const transcript = await transcribeWhatsAppAudio(audioMsg, chatLang);
    if (!transcript) return; // transcription failed — silently ignore

    // Log transcription
    appendGroupMsg(scriptDir, slug, 'user', `[Audio] ${transcript}`, senderNum);

    // Inject transcript as a synthetic text message and re-route
    const syntheticMsg = {
      ...msg,
      message: { conversation: transcript },
    };

    console.log(`[group] audio transcribed (${chatLang}): "${transcript.slice(0, 80)}"`);

    // Re-route with text content
    switch (phase) {
      case 'waiting':
        await handleWaiting(sock, syntheticMsg, scriptDir, botDir, slug, group, numbers, aiFn, botPhone);
        break;
      case 'form':
        await handleFormPhase(sock, syntheticMsg, scriptDir, botDir, slug, group, numbers, senderNum, aiFn);
        break;
      case 'active':
        await handleActivePhase(sock, syntheticMsg, scriptDir, botDir, slug, group, senderNum, numbers, botPhone, aiFn, botLid);
        break;
    }
    return;
  }

  // ── Log incoming text message ─────────────────────────────────────────────
  const incomingText = msg.message?.conversation || msg.message?.extendedTextMessage?.text || '';
  if (incomingText.trim()) appendGroupMsg(scriptDir, slug, 'user', incomingText, senderNum);

  switch (phase) {
    case 'waiting':
      await handleWaiting(sock, msg, scriptDir, botDir, slug, group, numbers, aiFn, botPhone);
      break;
    case 'form':
      await handleFormPhase(sock, msg, scriptDir, botDir, slug, group, numbers, senderNum, aiFn);
      break;
    case 'cvmaster':
      await handleCvMasterPhase(sock, msg, scriptDir, slug);
      break;
    case 'active':
      await handleActivePhase(sock, msg, scriptDir, botDir, slug, group, senderNum, numbers, botPhone, aiFn, botLid);
      break;
    default:
      console.warn(`[group] unknown phase: ${phase} for ${slug}`);
  }
}
