import { StateGraph, END, START, Annotation } from "@langchain/langgraph";
import { getLlm, type AiProvider } from "./llm-factory";
import type { ConversationStage } from "./aiHandoffStore";

// ── Graph state ───────────────────────────────────────────────────────────────

const GraphState = Annotation.Root({
  message:         Annotation<string>(),
  stage:           Annotation<string>(),
  intent:          Annotation<string>(),
  reply:           Annotation<string>(),
  language:        Annotation<string>(),
  nextStage:       Annotation<string>(),
  generalFallback: Annotation<string>(),
});

type GraphStateType = typeof GraphState.State;

// ── Transfer-to-staff fallback (multilingual) ─────────────────────────────────

const TRANSFER: Record<string, string> = {
  English:          "Thank you for reaching out! Our office staff will assist you shortly.",
  Spanish:          "¡Gracias por comunicarse! Un miembro del personal de la oficina le atenderá en breve.",
  Portuguese:       "Obrigado pelo contato! Nossa equipe entrará em contato em breve.",
  Mandarin:         "感谢您的联系！我们的工作人员将很快为您提供帮助。",
  Cantonese:        "感謝您的聯繫！我們的工作人員將很快為您提供幫助。",
  Arabic:           "شكراً لتواصلك! سيتواصل معك أحد موظفي المكتب قريباً.",
  "Haitian Creole": "Mèsi dèske ou kontakte nou! Yon manm ekip biwo a pral ede ou byento.",
};

function transferMsg(lang: string): string {
  return TRANSFER[lang] ?? TRANSFER["English"]!;
}

// ── Intent classifiers ────────────────────────────────────────────────────────

function wantsAppointment(text: string): boolean {
  return /appointment|schedule|cleaning|checkup|teeth|tooth|cavity|pain|dental|filling|crown|whitening|x-ray|exam|hygienist|dentist/i.test(text);
}

function isNewPatient(text: string): boolean {
  return /new patient|first time|first visit|never been|brand new|haven't been|i am new/i.test(text);
}

function isExistingPatient(text: string): boolean {
  return /existing|old patient|old\b|been there|have been|already|before|i have been|returning|came before|i was there|previous|prior|past patient/i.test(text);
}

function hasMassHealth(text: string): boolean {
  return /masshealth|mass health|medicaid|masscare/i.test(text);
}

function hasInsurance(text: string): boolean {
  return /yes|i have|my insurance|i do|have insurance|insured/i.test(text);
}

function hasNoInsurance(text: string): boolean {
  return /no insurance|uninsured|self.pay|self pay|i don't|don't have|no i don't|i have no|no,? i|not insured/i.test(text);
}

function sameInsurance(text: string): boolean {
  return /yes|same|still have|haven't changed|no change/i.test(text);
}

function changedInsurance(text: string): boolean {
  return /no|changed|different|new insurance|switched|lost|expired/i.test(text);
}

function saysYes(text: string): boolean {
  return /\byes\b|yeah|yep|sure|ok\b|okay|of course|absolutely|please|si\b|sí|oui|wi\b|نعم|好的/i.test(text);
}

function saysNo(text: string): boolean {
  return /\bno\b|nope|nah|not really|i don't|don't|no i|لا|非|no,/i.test(text);
}

// ── LLM reply helper ──────────────────────────────────────────────────────────

async function llmReply(
  system: string,
  userMsg: string,
  fallback: string,
  apiKey: string,
  provider: AiProvider = "google",
  model?: string
): Promise<string> {
  try {
    const llm = getLlm(provider, apiKey, model);
    const res = await llm.invoke([
      { role: "system", content: system },
      { role: "user",   content: userMsg },
    ]);
    return String(res.content).trim() || fallback;
  } catch {
    return fallback;
  }
}

// ── Graph nodes ───────────────────────────────────────────────────────────────

function classifyNode(state: GraphStateType) {
  const text  = state.message.toLowerCase();
  const stage = state.stage as ConversationStage;

  let intent = "other";

  if (stage === "new_patient_greeted") {
    intent = wantsAppointment(text) ? "wants_appointment" : "other";

  } else if (stage === "asked_new_or_existing") {
    if (isNewPatient(text))           intent = "new_patient";
    else if (isExistingPatient(text)) intent = "existing_patient";

  } else if (stage === "asked_new_patient_insurance") {
    if (hasMassHealth(text))     intent = "masshealth";
    else if (hasNoInsurance(text)) intent = "no_insurance";
    else if (hasInsurance(text)) intent = "has_insurance";

  } else if (stage === "asked_insurance_type") {
    if (hasMassHealth(text)) intent = "masshealth";
    else                     intent = "other_insurance";

  } else if (stage === "asked_masshealth_check_consent") {
    if (saysYes(text)) intent = "yes";
    else               intent = "no";

  } else if (stage === "asked_existing_insurance") {
    if (sameInsurance(text))     intent = "same_insurance";
    else if (changedInsurance(text)) intent = "changed_insurance";

  } else if (stage === "asked_appointment_time") {
    intent = "appointment_time";

  } else if (stage === "asked_appointment_preference") {
    intent = "appointment_preference_reply";

  } else if (stage === "asked_self_pay") {
    intent = "self_pay_reply";

  } else if (stage === "asked_other_insurance_after_inactive") {
    if (saysYes(text) && !saysNo(text)) intent = "yes";
    else if (saysNo(text))              intent = "no";

  } else if (stage === "collecting_contact_info") {
    intent = "contact_info_received";
  }

  return { intent };
}

function routeNode(state: GraphStateType): string {
  const stage  = state.stage as ConversationStage;
  const intent = state.intent;

  if (stage === "new_patient_greeted")
    return intent === "wants_appointment" ? "ask_new_or_existing" : "transfer";

  if (stage === "asked_new_or_existing")
    return intent === "new_patient" ? "ask_new_patient_insurance"
         : intent === "existing_patient" ? "ask_existing_insurance"
         : "transfer";

  if (stage === "asked_new_patient_insurance")
    return intent === "masshealth"    ? "ask_masshealth_check_consent"
         : intent === "no_insurance"  ? "ask_appointment_time"
         : intent === "has_insurance" ? "ask_insurance_type"
         : "transfer";

  if (stage === "asked_insurance_type")
    return intent === "masshealth" ? "ask_masshealth_check_consent" : "ask_appointment_time";

  if (stage === "asked_masshealth_check_consent")
    return intent === "yes" ? "ask_masshealth_info" : "ask_appointment_time";

  if (stage === "asked_existing_insurance")
    return intent === "same_insurance" ? "ask_appointment_time" : "transfer";

  if (stage === "asked_appointment_time")          return "acknowledge_appointment_time";
  if (stage === "asked_appointment_preference")    return "handle_appointment_preference";
  if (stage === "asked_self_pay")                  return "handle_self_pay";

  if (stage === "asked_other_insurance_after_inactive")
    return intent === "yes" ? "transfer"
         : intent === "no"  ? "ask_contact_info"
         : "transfer";

  if (stage === "collecting_contact_info") return "acknowledge_contact_info";

  return "transfer";
}

// ── Response nodes ────────────────────────────────────────────────────────────

async function askNewOrExistingNode(state: GraphStateType, config: any) {
  const lang    = state.language || "English";
  const apiKey: string | undefined = config?.configurable?.apiKey;
  const provider: AiProvider = config?.configurable?.provider ?? "google";
  const model: string | undefined = config?.configurable?.model;

  const fallbacks: Record<string, string> = {
    English:          "Are you a new patient or an existing patient?",
    Spanish:          "¿Es usted un paciente nuevo o ya ha visitado nuestra clínica antes?",
    Portuguese:       "Você é um paciente novo ou já veio ao nosso consultório antes?",
    Mandarin:         "您是新患者还是现有患者？",
    Cantonese:        "您係新病人定係舊病人？",
    Arabic:           "هل أنت مريض جديد أم مريض حالي؟",
    "Haitian Creole": "Èske ou se yon nouvo pasyan oswa yon pasyan egzistan?",
  };
  const fallback = fallbacks[lang] ?? fallbacks["English"]!;

  const reply = apiKey
    ? await llmReply(
        `You are a friendly dental office AI assistant. Ask the patient in ${lang} whether they are a new patient or an existing patient. One sentence, no formatting.`,
        `Patient wants an appointment. Ask if new or existing.`,
        fallback, apiKey, provider, model
      )
    : fallback;

  return { reply, nextStage: "asked_new_or_existing" };
}

async function askNewPatientInsuranceNode(state: GraphStateType, config: any) {
  const lang   = state.language || "English";
  const apiKey: string | undefined = config?.configurable?.apiKey;
  const provider: AiProvider = config?.configurable?.provider ?? "google";
  const model: string | undefined = config?.configurable?.model;

  const fallbacks: Record<string, string> = {
    English:          "Do you have any dental insurance?",
    Spanish:          "¿Tiene seguro dental?",
    Portuguese:       "Você tem plano odontológico?",
    Mandarin:         "您有牙科保险吗？",
    Cantonese:        "您有牙科保險嗎？",
    Arabic:           "هل لديك تأمين أسنان؟",
    "Haitian Creole": "Èske ou gen asirans dantè?",
  };
  const fallback = fallbacks[lang] ?? fallbacks["English"]!;

  const reply = apiKey
    ? await llmReply(
        `You are a friendly dental office AI assistant. Ask the new patient in ${lang} if they have dental insurance. One sentence, no formatting.`,
        `New patient confirmed. Ask about insurance.`,
        fallback, apiKey, provider, model
      )
    : fallback;

  return { reply, nextStage: "asked_new_patient_insurance" };
}

async function askInsuranceTypeNode(state: GraphStateType, config: any) {
  const lang   = state.language || "English";
  const apiKey: string | undefined = config?.configurable?.apiKey;
  const provider: AiProvider = config?.configurable?.provider ?? "google";
  const model: string | undefined = config?.configurable?.model;

  const fallbacks: Record<string, string> = {
    English:          "What kind of insurance do you have?",
    Spanish:          "¿Qué tipo de seguro tiene?",
    Portuguese:       "Que tipo de plano de saúde você tem?",
    Mandarin:         "您有什么类型的保险？",
    Cantonese:        "您有什麼類型的保險？",
    Arabic:           "ما نوع التأمين الذي لديك؟",
    "Haitian Creole": "Ki kalite asirans ou genyen?",
  };
  const fallback = fallbacks[lang] ?? fallbacks["English"]!;

  const reply = apiKey
    ? await llmReply(
        `You are a friendly dental office AI assistant. The patient confirmed they have insurance. Ask them in ${lang} what kind of insurance they have. One sentence, no formatting.`,
        `Patient has insurance. Ask what kind.`,
        fallback, apiKey, provider, model
      )
    : fallback;

  return { reply, nextStage: "asked_insurance_type" };
}

async function askMassHealthCheckConsentNode(state: GraphStateType, config: any) {
  const lang   = state.language || "English";
  const apiKey: string | undefined = config?.configurable?.apiKey;
  const provider: AiProvider = config?.configurable?.provider ?? "google";
  const model: string | undefined = config?.configurable?.model;

  const fallbacks: Record<string, string> = {
    English:          "Do you want to check your MassHealth insurance now?",
    Spanish:          "¿Desea verificar su seguro MassHealth ahora?",
    Portuguese:       "Você gostaria de verificar sua cobertura MassHealth agora?",
    Mandarin:         "您想现在查询您的MassHealth保险吗？",
    Cantonese:        "您想現在查詢您的MassHealth保險嗎？",
    Arabic:           "هل تريد التحقق من تأمين MassHealth الخاص بك الآن؟",
    "Haitian Creole": "Èske ou vle verifye asirans MassHealth ou kounye a?",
  };
  const fallback = fallbacks[lang] ?? fallbacks["English"]!;

  const reply = apiKey
    ? await llmReply(
        `You are a friendly dental office AI assistant. The patient has MassHealth insurance. Ask them in ${lang} if they would like to check their MassHealth coverage right now. One sentence, no formatting.`,
        `Patient has MassHealth. Ask if they want to check it now.`,
        fallback, apiKey, provider, model
      )
    : fallback;

  return { reply, nextStage: "asked_masshealth_check_consent" };
}

async function askMassHealthInfoNode(state: GraphStateType, config: any) {
  const lang   = state.language || "English";
  const apiKey: string | undefined = config?.configurable?.apiKey;
  const provider: AiProvider = config?.configurable?.provider ?? "google";
  const model: string | undefined = config?.configurable?.model;

  const fallbacks: Record<string, string> = {
    English:          "Please send me your MassHealth Member ID and date of birth so I can check your coverage.",
    Spanish:          "Por favor envíeme su número de miembro de MassHealth y su fecha de nacimiento para verificar su cobertura.",
    Portuguese:       "Por favor envie seu número de membro MassHealth e data de nascimento para verificar sua cobertura.",
    Mandarin:         "请发送您的MassHealth会员ID和出生日期，以便我查询您的保险。",
    Cantonese:        "請傳送您的MassHealth會員ID和出生日期，以便我查詢您的保險。",
    Arabic:           "من فضلك أرسل لي رقم عضوية MassHealth وتاريخ ميلادك حتى أتحقق من تغطيتك.",
    "Haitian Creole": "Tanpri voye ID manm MassHealth ou ak dat nesans ou pou mwen ka verifye kouvèti ou.",
  };
  const fallback = fallbacks[lang] ?? fallbacks["English"]!;

  const reply = apiKey
    ? await llmReply(
        `You are a friendly dental office AI assistant. The patient wants to check their MassHealth coverage. Ask them in ${lang} to provide their MassHealth Member ID and date of birth. 1-2 sentences, no formatting.`,
        `Patient agreed to MassHealth check. Ask for member ID and DOB.`,
        fallback, apiKey, provider, model
      )
    : fallback;

  return { reply, nextStage: "awaiting_masshealth_info" };
}

async function askExistingInsuranceNode(state: GraphStateType, config: any) {
  const lang   = state.language || "English";
  const apiKey: string | undefined = config?.configurable?.apiKey;
  const provider: AiProvider = config?.configurable?.provider ?? "google";
  const model: string | undefined = config?.configurable?.model;

  const fallbacks: Record<string, string> = {
    English:          "Got it — you are an existing patient! Do you still have the same insurance on file?",
    Spanish:          "¡Entendido — usted es un paciente existente! ¿Sigue teniendo el mismo seguro registrado?",
    Portuguese:       "Entendido — você é um paciente existente! Você ainda tem o mesmo plano registrado?",
    Mandarin:         "好的——您是现有患者！您还有相同的保险记录吗？",
    Cantonese:        "好的——您是現有病人！您仍然有相同的保險記錄嗎？",
    Arabic:           "حسناً — أنت مريض حالي! هل لا تزال تمتلك نفس التأمين المسجل لدينا؟",
    "Haitian Creole": "Konprann — ou se yon pasyan egzistan! Èske ou toujou gen menm asirans nou gen anrejistreman?",
  };
  const fallback = fallbacks[lang] ?? fallbacks["English"]!;

  const reply = apiKey
    ? await llmReply(
        `You are a friendly dental office AI assistant. The patient confirmed they are an existing patient. In ${lang}, acknowledge that they are an existing patient and then ask if they still have the same dental insurance on file. 1-2 sentences, no formatting.`,
        `Existing patient confirmed. Acknowledge and ask about insurance.`,
        fallback, apiKey, provider, model
      )
    : fallback;

  return { reply, nextStage: "asked_existing_insurance" };
}

async function askAppointmentTimeNode(state: GraphStateType, config: any) {
  const lang   = state.language || "English";
  const apiKey: string | undefined = config?.configurable?.apiKey;
  const provider: AiProvider = config?.configurable?.provider ?? "google";
  const model: string | undefined = config?.configurable?.model;

  const fallbacks: Record<string, string> = {
    English:          "When would you like to come in for your appointment?",
    Spanish:          "¿Cuándo le gustaría venir para su cita?",
    Portuguese:       "Quando você gostaria de agendar sua consulta?",
    Mandarin:         "您想什么时候来预约？",
    Cantonese:        "您想幾時來預約？",
    Arabic:           "متى تريد تحديد موعد؟",
    "Haitian Creole": "Ki lè ou ta renmen fè yon randevou?",
  };
  const fallback = fallbacks[lang] ?? fallbacks["English"]!;

  const reply = apiKey
    ? await llmReply(
        `You are a friendly dental office AI assistant. Ask the patient in ${lang} what date and time they would prefer for their appointment. One sentence, no formatting.`,
        `Ask when to schedule.`,
        fallback, apiKey, provider, model
      )
    : fallback;

  return { reply, nextStage: "asked_appointment_time" };
}

async function acknowledgeAppointmentTimeNode(state: GraphStateType, config: any) {
  const lang   = state.language || "English";
  const apiKey: string | undefined = config?.configurable?.apiKey;
  const provider: AiProvider = config?.configurable?.provider ?? "google";
  const model: string | undefined = config?.configurable?.model;

  const fallbacks: Record<string, string> = {
    English:          "Thank you! Our office staff will confirm your appointment details shortly.",
    Spanish:          "¡Gracias! El personal de la oficina confirmará los detalles de su cita en breve.",
    Portuguese:       "Obrigado! Nossa equipe confirmará os detalhes da sua consulta em breve.",
    Mandarin:         "谢谢！我们的工作人员将很快确认您的预约详情。",
    Cantonese:        "多謝！我們的工作人員將很快確認您的預約詳情。",
    Arabic:           "شكراً! سيؤكد فريق مكتبنا تفاصيل موعدك قريباً.",
    "Haitian Creole": "Mèsi! Ekip biwo nou an pral konfime detay randevou ou a byento.",
  };
  const fallback = fallbacks[lang] ?? fallbacks["English"]!;

  const reply = apiKey
    ? await llmReply(
        `You are a friendly dental office AI assistant. The patient stated their preferred appointment time. Acknowledge in ${lang} and tell them the office staff will confirm shortly. 1-2 sentences, no formatting.`,
        `Patient said: "${state.message}". Acknowledge.`,
        fallback, apiKey, provider, model
      )
    : fallback;

  return { reply, nextStage: "done" };
}

async function handleAppointmentPreferenceNode(state: GraphStateType, config: any) {
  const lang   = state.language || "English";
  const apiKey: string | undefined = config?.configurable?.apiKey;
  const provider: AiProvider = config?.configurable?.provider ?? "google";
  const model: string | undefined = config?.configurable?.model;

  const fallbacks: Record<string, string> = {
    English:          "When would you like to come in? Are you looking for a routine check-up and teeth cleaning, or do you have a tooth problem or pain?",
    Spanish:          "¿Cuándo le gustaría venir? ¿Busca una revisión rutinaria y limpieza dental, o tiene algún problema dental o dolor?",
    Portuguese:       "Quando gostaria de vir? Você busca uma consulta de rotina e limpeza, ou tem algum problema dentário ou dor?",
    Mandarin:         "您想什么时候来？您是想做常规检查和洗牙，还是您有牙齿问题或疼痛？",
    Cantonese:        "您想幾時來？您是想做例行檢查和洗牙，還是您有牙齒問題或疼痛？",
    Arabic:           "متى تودّ الحضور؟ هل تبحث عن فحص روتيني وتنظيف أسنان، أم أن لديك مشكلة في الأسنان أو ألماً؟",
    "Haitian Creole": "Ki lè ou ta renmen vini? Èske ou ap chèche yon egzamen woutin ak netwayaj dan, oswa èske ou gen yon pwoblèm dan oswa doulè?",
  };
  const fallback = fallbacks[lang] ?? fallbacks["English"]!;

  const reply = apiKey
    ? await llmReply(
        `You are a friendly dental office AI assistant. The patient's MassHealth is active. In ${lang}, ask when they would like to come in and whether they want a routine check-up and teeth cleaning, or if they have a dental problem or pain. 1-2 sentences, no formatting.`,
        `MassHealth is active. Ask appointment preference.`,
        fallback, apiKey, provider, model
      )
    : fallback;

  return { reply, nextStage: "asked_appointment_time" };
}

async function handleSelfPayNode(state: GraphStateType, config: any) {
  const lang   = state.language || "English";
  const apiKey: string | undefined = config?.configurable?.apiKey;
  const provider: AiProvider = config?.configurable?.provider ?? "google";
  const model: string | undefined = config?.configurable?.model;
  const text   = state.message.toLowerCase();

  const acceptsSelfPay = /yes|sure|ok|okay|yep|yeah|sí|si|claro|sim|confirmado|好的|نعم|wi|oke/i.test(text);
  const declinesSelfPay = /no|nope|can't|won't|not interested|no puedo|não|لا|pa ka/i.test(text);

  if (declinesSelfPay) {
    const declineFallbacks: Record<string, string> = {
      English:          "No problem! If you ever change your mind or have any questions, feel free to reach out. Our staff is happy to help.",
      Spanish:          "¡Sin problema! Si cambia de opinión o tiene alguna pregunta, no dude en contactarnos.",
      Portuguese:       "Sem problema! Se mudar de ideia ou tiver dúvidas, entre em contato. Nossa equipe está feliz em ajudar.",
      Mandarin:         "没关系！如果您改变主意或有任何问题，随时联系我们，我们的工作人员很乐意帮助您。",
      Cantonese:        "沒問題！如果您改變主意或有任何問題，隨時聯繫我們。",
      Arabic:           "لا بأس! إذا غيّرت رأيك أو كان لديك أي سؤال، لا تتردد في التواصل معنا.",
      "Haitian Creole": "Pa gen pwoblèm! Si ou chanje lide ou oswa gen kesyon, pa ezite kontakte nou.",
    };
    const fallback = declineFallbacks[lang] ?? declineFallbacks["English"]!;
    const reply = apiKey
      ? await llmReply(
          `You are a friendly dental office AI assistant. The patient declined a self-pay appointment. In ${lang}, politely close the conversation and invite them to reach out anytime. 1-2 sentences, no formatting.`,
          `Patient declined self-pay.`,
          fallback, apiKey
        )
      : fallback;
    return { reply, nextStage: "done" };
  }

  if (acceptsSelfPay) {
    const acceptFallbacks: Record<string, string> = {
      English:          "When would you like to come in for your examination?",
      Spanish:          "¿Cuándo le gustaría venir para su examen?",
      Portuguese:       "Quando você gostaria de vir para seu exame?",
      Mandarin:         "您想什么时候来进行检查？",
      Cantonese:        "您想幾時來進行檢查？",
      Arabic:           "متى تودّ الحضور للفحص؟",
      "Haitian Creole": "Ki lè ou ta renmen vini pou egzamen ou?",
    };
    const fallback = acceptFallbacks[lang] ?? acceptFallbacks["English"]!;
    const reply = apiKey
      ? await llmReply(
          `You are a friendly dental office AI assistant. The patient agreed to a self-pay examination. In ${lang}, ask when they would like to come in. One sentence, no formatting.`,
          `Patient agreed to self-pay. Ask when to come in.`,
          fallback, apiKey
        )
      : fallback;
    return { reply, nextStage: "asked_appointment_time" };
  }

  return { reply: transferMsg(lang), nextStage: "done" };
}

async function askContactInfoNode(state: GraphStateType, config: any) {
  const lang   = state.language || "English";
  const apiKey: string | undefined = config?.configurable?.apiKey;
  const provider: AiProvider = config?.configurable?.provider ?? "google";
  const model: string | undefined = config?.configurable?.model;

  const fallbacks: Record<string, string> = {
    English:          "Please leave your name and phone number. Our receptionist will contact you as soon as possible.",
    Spanish:          "Por favor déjenos su nombre y número de teléfono. Nuestra recepcionista se comunicará con usted lo antes posible.",
    Portuguese:       "Por favor deixe seu nome e número de telefone. Nossa recepcionista entrará em contato o mais breve possível.",
    Mandarin:         "请留下您的姓名和电话号码，我们的前台将尽快与您联系。",
    Cantonese:        "請留下您的姓名和電話號碼，我們的接待員將盡快與您聯絡。",
    Arabic:           "يرجى ترك اسمك ورقم هاتفك. ستتواصل معك موظفة الاستقبال في أقرب وقت ممكن.",
    "Haitian Creole": "Tanpri kite non ou ak nimewo telefòn ou. Resepsyonis nou an pral kontakte ou pi vit posib.",
  };
  const fallback = fallbacks[lang] ?? fallbacks["English"]!;

  const reply = apiKey
    ? await llmReply(
        `You are a friendly dental office AI assistant. The patient's MassHealth is inactive and they have no other insurance. In ${lang}, politely ask them to leave their name and phone number so the receptionist can contact them. 1-2 sentences, no formatting.`,
        `MassHealth inactive, no other insurance. Ask for name and phone.`,
        fallback, apiKey, provider, model
      )
    : fallback;

  return { reply, nextStage: "collecting_contact_info" };
}

async function acknowledgeContactInfoNode(state: GraphStateType, config: any) {
  const lang   = state.language || "English";
  const apiKey: string | undefined = config?.configurable?.apiKey;
  const provider: AiProvider = config?.configurable?.provider ?? "google";
  const model: string | undefined = config?.configurable?.model;

  const fallbacks: Record<string, string> = {
    English:          "Thank you! Our receptionist will reach out to you shortly.",
    Spanish:          "¡Gracias! Nuestra recepcionista se pondrá en contacto con usted en breve.",
    Portuguese:       "Obrigado! Nossa recepcionista entrará em contato com você em breve.",
    Mandarin:         "谢谢！我们的前台将很快与您联系。",
    Cantonese:        "多謝！我們的接待員將很快與您聯絡。",
    Arabic:           "شكراً! ستتواصل معك موظفة الاستقبال قريباً.",
    "Haitian Creole": "Mèsi! Resepsyonis nou an pral kontakte ou byento.",
  };
  const fallback = fallbacks[lang] ?? fallbacks["English"]!;

  const reply = apiKey
    ? await llmReply(
        `You are a friendly dental office AI assistant. The patient has left their contact information. Thank them in ${lang} and let them know the receptionist will reach out soon. 1-2 sentences, no formatting.`,
        `Patient provided contact info. Acknowledge.`,
        fallback, apiKey, provider, model
      )
    : fallback;

  return { reply, nextStage: "done" };
}

function transferNode(state: GraphStateType) {
  const lang = state.language || "English";
  return { reply: state.generalFallback || transferMsg(lang), nextStage: "done" };
}

// ── Graph assembly ────────────────────────────────────────────────────────────

const graph = new StateGraph(GraphState)
  .addNode("classify",                     classifyNode)
  .addNode("ask_new_or_existing",          askNewOrExistingNode)
  .addNode("ask_new_patient_insurance",    askNewPatientInsuranceNode)
  .addNode("ask_insurance_type",           askInsuranceTypeNode)
  .addNode("ask_masshealth_check_consent", askMassHealthCheckConsentNode)
  .addNode("ask_masshealth_info",          askMassHealthInfoNode)
  .addNode("ask_existing_insurance",       askExistingInsuranceNode)
  .addNode("ask_appointment_time",         askAppointmentTimeNode)
  .addNode("acknowledge_appointment_time", acknowledgeAppointmentTimeNode)
  .addNode("handle_appointment_preference",handleAppointmentPreferenceNode)
  .addNode("handle_self_pay",              handleSelfPayNode)
  .addNode("ask_contact_info",             askContactInfoNode)
  .addNode("acknowledge_contact_info",     acknowledgeContactInfoNode)
  .addNode("transfer",                     transferNode)
  .addEdge(START, "classify")
  .addConditionalEdges("classify", routeNode, {
    ask_new_or_existing:           "ask_new_or_existing",
    ask_new_patient_insurance:     "ask_new_patient_insurance",
    ask_insurance_type:            "ask_insurance_type",
    ask_masshealth_check_consent:  "ask_masshealth_check_consent",
    ask_masshealth_info:           "ask_masshealth_info",
    ask_existing_insurance:        "ask_existing_insurance",
    ask_appointment_time:          "ask_appointment_time",
    acknowledge_appointment_time:  "acknowledge_appointment_time",
    handle_appointment_preference: "handle_appointment_preference",
    handle_self_pay:               "handle_self_pay",
    ask_contact_info:              "ask_contact_info",
    acknowledge_contact_info:      "acknowledge_contact_info",
    transfer:                      "transfer",
  })
  .addEdge("ask_new_or_existing",           END)
  .addEdge("ask_new_patient_insurance",     END)
  .addEdge("ask_insurance_type",            END)
  .addEdge("ask_masshealth_check_consent",  END)
  .addEdge("ask_masshealth_info",           END)
  .addEdge("ask_existing_insurance",        END)
  .addEdge("ask_appointment_time",          END)
  .addEdge("acknowledge_appointment_time",  END)
  .addEdge("handle_appointment_preference", END)
  .addEdge("handle_self_pay",               END)
  .addEdge("ask_contact_info",              END)
  .addEdge("acknowledge_contact_info",      END)
  .addEdge("transfer",                      END)
  .compile();

// ── Public API ────────────────────────────────────────────────────────────────

export async function runNewPatientStep(
  message:         string,
  stage:           ConversationStage,
  language:        string,
  apiKey:          string,
  generalFallback  = "",
  provider: AiProvider = "google",
  model?: string
): Promise<{ reply: string; nextStage: ConversationStage }> {
  const result = await graph.invoke(
    { message, stage, intent: "", reply: "", language, nextStage: "", generalFallback },
    { configurable: { apiKey, provider, model } }
  );
  return {
    reply:     result.reply     || transferMsg(language),
    nextStage: (result.nextStage as ConversationStage) || "done",
  };
}
