// Copilot NLU — deterministic rule-based parser. No LLM.
// Returns a list of typed intents extracted from a user message + a confidence/ambiguity field.
//
// Intent shapes (all carry { kind, ... }):
//   { kind: 'set', field, value, ambig?: true, candidates?: [...] }   field updates
//   { kind: 'calc', what }                                               calc requests: 'energy'|'protein'|'fluid'|'all'
//   { kind: 'method', method }                                           switch energy method
//   { kind: 'doc', what }                                                'pes'|'interventions'|'note'|'all'
//   { kind: 'explain', topic }                                           ask why/what
//   { kind: 'meta', topic }                                              meta about prototype
//   { kind: 'flag', action, ruleId }                                     respond to a clinical flag
//   { kind: 'oos' }                                                      out-of-scope
//   { kind: 'noop' }                                                     small talk / nothing actionable

(function () {
  const { DIAGNOSES, ACTIVITY, COMORBIDITIES, FOOD_SECURITY } = window.RD;

  // ----- Lexicons --------------------------------------------------------
  const DX_PATTERNS = [
    { id: "ckd",     re: /\b(ckd|chronic kidney disease|kidney disease)\b/i },
    { id: "obesity", re: /\b(obesity|weight management|bariatric)\b/i },
    { id: "t2dm",    re: /\b(t2dm|type ?2 ?diabetes|type ii diabetes|diabetes type 2|t2d)\b/i },
    // v1d S2: prediabetes is a primary diagnosis; must match before the weak generic "diabetes".
    { id: "prediabetes", re: /\b(pre-?diabetes|pre-?diabetic|impaired (fasting )?glucose|igt|ifg)\b/i },
    // Generic "diabetes" only — assume T2DM for prototype, but flag mild ambig.
    { id: "t2dm",    re: /\bdiabetes\b/i, weak: true },
    { id: "wound",   re: /\b(wound|pressure (injury|ulcer)|sacral|stage ?[1-4] (pressure|sacral|wound|injury|ulcer)|decubitus)\b/i },
    { id: "elderly", re: /\b(sarcopenia|elderly malnutrition|geriatric malnutrition|frail(ty)?)\b/i },
    { id: "healthy", re: /\b(healthy adult|maintenance|wellness)\b/i },
  ];

  const COMORB_PATTERNS = [
    { name: "Hypertension",            re: /\b(htn|hypertension|high blood pressure)\b/i },
    { name: "Hyperlipidemia",          re: /\b(hld|hyperlipidemia|high cholesterol|dyslipidemia)\b/i },
    { name: "Overweight",              re: /\boverweight\b/i },
    { name: "Underweight/Malnutrition",re: /\b(underweight|malnutrition|malnourished|cachexia)\b/i },
    { name: "Dialysis",                re: /\b(dialysis|hemodialysis|peritoneal dialysis|hd|pd)\b/i },
    { name: "Cancer",                  re: /\b(cancer|malignan(t|cy)|oncolog(y|ic))\b/i },
    { name: "GI Disorder",             re: /\b(gi disorder|crohn|colitis|ibs|ibd|celiac|gastroparesis)\b/i },
  ];

  const ACTIVITY_PATTERNS = [
    { id: "sedentary", re: /\b(sedentary|inactive|no exercise|desk job(?! and))\b/i },
    { id: "light",     re: /\b(light(ly)? active|some exercise|1-?3 ?days?|walks? (a |occasional)|mildly active)\b/i },
    { id: "moderate",  re: /\b(moderate(ly)? active|3-?5 ?days?|regular exercise)\b/i },
    { id: "very",      re: /\b(very active|6-?7 ?days?|trains? \d|works out daily|daily exercise)\b/i },
    { id: "extra",     re: /\b(extra(ordinarily)? active|physical job|athlete|professional athlete)\b/i },
    // Ambiguous — needs disambiguation
    { id: null,        re: /\b(active|exercises|works out)\b/i, weak: true },
  ];

  // ----- Helpers ----------------------------------------------------------
  function findDx(text) {
    for (const p of DX_PATTERNS) {
      if (p.re.test(text)) return p.id;
    }
    return null;
  }

  function findComorbidities(text) {
    const found = [];
    for (const p of COMORB_PATTERNS) {
      if (p.re.test(text)) found.push(p.name);
    }
    return [...new Set(found)];
  }

  function findActivity(text) {
    let weakHit = null;
    for (const p of ACTIVITY_PATTERNS) {
      if (p.re.test(text)) {
        if (p.id) return { id: p.id, ambig: false };
        weakHit = p;
      }
    }
    if (weakHit) return { id: null, ambig: true };
    return null;
  }

  function findCkdStage(text) {
    // Match "stage 3", "stage 4", "stage 5" near "ckd" OR "kidney"
    const stageMatch = text.match(/\bstage ?([1-5])\b/i);
    if (stageMatch && /\b(ckd|kidney)\b/i.test(text)) {
      return parseInt(stageMatch[1], 10);
    }
    return null;
  }

  function findWoundStage(text) {
    const m = text.match(/\bstage ?([1-4])\b/i);
    if (m && /\b(pressure|wound|sacral|injury|ulcer|decubitus)\b/i.test(text)) {
      return parseInt(m[1], 10);
    }
    return null;
  }

  // Age — "52F", "52 yo", "age 52", "74-year-old"
  function findAge(text) {
    const m1 = text.match(/\b(\d{1,3})\s*(?:y\.?o\.?|yo|years?\s*old|year-?old)\b/i);
    if (m1) {
      const a = parseInt(m1[1], 10);
      if (a > 0 && a < 130) return a;
    }
    const m2 = text.match(/\bage\s*[:=]?\s*(\d{1,3})\b/i);
    if (m2) {
      const a = parseInt(m2[1], 10);
      if (a > 0 && a < 130) return a;
    }
    // "52F" / "74M" — age + sex glued
    const m3 = text.match(/\b(\d{1,3})\s*([fmFM])\b/);
    if (m3) {
      const a = parseInt(m3[1], 10);
      if (a > 0 && a < 130) return a;
    }
    return null;
  }

  function findSex(text) {
    if (/\b(female|woman|she\/her|she)\b/i.test(text)) return "F";
    if (/\b(male|man|he\/him|he)\b/i.test(text)) return "M";
    const m = text.match(/\b\d{1,3}\s*([fmFM])\b/);
    if (m) return m[1].toUpperCase();
    return null;
  }

  // Weight — "89.5 kg", "187 lbs", "weight 125", "weighs 70kg"
  // Returns { value, unit, ambig?: true } where ambig=true means a number was given without units.
  function findWeight(text) {
    // Explicit unit
    const m1 = text.match(/\b(?:weight|weighs|wt)[\s:=]*?(\d{1,3}(?:\.\d+)?)\s*(kg|kilograms?|lb|lbs|pounds?)\b/i);
    if (m1) {
      const v = parseFloat(m1[1]);
      const u = /^k/i.test(m1[2]) ? "kg" : "lb";
      return { value: v, unit: u };
    }
    const m2 = text.match(/\b(\d{1,3}(?:\.\d+)?)\s*(kg|kilograms?|lb|lbs|pounds?)\b/i);
    if (m2) {
      const v = parseFloat(m2[1]);
      const u = /^k/i.test(m2[2]) ? "kg" : "lb";
      return { value: v, unit: u };
    }
    // "weight 125" — ambiguous
    const m3 = text.match(/\b(?:weight|weighs|wt)[\s:=]*(\d{1,3}(?:\.\d+)?)\b/i);
    if (m3) {
      return { value: parseFloat(m3[1]), unit: null, ambig: true };
    }
    return null;
  }

  // Height — "163 cm", "5'4\"", "5 ft 4 in", "65 inches", "height 64"
  function findHeight(text) {
    // 5'4", 5' 4", 5 ft 4 in, 5'4
    const m1 = text.match(/\b(\d)\s*(?:'|ft|feet)\s*(\d{1,2})?\s*(?:"|in|inches?)?/i);
    if (m1) {
      const ft = parseInt(m1[1], 10);
      const inch = m1[2] ? parseInt(m1[2], 10) : 0;
      if (ft >= 3 && ft <= 8) {
        return { value: ft * 12 + inch, unit: "in" };
      }
    }
    // Explicit unit
    const m2 = text.match(/\b(?:height|ht)[\s:=]*?(\d{2,3}(?:\.\d+)?)\s*(cm|centimeters?|in|inches?)\b/i);
    if (m2) {
      const v = parseFloat(m2[1]);
      const u = /^c/i.test(m2[2]) ? "cm" : "in";
      return { value: v, unit: u };
    }
    const m3 = text.match(/\b(\d{2,3}(?:\.\d+)?)\s*(cm|centimeters?|in|inches?)\b/i);
    if (m3) {
      const v = parseFloat(m3[1]);
      const u = /^c/i.test(m3[2]) ? "cm" : "in";
      return { value: v, unit: u };
    }
    return null;
  }

  // Weight loss / change — "lost 12 lbs in 3 months", "10% in 6 months", "unintentional weight loss"
  function findWeightLoss(text) {
    if (!/\b(weight loss|lost|losing|dropped)\b/i.test(text)) return null;
    const m = text.match(/\b(?:lost|dropped|down)\s*(\d+(?:\.\d+)?)\s*(lb|lbs|pounds|kg|kilograms?|%)?\s*(?:in\s*(\d+)\s*(day|days|week|weeks|month|months))?/i);
    if (m) {
      return {
        amount: parseFloat(m[1]),
        unit: m[2] || null,
        period: m[3] ? `${m[3]} ${m[4]}` : null,
        text: m[0],
      };
    }
    // Just mentions weight loss without numbers
    if (/\b(unintentional|recent|significant|noticeable) (weight loss|wt loss)\b/i.test(text)) {
      return { unspecified: true, text: text.match(/\b(unintentional|recent|significant|noticeable) (weight loss|wt loss)\b/i)[0] };
    }
    return null;
  }

  // Labs — parse known abbreviations into canonical "Label value[unit]" strings.
  // Returns { labs: [...], bareNumbers: [...] } where bareNumbers are unattached.
  const LAB_PATTERNS = [
    { name: "HbA1c",      re: /\b(?:hba1c|a1c)[\s:=]*([\d.]+)\s*%?/i,        suffix: "%" },
    { name: "eGFR",       re: /\b(?:e?gfr)[\s:=]*([\d.]+)(?:\s*ml\/min)?/i,  suffix: "" },
    { name: "Albumin",    re: /\balbumin[\s:=]*([\d.]+)/i,                    suffix: "" },
    { name: "Creatinine", re: /\bcreatinine[\s:=]*([\d.]+)/i,                 suffix: "" },
    { name: "BUN",        re: /\bbun[\s:=]*([\d.]+)/i,                        suffix: "" },
    { name: "Potassium",  re: /\b(?:potassium|k\+)[\s:=]*([\d.]+)/i,          suffix: "" },
    { name: "Phosphorus", re: /\b(?:phosphorus|phos)[\s:=]*([\d.]+)/i,        suffix: "" },
  ];

  function findLabs(text) {
    const labs = [];
    for (const p of LAB_PATTERNS) {
      const m = text.match(p.re);
      if (m) {
        const val = parseFloat(m[1]);
        if (Number.isFinite(val)) labs.push(`${p.name} ${val}${p.suffix}`);
      }
    }
    return labs;
  }

  // Detect a bare ambiguous number — "5.2" alone, no units, no field hints
  // and no other extracted intents.
  function isBareNumberOnly(text) {
    const t = text.trim();
    return /^\d+(\.\d+)?$/.test(t);
  }

  // ----- Top-level classify ----------------------------------------------
  function classify(text) {
    const t = text.trim();
    if (!t) return [{ kind: "noop" }];

    const intents = [];

    // Method change
    if (/\b(use|switch to|change to|try)\s*(harris[\- ]?benedict|harris)\b/i.test(t)) {
      intents.push({ kind: "method", method: "hb" });
    }
    if (/\b(use|switch to|change to)\s*(mifflin|mifflin[\- ]?st\.?[\- ]?jeor|mifflin st jeor)\b/i.test(t)) {
      intents.push({ kind: "method", method: "msj" });
    }

    // Calculation requests
    if (/\b(calculate|compute|run|recompute|recalculate)\b/i.test(t) ||
        /\b(energy|calorie|kcal) needs?\b/i.test(t) ||
        /\b(protein|fluid)s? (needs?|requirements?)\b/i.test(t)) {
      let what = "all";
      if (/\benergy|calorie|kcal\b/i.test(t)) what = "energy";
      if (/\bprotein\b/i.test(t)) what = "protein";
      if (/\bfluid\b/i.test(t)) what = "fluid";
      if (/\b(all|everything)\b/i.test(t)) what = "all";
      intents.push({ kind: "calc", what });
    }

    // Documentation requests
    if (/\b(draft|write|generate|regenerate|redraft|build)\b/i.test(t) &&
        /\b(pes|note|intervention|monitoring|documentation)\b/i.test(t)) {
      let what = "all";
      if (/\bpes\b/i.test(t)) what = "pes";
      if (/\bintervention/i.test(t)) what = "interventions";
      if (/\bmonitoring/i.test(t)) what = "monitoring";
      if (/\bnote\b/i.test(t)) what = "note";
      intents.push({ kind: "doc", what });
    }

    // Explanation requests
    if (/\b(why|how come|explain|what does .* mean|what is)\b/i.test(t)) {
      let topic = "general";
      if (/\b(0\.6|0\.55|protein|g\/kg)\b/i.test(t)) topic = "protein";
      else if (/\b(activity|tdee|multiplier)\b/i.test(t)) topic = "activity";
      else if (/\b(mifflin|harris|bmr|energy)\b/i.test(t)) topic = "energy";
      else if (/\b(adjusted|abw|ibw|ideal body weight)\b/i.test(t)) topic = "abw";
      else if (/\b(bmi)\b/i.test(t)) topic = "bmi";
      else if (/\b(citation|source|reference)\b/i.test(t)) topic = "citations";
      intents.push({ kind: "explain", topic });
    }

    // Meta about prototype
    if (/\b(what can you do|how does this work|are you|hipaa|privacy|store|save|persistence|prototype|capabilities)\b/i.test(t)) {
      let topic = "general";
      if (/\bhipaa|privacy|phi|store|save|persistence\b/i.test(t)) topic = "privacy";
      else if (/\bcitation|library|sources?\b/i.test(t)) topic = "citations";
      else if (/\bcalculation|formula\b/i.test(t)) topic = "methods";
      intents.push({ kind: "meta", topic });
    }

    // OOS — generic clinical questions not about THIS patient
    if (/\b(protein recommendation for|recommendation for|guideline for|drug interaction|dosing for|side effects of|differential diagnosis)\b/i.test(t) &&
        !/\b(this patient|my patient|the patient)\b/i.test(t)) {
      // Plausibly OOS — check if no patient-specific data extracted
      const hasPatientCtx = findAge(t) || findSex(t) || findWeight(t) || findHeight(t) || findDx(t);
      if (!hasPatientCtx) {
        intents.push({ kind: "oos", topic: t.slice(0, 80) });
        return intents;
      }
    }

    // ----- Field extractions (set intents) -----
    const age = findAge(t);
    if (age) intents.push({ kind: "set", field: "age", value: String(age) });

    const sex = findSex(t);
    if (sex) intents.push({ kind: "set", field: "sex", value: sex });

    const weight = findWeight(t);
    if (weight) {
      if (weight.ambig) {
        intents.push({ kind: "set", field: "weight", value: weight.value, ambig: true,
                       candidates: [{ unit: "lb", label: "lb" }, { unit: "kg", label: "kg" }] });
      } else {
        intents.push({ kind: "set", field: "weight", value: weight.value, unit: weight.unit });
      }
    }

    const height = findHeight(t);
    if (height) intents.push({ kind: "set", field: "height", value: height.value, unit: height.unit });

    const dxId = findDx(t);
    if (dxId) {
      // For CKD, check stage info
      const stage = findCkdStage(t);
      if (dxId === "ckd" && !stage) {
        intents.push({ kind: "set", field: "diagnosis", value: dxId, ambig: true,
                       candidates: [
                         { stage: 3, label: "Stage 3" },
                         { stage: 4, label: "Stage 4" },
                         { stage: 5, label: "Stage 5" },
                       ] });
      } else {
        intents.push({ kind: "set", field: "diagnosis", value: dxId, stage });
      }
    }

    const comorbs = findComorbidities(t);
    if (comorbs.length) intents.push({ kind: "set", field: "comorbidities", value: comorbs });

    const activity = findActivity(t);
    if (activity) {
      if (activity.ambig) {
        intents.push({ kind: "set", field: "activity", ambig: true,
                       candidates: ACTIVITY.map(a => ({ id: a.id, label: a.label })) });
      } else {
        intents.push({ kind: "set", field: "activity", value: activity.id });
      }
    }

    const wl = findWeightLoss(t);
    if (wl) intents.push({ kind: "set", field: "weightChange", value: wl });

    const labs = findLabs(t);
    if (labs.length) intents.push({ kind: "set", field: "labs", value: labs });

    // Food security mentions
    if (/\b(food (in)?secure|food access|limited (food )?access|food insecure|food insecurity|no access to food|skipping meals|cannot afford food)\b/i.test(t)) {
      const cls = /\bfood secure\b/i.test(t) ? "secure"
                : /\bfood insecure|food insecurity|cannot afford|skipping meals\b/i.test(t) ? "insecure"
                : "limited";
      intents.push({ kind: "set", field: "foodSecurity", value: cls });
    }

    // Set X to Y patterns — fallback for "set weight to 80 kg"
    // Already handled by extractors above.

    // Bare ambiguous number — ask which lab
    if (intents.length === 0 && isBareNumberOnly(t)) {
      intents.push({ kind: "set", field: "lab", ambig: true, value: parseFloat(t),
                     candidates: [
                       { name: "HbA1c", suffix: "%" },
                       { name: "eGFR", suffix: "" },
                       { name: "Albumin", suffix: "" },
                       { name: "Creatinine", suffix: "" },
                       { name: "BUN", suffix: "" },
                     ] });
    }

    if (intents.length === 0) {
      intents.push({ kind: "noop", text: t });
    }

    return intents;
  }

  window.RD_NLU = { classify };
})();
