/* Primitive UI components */

const { useState, useEffect, useRef, useMemo, useCallback } = React;

/* Logo mark (inline SVG placeholder until user provides file) */
function LogoMark({ size=34 }){
  return (
    <svg width={size*0.7} height={size*0.7} viewBox="0 0 40 40" fill="none">
      <defs>
        <linearGradient id="lg-g" x1="0" y1="0" x2="40" y2="40">
          <stop offset="0" stopColor="#9b5cff"/>
          <stop offset="1" stopColor="#22d3ee"/>
        </linearGradient>
      </defs>
      {/* waveform bars */}
      <rect x="3" y="17" width="2.5" height="6" rx="1" fill="url(#lg-g)"/>
      <rect x="7" y="13" width="2.5" height="14" rx="1" fill="url(#lg-g)"/>
      <rect x="11" y="9" width="2.5" height="22" rx="1" fill="url(#lg-g)"/>
      <rect x="15" y="15" width="2.5" height="10" rx="1" fill="url(#lg-g)"/>
      {/* B */}
      <path d="M22 9 L22 31 L30 31 Q35 31 35 26 Q35 22 31 21 Q34 20 34 16 Q34 11 29 11 Z
               M26 14 L29 14 Q31 14 31 16 Q31 18 29 18 L26 18 Z
               M26 22 L29 22 Q32 22 32 25 Q32 28 29 28 L26 28 Z"
            fill="#e7e8ee"/>
    </svg>
  );
}

/* Waveform bars (deterministic per-seed) */
function WaveBars({ count=40, height=40, seed=1, color }){
  const bars = useMemo(()=>{
    const out = [];
    let s = seed * 9301 + 49297;
    for(let i=0;i<count;i++){
      s = (s * 9301 + 49297) % 233280;
      const n = s / 233280;
      // envelope: quick attack, decay
      const t = i/count;
      const env = Math.pow(1 - t, 0.6) * (0.3 + n*0.7);
      const h = Math.max(2, env * height);
      out.push(h);
    }
    return out;
  }, [count, height, seed]);
  return (
    <>
      {bars.map((h, i)=>(
        <div key={i} className="bar" style={{height: h+'px', background: color||undefined}}/>
      ))}
    </>
  );
}

/* Circular knob (display + drag-to-change) */
function Knob({ value, min=0, max=100, unit='', label, onChange, size='normal' }){
  const ref = useRef(null);
  const startY = useRef(0); const startV = useRef(0);
  const pct = (value - min) / (max - min);
  const deg = 270 * Math.max(0, Math.min(1, pct));
  const rot = -135 + deg;

  const onDown = (e)=>{
    e.preventDefault();
    startY.current = (e.touches ? e.touches[0].clientY : e.clientY);
    startV.current = value;
    const move = (ev)=>{
      const y = ev.touches ? ev.touches[0].clientY : ev.clientY;
      const dy = startY.current - y;
      const range = max - min;
      const nv = Math.max(min, Math.min(max, startV.current + (dy/120)*range));
      onChange && onChange(nv);
    };
    const up = ()=>{
      window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up);
      window.removeEventListener('touchmove', move); window.removeEventListener('touchend', up);
    };
    window.addEventListener('mousemove', move); window.addEventListener('mouseup', up);
    window.addEventListener('touchmove', move); window.addEventListener('touchend', up);
  };

  const display = typeof value === 'number' ?
    (Math.abs(value) >= 100 ? value.toFixed(0) : (Math.abs(value)>=10 ? value.toFixed(1) : value.toFixed(2))) : value;

  return (
    <div className={"knob-cell "+(size==='small'?'small':'')}>
      <div className="knob-ring" ref={ref}
           onMouseDown={onDown} onTouchStart={onDown}
           style={{'--knob-deg': deg+'deg', '--knob-rot': rot+'deg'}}>
        <div className="tick-line"/>
      </div>
      {label && <div className="lbl">{label}</div>}
      <div className="val">{display}{unit}</div>
    </div>
  );
}

/* Fader display (vertical) with dragging */
function Fader({ db=0, onChange, selected }){
  // map db -10..+10 -> 0..1 ; below -10 compresses
  const ref = useRef(null);
  const valueToPct = (v)=>{
    // standard console: 0dB at ~75%; -inf at bottom
    if(v <= -60) return 0;
    if(v >= 10) return 1;
    // simple piecewise
    if(v >= 0) return 0.72 + (v/10)*0.28;
    if(v >= -10) return 0.5 + ((v+10)/10)*0.22;
    if(v >= -30) return 0.2 + ((v+30)/20)*0.3;
    return 0 + ((v+60)/30)*0.2;
  };
  const pctToValue = (p)=>{
    if(p >= 0.72) return ((p-0.72)/0.28)*10;
    if(p >= 0.5) return -10 + ((p-0.5)/0.22)*10;
    if(p >= 0.2) return -30 + ((p-0.2)/0.3)*20;
    return -60 + (p/0.2)*30;
  };
  const pct = valueToPct(db);
  const bottom = (pct*100) + '%';

  const onDown = (e)=>{
    e.preventDefault();
    const rect = ref.current.getBoundingClientRect();
    const handle = (ev)=>{
      const y = ev.touches ? ev.touches[0].clientY : ev.clientY;
      const rel = 1 - Math.max(0, Math.min(1, (y - rect.top) / rect.height));
      onChange && onChange(pctToValue(rel));
    };
    handle(e);
    const move = handle;
    const up = ()=>{
      window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up);
      window.removeEventListener('touchmove', move); window.removeEventListener('touchend', up);
    };
    window.addEventListener('mousemove', move); window.addEventListener('mouseup', up);
    window.addEventListener('touchmove', move, {passive:false}); window.addEventListener('touchend', up);
  };

  // build tick labels
  const tickVals = [10, 5, 0, -5, -10, -20, -40];
  const ticks = tickVals.map(v=>({v, y: (1-valueToPct(v))*100}));

  return (
    <div className={"fader "+(selected?'is-selected':'')} ref={ref} onMouseDown={onDown} onTouchStart={onDown}>
      <div className="scale"/>
      {ticks.map(t=>(
        <React.Fragment key={t.v}>
          <div className={"tick "+(t.v===0?'unity-tick':'')} style={{top:t.y+'%'}}/>
          <div className="tick-lbl" style={{top:t.y+'%'}}>{t.v>0?'+'+t.v:t.v}</div>
        </React.Fragment>
      ))}
      <div className="knob" style={{bottom}}/>
    </div>
  );
}

/* Meter — rendered once by React; height is mutated directly via DOM
 * inside the parent's poll loop (data-meter-* selectors). Bypassing React
 * here is critical on Pi 5 WebKit: with 16 meters re-rendering at >5 Hz
 * via setState, the entire MixerScreen subtree re-rendered (8 strips ×
 * Knobs/Faders/EqDragCell), pinning a single core at 99 % CPU.
 *
 * `dataMeterCh` + `dataMeterSide` mark per-channel meters; `dataMeterMaster`
 * marks the master-bus pair. The poll effect in mixer.jsx finds these via
 * cached querySelector refs and updates style.height — zero React work.
 */
function Meter({ dataMeterCh, dataMeterSide, dataMeterMaster }){
  // Note: NO inline `height` here. The CSS rule for `.meter .fill` uses
  // `position:absolute; top:0; bottom:0` to take the full container height,
  // and the visible level is driven by `clip-path: inset(N% 0 0 0)`
  // mutated from mixer.jsx's poll. Setting `height: 0%` inline would
  // override `bottom:0` per CSS spec and collapse the .fill to 0px tall,
  // leaving the clip-path with nothing to reveal.
  return (
    <div className="meter">
      <div className="fill"
           data-meter-ch={dataMeterCh}
           data-meter-side={dataMeterSide}
           data-meter-master={dataMeterMaster}/>
    </div>
  );
}

Object.assign(window, { LogoMark, WaveBars, Knob, Fader, Meter });
