/* ============================================================
   Glafia Dex — Roux the mascot + offline conversion engine
   No backend: works on Cloudflare Pages as static files.
   Exports: Mascot, rouxReply (for testing)
============================================================ */

/* ---------------- conversion engine ---------------- */
const VOL = { ml:1, milliliter:1, millilitre:1, millilitres:1, milliliters:1,
  l:1000, liter:1000, litre:1000, liters:1000, litres:1000,
  tsp:5, teaspoon:5, teaspoons:5, tbsp:15, tablespoon:15, tablespoons:15,
  cup:240, cups:240, 'fl oz':29.57, floz:29.57, 'fluid ounce':29.57, 'fluid ounces':29.57 };
const WT = { g:1, gram:1, grams:1, gramme:1, grammes:1, kg:1000, kilogram:1000, kilograms:1000, kilo:1000, kilos:1000,
  oz:28.35, ounce:28.35, ounces:28.35, lb:453.59, lbs:453.59, pound:453.59, pounds:453.59 };
const TEMP = { c:'c', '°c':'c', celsius:'c', centigrade:'c', f:'f', '°f':'f', fahrenheit:'f' };
// grams per 240ml cup
const DENS = { flour:120, 'plain flour':120, 'all-purpose flour':120, 'all purpose flour':120, 'self-raising flour':120,
  sugar:200, 'caster sugar':200, 'granulated sugar':200, 'white sugar':200, 'brown sugar':220,
  'icing sugar':125, 'powdered sugar':125, butter:227, rice:185, oats:90, 'rolled oats':90,
  milk:240, water:240, honey:340, 'maple syrup':320, cocoa:100, 'cocoa powder':100, breadcrumbs:110,
  cornflour:120, cornstarch:120, salt:288, 'chopped nuts':140, raisins:160 };
const GAS = { 1:140, 2:150, 3:170, 4:180, 5:190, 6:200, 7:220, 8:230, 9:240 };

function pretty(n){
  if (n >= 50) return String(Math.round(n));
  if (n >= 10) return String(Math.round(n * 10) / 10);
  const whole = Math.floor(n + 1e-9);
  const f = n - whole;
  const opts = [[0,''],[0.25,'¼'],[0.333,'⅓'],[0.5,'½'],[0.667,'⅔'],[0.75,'¾'],[1,'']];
  let best = opts[0], bd = 1;
  for (const o of opts){ const d = Math.abs(f - o[0]); if (d < bd){ bd = d; best = o; } }
  let w = whole, fr = best[1];
  if (best[0] === 1) { w += 1; fr = ''; }
  if (w === 0 && fr === '') return n < 0.06 ? '0' : String(Math.round(n * 100) / 100);
  return (w > 0 ? w : '') + fr;
}
function parseNum(s){
  s = s.replace('½','.5').replace('¼','.25').replace('¾','.75').replace('⅓','.333').replace('⅔','.667');
  const mixed = s.match(/(\d+)\s+(\d+)\s*\/\s*(\d+)/);
  if (mixed) return (+mixed[1]) + (+mixed[2] / +mixed[3]);
  const frac = s.match(/(\d+)\s*\/\s*(\d+)/);
  if (frac) return (+frac[1]) / (+frac[2]);
  const dec = s.match(/-?\d+(?:\.\d+)?/);
  return dec ? parseFloat(dec[0]) : null;
}
function findUnit(seg){
  seg = ' ' + seg.replace(/°/g, ' °').replace(/\s+/g, ' ').trim() + ' ';
  const multi = ['fluid ounces','fluid ounce','fl oz'];
  for (const m of multi) if (seg.includes(m)) return m;
  const allKeys = Object.keys(VOL).concat(Object.keys(WT)).concat(['c','f','celsius','fahrenheit','centigrade']);
  // longest token match
  let found = null, flen = 0;
  for (const k of allKeys){
    if (k.includes(' ')) continue;
    const re = new RegExp('(^|[^a-z])' + k + '($|[^a-z])');
    if (re.test(seg) && k.length > flen){ found = k; flen = k.length; }
  }
  return found;
}
function unitKind(u){
  if (!u) return null;
  if (u in TEMP || u === 'celsius' || u === 'fahrenheit' || u === 'centigrade') return 'temp';
  if (u in VOL) return 'vol';
  if (u in WT) return 'wt';
  return null;
}
function findIngredient(text){
  let best = null, blen = 0;
  for (const k of Object.keys(DENS)){
    if (text.includes(k) && k.length > blen){ best = k; blen = k.length; }
  }
  return best;
}

function rouxReply(raw){
  const text = (raw || '').toLowerCase().trim();
  if (!text) return null;

  // social
  if (/^(hi|hey|hello|yo|hiya|hewwo|sup|morning|evening)\b/.test(text))
    return "Mrrp! 🐾 I'm Nedi. Toss me a temperature or measurement and I'll convert it, try “180°C in °F” or “200g flour to cups”.";
  if (/(thank|thx|ty|cheers|good (cat|kitty)|love you)/.test(text))
    return "Anytime! 😺 *purrs*";
  if (/(who are you|your name|what are you)/.test(text))
    return "I'm Nedi, your kitchen cat 🐈, calico, one winking eye, excellent at conversions.";
  if (/(help|what can you|how do you work)/.test(text))
    return "I convert cooking measures, all offline 🧶\n• Temperatures: “180°C to °F”, “gas mark 6”\n• Weights: “8 oz in grams”, “1.5 kg to lb”\n• Volumes: “500 ml in cups”, “2 tbsp to ml”\n• Cups ↔ grams: “1 cup butter in grams” (I know common ingredients!)";

  // ----- gas mark -----
  const gas = text.match(/gas\s*(?:mark)?\s*(\d)/);
  if (gas){
    const c = GAS[+gas[1]];
    if (c) return `Gas mark ${gas[1]} ≈ ${c}°C = ${Math.round(c*9/5+32)}°F ♨️`;
  }
  // gas mark from a temperature
  // ----- temperature -----
  const tempM = text.match(/(-?\d+(?:\.\d+)?)\s*°?\s*(c|celsius|centigrade|f|fahrenheit)\b/);
  if (tempM){
    const val = parseFloat(tempM[1]);
    const from = tempM[2][0]; // c or f
    let c, f;
    if (from === 'c'){ c = val; f = val * 9/5 + 32; }
    else { f = val; c = (val - 32) * 5/9; }
    // nearest gas mark
    let gm = null, gd = 999;
    for (const k in GAS){ const d = Math.abs(GAS[k] - c); if (d < gd){ gd = d; gm = k; } }
    const gmStr = gd <= 8 ? ` = Gas mark ${gm}` : '';
    return `${pretty2(c)}°C = ${pretty2(f)}°F${gmStr} ♨️`;
  }

  // ----- measurement conversion -----
  const num = parseNum(text);
  if (num != null){
    // split into source / target on to|in|into|=
    const parts = text.split(/\s+(?:to|in|into|=|->|→)\s+/);
    const left = parts[0];
    const right = parts[1] || '';
    const su = findUnit(left.replace(/^[^a-z]*\d[\d.\/¼½¾⅓⅔ ]*/, ' '));
    const sk = unitKind(su);
    if (sk === 'vol' || sk === 'wt'){
      const ing = findIngredient(text);
      let tu = right ? findUnit(right) : null;
      let tk = unitKind(tu);

      // no explicit target → pick a friendly default
      if (!tk){
        if (sk === 'wt') { tu = 'oz'; tk = 'wt'; }       // grams → oz
        else { tu = 'cups'; tk = 'vol'; }                // ml → cups
        if (su === 'oz' || su === 'lb') { tu = 'g'; }
        if (su === 'tbsp' || su === 'tsp') { tu = 'ml'; }
        tk = unitKind(tu);
      }

      const dens = ing ? DENS[ing] : null;
      const ingTxt = ing ? ` of ${ing}` : '';
      let out;

      if (sk === tk){
        const baseMl = sk === 'vol' ? VOL[su] : WT[su];
        const tgt = sk === 'vol' ? VOL[tu] : WT[tu];
        out = num * baseMl / tgt;
        return `${trimNum(num)} ${prettyUnit(su, num)}${sk==='vol'&&!ing?'':ingTxt} ≈ ${fmtUnit(out, tu)} ${catEmoji(ing)}`;
      } else {
        // crossing vol <-> wt
        if (!dens){
          // assume water-like 1g/ml, but be honest
          const ml = sk === 'vol' ? num * VOL[su] : num * WT[su]; // grams≈ml
          let out2 = sk === 'vol' ? ml : ml; // 1:1
          const tgtUnit = tk === 'wt' ? tu : tu;
          const conv = tk === 'wt' ? out2 / WT[tu] : out2 / VOL[tu];
          return `${trimNum(num)} ${prettyUnit(su,num)} ≈ ${fmtUnit(conv, tu)} 💧 (assuming water, tell me the ingredient, like “${trimNum(num)} ${su} flour”, for a closer guess!)`;
        }
        if (sk === 'vol'){
          const ml = num * VOL[su];
          const grams = ml * dens / 240;
          const conv = tk === 'wt' ? grams / WT[tu] : grams; // tk is wt
          return `${trimNum(num)} ${prettyUnit(su,num)}${ingTxt} ≈ ${fmtUnit(conv, tu)} ${catEmoji(ing)}`;
        } else {
          const grams = num * WT[su];
          const ml = grams * 240 / dens;
          const conv = ml / VOL[tu];
          return `${trimNum(num)} ${prettyUnit(su,num)}${ingTxt} ≈ ${fmtUnit(conv, tu)} ${catEmoji(ing)}`;
        }
      }
    }
  }

  // fallback
  return "Hmm, I didn't catch a measurement there 🐱 Try things like “180°C in °F”, “200g flour to cups”, or “2 tbsp in ml”.";
}

function pretty2(n){ return (Math.round(n * 10) / 10).toString().replace(/\.0$/, ''); }
function trimNum(n){ return (Math.round(n * 100) / 100).toString(); }
function catEmoji(ing){
  if (!ing) return '🥄';
  if (ing.includes('flour')) return '🌾';
  if (ing.includes('sugar')) return '🍬';
  if (ing.includes('butter')) return '🧈';
  if (ing.includes('milk')) return '🥛';
  if (ing.includes('rice')) return '🍚';
  if (ing.includes('honey')) return '🍯';
  return '🥣';
}
function prettyUnit(u, n){
  const map = { tbsp:'tbsp', tsp:'tsp', cup:'cup', cups:'cup', g:'g', kg:'kg', ml:'ml', l:'L', oz:'oz', lb:'lb' };
  let label = map[u] || u;
  if ((u === 'cup' || u === 'cups')) label = (Math.abs(n-1) < 0.001 ? 'cup' : 'cups');
  return label;
}
function fmtUnit(v, u){
  if (u === 'g' || u === 'ml') return Math.round(v) + ' ' + u;
  if (u === 'kg' || u === 'l') return (Math.round(v*100)/100) + ' ' + (u==='l'?'L':'kg');
  if (u === 'oz' || u === 'lb') return (Math.round(v*10)/10) + ' ' + u;
  if (u === 'cup' || u === 'cups') return pretty(v) + ' ' + (Math.abs(v-1)<0.04?'cup':'cups');
  if (u === 'tbsp' || u === 'tsp') return pretty(v) + ' ' + u;
  if (u === 'fl oz' || u === 'floz') return (Math.round(v*10)/10) + ' fl oz';
  return pretty(v) + ' ' + u;
}

/* ---------------- Mascot React component ---------------- */
function Mascot(){
  const [open, setOpen] = React.useState(false);
  const [teaser, setTeaser] = React.useState(true);
  const [typing, setTyping] = React.useState(false);
  const [input, setInput] = React.useState('');
  const [msgs, setMsgs] = React.useState([
    { from:'nedi', text:"Mrrp! 🐾 I'm Nedi. Ask me to convert a temperature or measurement, like “180°C in °F” or “200g flour to cups”." }
  ]);
  const bodyRef = React.useRef(null);
  const chips = ['180°C in °F', '200g flour in cups', '1 cup butter in g', 'Gas mark 6'];

  React.useEffect(() => {
    if (bodyRef.current) bodyRef.current.scrollTop = bodyRef.current.scrollHeight;
  }, [msgs, typing, open]);

  React.useEffect(() => {
    const t = setTimeout(() => setTeaser(false), 6500);
    return () => clearTimeout(t);
  }, []);

  function send(text){
    const q = (text != null ? text : input).trim();
    if (!q) return;
    setMsgs(m => [...m, { from:'me', text:q }]);
    setInput('');
    setTyping(true);
    setTimeout(() => {
      const r = rouxReply(q) || "I'm best with conversions! 🐱";
      setTyping(false);
      setMsgs(m => [...m, { from:'nedi', text:r }]);
    }, 380);
  }

  return (
    <div className="nedi-root">
      {open && (
        <div className="chatpanel fadein">
          <div className="chathead">
            <span className="ch-cat"><MascotCat size={40} mood="idle" /></span>
            <div className="ch-id">
              <b>Nedi</b>
              <small>kitchen helper · converts anything</small>
            </div>
            <button className="ch-close" onClick={() => setOpen(false)} aria-label="close chat">✕</button>
          </div>
          <div className="chatbody" ref={bodyRef}>
            {msgs.map((m, i) => (
              <div key={i} className={'bubble ' + (m.from === 'me' ? 'me' : 'nedi')}>
                {m.text.split('\n').map((line, j) => <div key={j}>{line}</div>)}
              </div>
            ))}
            {typing && <div className="bubble nedi typing"><span></span><span></span><span></span></div>}
          </div>
          <div className="chips">
            {chips.map(c => <button key={c} className="chipbtn" onClick={() => send(c)}>{c}</button>)}
          </div>
          <div className="chatinput">
            <input value={input} onChange={e => setInput(e.target.value)}
              onKeyDown={e => { if (e.key === 'Enter') send(); }}
              placeholder="Ask Nedi to convert…" />
            <button className="sendbtn" onClick={() => send()} aria-label="send">➤</button>
          </div>
        </div>
      )}

      {!open && teaser && (
        <div className="teaser fadein" onClick={() => { setOpen(true); setTeaser(false); }}>
          psst, need a conversion? ask me! 🐾
        </div>
      )}

      <button className={'nedi-fab' + (open ? ' active' : '')} onClick={() => { setOpen(o => !o); setTeaser(false); }} aria-label="Chat with Nedi">
        <MascotCat size={56} mood={open ? 'idle' : 'happy'} />
      </button>
    </div>
  );
}

Object.assign(window, { Mascot, rouxReply });
