/* Shared constants + helpers (extracted from source profit-calc.html) */
const {useState,useMemo,useCallback,useEffect,useRef}=React;

const GRAMS_PER_LB=453.592;
const COA_LABEL=0.10;

const TABS=[
  {id:'flower',label:'flower',free:true},
  {id:'prerolls',label:'prerolls',free:true},
  {id:'vapes',label:'vapes',free:true},
  {id:'hash',label:'extracts',free:true},
  {id:'toll',label:'toll',free:true},
  {id:'distro',label:'distro',free:true},
  {id:'forecast',label:'forecast',free:true},
  {id:'insights',label:'ai',free:true}
];

const FLOWER_GRADES=[
  {id:'exotics',label:'exotics',defaultPerLb:1400},
  {id:'indoor',label:'indoor',defaultPerLb:900},
  {id:'deps',label:'deps',defaultPerLb:400},
  {id:'outdoor',label:'outdoor',defaultPerLb:250},
  {id:'smalls',label:'smalls',defaultPerLb:500},
  {id:'trim',label:'trim',defaultPerLb:35}
];
const PKG_SIZES=[
  {id:'eighth',label:'1/8 oz (3.5g)',short:'3.5g',grams:3.5},
  {id:'quarter',label:'1/4 oz (7g)',short:'7g',grams:7},
  {id:'half',label:'1/2 oz (14g)',short:'14g',grams:14},
  {id:'oz',label:'1 oz (28g)',short:'28g',grams:28}
];
const PKG_TYPES=[
  {id:'glass',label:'Glass Jar',short:'jar',costs:{eighth:1.00,quarter:1.50,half:2.00,oz:3.00}},
  {id:'mylar',label:'Mylar Bag',short:'mylar',costs:{eighth:0.35,quarter:0.50,half:0.75,oz:1.00}},
  {id:'miron',label:'Miron Jar',short:'miron',costs:{eighth:3.50,quarter:5.00,half:7.00,oz:10.00}}
];
const CONSUMER_RETAIL_DEFAULTS={
  exotics:{eighth:65,quarter:120,half:220,oz:400},
  indoor:{eighth:45,quarter:80,half:150,oz:280},
  deps:{eighth:30,quarter:55,half:100,oz:180},
  outdoor:{eighth:20,quarter:35,half:60,oz:110},
  smalls:{eighth:28,quarter:50,half:90,oz:160},
  trim:{eighth:12,quarter:20,half:35,oz:60}
};
const WS_LIST_DEFAULTS={
  exotics:{eighth:36,quarter:65,half:121,oz:220},
  indoor:{eighth:20,quarter:36,half:67,oz:126},
  deps:{eighth:13,quarter:24,half:45,oz:81},
  outdoor:{eighth:9,quarter:16,half:27,oz:49},
  smalls:{eighth:12,quarter:22,half:40,oz:72},
  trim:{eighth:5,quarter:9,half:16,oz:27}
};
const WS_FLOOR_DEFAULTS={
  exotics:{eighth:29,quarter:52,half:97,oz:176},
  indoor:{eighth:16,quarter:29,half:54,oz:101},
  deps:{eighth:10,quarter:19,half:36,oz:65},
  outdoor:{eighth:7,quarter:13,half:22,oz:39},
  smalls:{eighth:10,quarter:18,half:32,oz:58},
  trim:{eighth:4,quarter:7,half:13,oz:22}
};

const fmt=v=>{if(v===undefined||v===null||isNaN(v)||!isFinite(v))return'$0.00';const n=Number(v);return(n<0?'-$':'$')+Math.abs(n).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g,',');};
const fmtK=v=>{if(v===undefined||v===null||isNaN(v)||!isFinite(v))return'$0';if(v===0)return'$0';const sign=v<0?'-':'';const a=Math.abs(v);if(a>=1e6)return sign+'$'+(a/1e6).toFixed(2)+'M';if(a>=1e3)return sign+'$'+(a/1e3).toFixed(1)+'K';return sign+'$'+a.toFixed(2);};
const uid=()=>Math.random().toString(36).slice(2,9);
const clamp=(v,min,max)=>Math.min(max,Math.max(min,Number.isFinite(v)?v:min));
const nonNegative=v=>Math.max(0,Number.isFinite(v)?v:0);
const positiveOr=v=>Math.max(0.0001,Number.isFinite(v)?v:0.0001);
const pct=v=>clamp(v,0,100);

const EXTRACT_TYPES={
  solventless:[
    {id:'rosin',label:'Hash Rosin',desc:'Pressed from bubble hash. Premium solventless concentrate.',defCostT1:15,defCostT2:10,defWsT1:32,defWsT2:22,defFloorT1:26,defFloorT2:18},
    {id:'bubble_wpff',label:'WPFF Bubble Hash',desc:'Whole Plant Fresh Frozen bubble hash — ice water extraction from fresh frozen material. Peak terpene preservation.',defCostT1:10,defCostT2:6,defWsT1:22,defWsT2:14,defFloorT1:18,defFloorT2:10},
    {id:'bubble_cured',label:'Cured Bubble Hash',desc:'Ice water extraction from cured/dried flower. Traditional method, lower cost input.',defCostT1:6,defCostT2:4,defWsT1:14,defWsT2:9,defFloorT1:11,defFloorT2:7},
    {id:'drysift',label:'Dry Sift',desc:'Mechanically separated trichomes via screens. Traditional method.',defCostT1:8,defCostT2:5,defWsT1:18,defWsT2:12,defFloorT1:14,defFloorT2:9},
    {id:'static',label:'Static Sift',desc:'Static electricity separation — ultra-clean trichome heads.',defCostT1:12,defCostT2:8,defWsT1:26,defWsT2:18,defFloorT1:22,defFloorT2:14}
  ],
  solvent:[
    {id:'liveresin',label:'Live Resin',desc:'BHO/PHO extraction from fresh frozen. Preserves full terpene profile.',defCostT1:6,defCostT2:4,defWsT1:18,defWsT2:12,defFloorT1:14,defFloorT2:9},
    {id:'curedresin',label:'Cured Resin',desc:'Hydrocarbon extraction from cured flower. Classic BHO shatter, budder, badder.',defCostT1:4,defCostT2:2.50,defWsT1:12,defWsT2:8,defFloorT1:9,defFloorT2:6},
    {id:'distillate',label:'Distillate',desc:'Refined, high-potency oil (90%+ THC). Versatile — dabs, edibles, vapes.',defCostT1:3,defCostT2:1.50,defWsT1:8,defWsT2:5,defFloorT1:6,defFloorT2:4},
    {id:'diamonds',label:'Diamonds / THCa',desc:'THCa crystalline isolated from live resin or sauce. Ultra-pure.',defCostT1:8,defCostT2:5,defWsT1:20,defWsT2:14,defFloorT1:16,defFloorT2:10},
    {id:'sauce',label:'Sauce / Terp Sauce',desc:'High-terpene extract with diamond crystals. Flavorful and potent.',defCostT1:6,defCostT2:4,defWsT1:16,defWsT2:10,defFloorT1:12,defFloorT2:8},
    {id:'crumble',label:'Crumble / Wax',desc:'Whipped BHO with dry, crumbly texture. Popular budget concentrate.',defCostT1:3,defCostT2:2,defWsT1:10,defWsT2:7,defFloorT1:8,defFloorT2:5}
  ]
};

const VAPE_HARDWARE=[
  {id:'ccell_th2_05',name:'CCELL TH2 0.5ml (510)',type:'510',ml:0.5,p1k:2.80,p5k:2.20,p10k:1.90},
  {id:'ccell_th2_10',name:'CCELL TH2 1.0ml (510)',type:'510',ml:1.0,p1k:3.20,p5k:2.60,p10k:2.20},
  {id:'avd_ep_05',name:'AVD Eazy-Press 0.5ml (510)',type:'510',ml:0.5,p1k:2.50,p5k:2.00,p10k:1.70},
  {id:'avd_ep_10',name:'AVD Eazy-Press 1.0ml (510)',type:'510',ml:1.0,p1k:2.90,p5k:2.30,p10k:1.95},
  {id:'jupiter_05',name:'Jupiter L2 0.5ml (510)',type:'510',ml:0.5,p1k:2.60,p5k:2.10,p10k:1.80},
  {id:'jupiter_10',name:'Jupiter L2 1.0ml (510)',type:'510',ml:1.0,p1k:3.10,p5k:2.50,p10k:2.10},
  {id:'ccell_dart_10',name:'CCELL Dart X 1.0ml (Disp)',type:'disposable',ml:1.0,p1k:4.50,p5k:3.80,p10k:3.20},
  {id:'ccell_dart_20',name:'CCELL Dart X 2.0ml (Disp)',type:'disposable',ml:2.0,p1k:5.20,p5k:4.40,p10k:3.80},
  {id:'ispire_10',name:'Ispire Ducore 1.0ml (Disp)',type:'disposable',ml:1.0,p1k:5.00,p5k:4.20,p10k:3.60},
  {id:'ispire_20',name:'Ispire Ducore 2.0ml (Disp)',type:'disposable',ml:2.0,p1k:5.80,p5k:4.90,p10k:4.20},
  {id:'blinker_20',name:'Blinker Bar 2.0ml (Disp)',type:'disposable',ml:2.0,p1k:4.00,p5k:3.20,p10k:2.70},
  {id:'generic_10',name:'Generic Disposable 1.0ml',type:'disposable',ml:1.0,p1k:3.50,p5k:2.80,p10k:2.30},
  {id:'generic_20',name:'Generic Disposable 2.0ml',type:'disposable',ml:2.0,p1k:4.20,p5k:3.40,p10k:2.80},
  {id:'o2vape_05',name:'O2 Vape Flip 0.5ml (510)',type:'510',ml:0.5,p1k:3.00,p5k:2.40,p10k:2.00},
  {id:'o2vape_10',name:'O2 Vape Flip 1.0ml (510)',type:'510',ml:1.0,p1k:3.50,p5k:2.80,p10k:2.40},
  {id:'o2vape_disp',name:'O2 Vape Disposable 1.0ml',type:'disposable',ml:1.0,p1k:4.80,p5k:4.00,p10k:3.40},
  {id:'greentank_025',name:'GreenTank S1 0.25ml (Rosin)',type:'510-rosin',ml:0.25,p1k:3.20,p5k:2.60,p10k:2.20},
  {id:'greentank_05',name:'GreenTank S1 0.5ml (Rosin)',type:'510-rosin',ml:0.5,p1k:3.50,p5k:2.90,p10k:2.50},
  {id:'greentank_10',name:'GreenTank S1 1.0ml (Rosin)',type:'510-rosin',ml:1.0,p1k:4.00,p5k:3.30,p10k:2.80},
  {id:'ccell_rosin_025',name:'CCELL Rosin Cart 0.25ml (510)',type:'510-rosin',ml:0.25,p1k:3.50,p5k:2.80,p10k:2.40},
  {id:'ccell_rosin_05',name:'CCELL Rosin Cart 0.5ml (510)',type:'510-rosin',ml:0.5,p1k:3.80,p5k:3.10,p10k:2.60},
  {id:'ccell_rosin_10',name:'CCELL Rosin Cart 1.0ml (510)',type:'510-rosin',ml:1.0,p1k:4.30,p5k:3.50,p10k:3.00},
  {id:'hamilton_rosin_05',name:'Hamilton Devices Rosin 0.5ml (Disp)',type:'disposable-rosin',ml:0.5,p1k:4.80,p5k:4.00,p10k:3.40},
  {id:'hamilton_rosin',name:'Hamilton Devices Rosin 1.0ml (Disp)',type:'disposable-rosin',ml:1.0,p1k:5.50,p5k:4.60,p10k:3.90},
  {id:'custom',name:'Custom Hardware',type:'custom',ml:1.0,p1k:3.00,p5k:3.00,p10k:3.00}
];

/* margin bands for flower/indoor (per spec) */
function bandForMargin(pct){
  if(pct>=45)return{label:'premium',cls:'premium',color:'var(--accent)',idx:4};
  if(pct>=30)return{label:'strong',cls:'strong',color:'var(--accent)',idx:3};
  if(pct>=20)return{label:'average',cls:'average',color:'var(--warn)',idx:2};
  if(pct>=10)return{label:'thin',cls:'thin',color:'var(--warn)',idx:1};
  return{label:'unviable',cls:'low',color:'var(--neg)',idx:0};
}

/* ========= Primitives ========= */
function Tip({text}){
  if(!text)return null;
  return(<span className="tip" tabIndex={0}><span className="tip-dot">?</span><span className="tip-body">{text}</span></span>);
}

function Field({label,value,onChange,type='number',step,min,max,prefix,suffix,options,hint,tip,mono=true,disabled,highlight}){
  const [editing,setEditing]=useState(false);
  const [draft,setDraft]=useState('');
  const labelEl=(<div className="fld-label">
    <span>{label}</span>
    {tip&&<Tip text={tip}/>}
  </div>);
  if(options){
    return(<label className={`fld${highlight?' key':''}`}>
      {labelEl}
      <div className="fld-input sel">
        <select value={value} onChange={e=>onChange(e.target.value)} disabled={disabled}>
          {options.map(o=><option key={o.value} value={o.value}>{o.label}</option>)}
        </select>
      </div>
      {hint&&<div className="fld-hint">{hint}</div>}
    </label>);
  }
  const displayVal=editing?draft:value;
  const isNum=type==='number';
  return(<label className={`fld${highlight?' key':''}`}>
    {labelEl}
    <div className={`fld-input${isNum&&mono?' mono':''}`}>
      {prefix&&<span className="prefix">{prefix}</span>}
      <input type={isNum?'text':type} inputMode={isNum?'decimal':undefined} value={displayVal}
        onFocus={()=>{if(isNum){setEditing(true);setDraft(String(value));}}}
        onChange={e=>{if(isNum){const v=e.target.value;setDraft(v);const n=parseFloat(v);if(!isNaN(n))onChange(n);}else onChange(e.target.value);}}
        onBlur={()=>{if(isNum){setEditing(false);const n=parseFloat(draft);onChange(isNaN(n)?0:n);}}}
        step={step||'any'} min={min} max={max} disabled={disabled} />
      {suffix&&<span className="suffix">{suffix}</span>}
    </div>
    {hint&&<div className="fld-hint">{hint}</div>}
  </label>);
}

function Stat({label,value,sub,tone}){
  return(<div className="stat">
    <div className="stat-label">{label}</div>
    <div className={`stat-val ${tone||''}`}>{value}</div>
    {sub&&<div className="stat-sub">{sub}</div>}
  </div>);
}

function SecHead({idx,title,done,active,note}){
  const glyph=done?'[✓]':active?'[→]':'[ ]';
  const cls=done?'done':active?'active':'';
  return(<header className="sec-head">
    <span className={`sec-glyph ${cls}`}>§{String(idx).padStart(2,'0')} {glyph}</span>
    <span className="sec-title">{title}</span>
    <span className="sec-rule"></span>
    {note&&<span className="fld-hint" style={{marginLeft:8}}>{note}</span>}
  </header>);
}

function SubHead({mark,title,note}){
  return(<div className="sub-head">
    <span className="mark">{mark||'//'}</span>
    <h4>{title}</h4>
    {note&&<span className="note">{note}</span>}
  </div>);
}

function Seg({options,value,onChange,size}){
  return(<div className="seg" style={size==='sm'?{fontSize:10}:{}}>
    {options.map(o=><button key={o.value} className={`seg-opt ${value===o.value?'active':''}`} onClick={()=>onChange(o.value)}>{o.label}</button>)}
  </div>);
}

function BenchBar({name,pct,scale=[0,10,20,30,45,60]}){
  const b=bandForMargin(pct);
  const capped=Math.max(0,Math.min(60,pct));
  const tickPct=(capped/60)*100;
  return(<div className="bench">
    <div className="bench-head">
      <div>
        <div className="bench-name">{name}</div>
        <div className="bench-value" style={{color:b.color}}>{pct.toFixed(1)}%</div>
      </div>
      <span className="bench-tag" style={{color:b.color}}>[{b.label.toUpperCase()}]</span>
    </div>
    <div className="bench-track">
      <div className="bench-seg s1"></div>
      <div className="bench-seg s2"></div>
      <div className="bench-seg s3"></div>
      <div className="bench-seg s4"></div>
      <div className="bench-seg s5"></div>
      <div className="bench-tick" style={{left:`calc(${tickPct}% - 1px)`}}></div>
    </div>
    <div className="bench-scale">
      {scale.map((s,i)=><span key={i}>{s}{i===scale.length-1?'+':''}</span>)}
    </div>
  </div>);
}

function Waterfall({caption,lines,total,totalLabel}){
  return(<div className="waterfall">
    {caption&&<div className="wf-caption">{caption}</div>}
    {lines.map((l,i)=><div key={i} className={`wf-row ${l.tone||''}`}>
      <span className="wf-label">{l.label}</span>
      <span className="wf-val">{l.sign||''}{l.value}</span>
    </div>)}
    <div className="wf-rule"></div>
    <div className="wf-row total">
      <span className="wf-label">{totalLabel||'net'}</span>
      <span className="wf-val">{total}</span>
    </div>
  </div>);
}

function Section({idx,title,done,active,note,stepId,children}){
  return(<section className="sec" data-step={stepId||''}>
    <SecHead idx={idx} title={title} done={done} active={active} note={note}/>
    <div className="sec-body">{children}</div>
  </section>);
}

/* export to window so per-file babel scripts can see these */
Object.assign(window,{
  GRAMS_PER_LB,COA_LABEL,TABS,FLOWER_GRADES,PKG_SIZES,PKG_TYPES,
  CONSUMER_RETAIL_DEFAULTS,WS_LIST_DEFAULTS,WS_FLOOR_DEFAULTS,
  EXTRACT_TYPES,VAPE_HARDWARE,
  fmt,fmtK,uid,clamp,nonNegative,positiveOr,pct,bandForMargin,
  Tip,Field,Stat,SecHead,SubHead,Seg,BenchBar,Waterfall,Section
});
