(() => {
  'use strict';

  // kir0Qual V2 browser preparation engine
  // Uses Chrome's native MediaRecorder MP4/H.264/AAC encoder, then converts
  // Chrome's fragmented MP4 into a classic fast-start MP4 so kir0Method can run.

  const te = new TextEncoder();
  const CONTAINERS = new Set(['moov','trak','mdia','minf','dinf','stbl','mvex','moof','traf','udta','meta','ilst']);

  const u32=(a,p)=>(((a[p]<<24)>>>0)|(a[p+1]<<16)|(a[p+2]<<8)|a[p+3])>>>0;
  const i32=(a,p)=>{const v=u32(a,p);return v>0x7fffffff?v-0x100000000:v;};
  const u64=(a,p)=>(BigInt(u32(a,p))<<32n)|BigInt(u32(a,p+4));
  const w32=n=>new Uint8Array([(Number(n)>>>24)&255,(Number(n)>>>16)&255,(Number(n)>>>8)&255,Number(n)&255]);
  const wi32=n=>w32(n>>>0);
  const w64=n=>{const x=BigInt(n);return new Uint8Array([
    Number((x>>56n)&255n),Number((x>>48n)&255n),Number((x>>40n)&255n),Number((x>>32n)&255n),
    Number((x>>24n)&255n),Number((x>>16n)&255n),Number((x>>8n)&255n),Number(x&255n)
  ]);};
  const cat=parts=>{let n=0;for(const p of parts)n+=p.length;const out=new Uint8Array(n);let o=0;for(const p of parts){out.set(p,o);o+=p.length;}return out;};
  const box=(type,payload)=>cat([w32(payload.length+8),te.encode(type),payload]);
  const typeAt=(a,p)=>String.fromCharCode(a[p+4],a[p+5],a[p+6],a[p+7]);

  function parseBoxes(a,start=0,end=a.length){
    const out=[];let p=start;
    while(p+8<=end){
      let size=u32(a,p),header=8;
      if(size===1){if(p+16>end)throw new Error('Truncated extended MP4 box');size=Number(u64(a,p+8));header=16;}
      else if(size===0) size=end-p;
      if(size<header||p+size>end) throw new Error('Invalid MP4 box boundary');
      const b={type:typeAt(a,p),start:p,end:p+size,size,header,children:null};
      let cs=p+header;if(b.type==='meta')cs+=4;
      if(CONTAINERS.has(b.type)&&cs<b.end)b.children=parseBoxes(a,cs,b.end);
      out.push(b);p=b.end;
    }
    return out;
  }
  const raw=(a,b)=>a.subarray(b.start,b.end);
  const payload=(a,b)=>a.subarray(b.start+b.header,b.end);
  const child=(b,t)=>b?.children?.find(x=>x.type===t)||null;
  function path(b,parts){let c=b;for(const t of parts){c=child(c,t);if(!c)return null;}return c;}

  function fullFlags(p){return u32(p,0)&0x00ffffff;}
  function fullVersion(p){return p[0]||0;}
  function getTrackId(a,trak){
    const tkhd=child(trak,'tkhd');if(!tkhd)throw new Error('Missing tkhd');
    const p=payload(a,tkhd),v=fullVersion(p);return u32(p,v===1?20:12);
  }
  function getMdhdTimescale(a,trak){
    const mdhd=path(trak,['mdia','mdhd']);if(!mdhd)throw new Error('Missing mdhd');
    const p=payload(a,mdhd),v=fullVersion(p);return u32(p,v===1?20:12);
  }
  function handler(a,trak){
    const h=path(trak,['mdia','hdlr']);if(!h)return 'unknown';const p=payload(a,h);
    return p.length>=12?String.fromCharCode(p[8],p[9],p[10],p[11]):'unknown';
  }
  function mvhdTimescale(a,mvhd){const p=payload(a,mvhd),v=fullVersion(p);return u32(p,v===1?20:12);}

  function patchDuration(a,b,duration){
    const p=new Uint8Array(payload(a,b));const v=fullVersion(p);
    let off;
    if(b.type==='tkhd') off=v===1?28:20;
    else if(b.type==='mvhd'||b.type==='mdhd') off=v===1?24:16;
    else return raw(a,b);
    if(v===1)p.set(w64(BigInt(Math.max(0,Math.round(duration)))),off);
    else p.set(w32(Math.max(0,Math.min(0xffffffff,Math.round(duration)))),off);
    return box(b.type,p);
  }

  function parseTrex(a,moov){
    const map=new Map();const mvex=child(moov,'mvex');
    for(const t of mvex?.children||[]){
      if(t.type!=='trex')continue;const p=payload(a,t);if(p.length<24)continue;
      map.set(u32(p,4),{desc:u32(p,8),duration:u32(p,12),size:u32(p,16),flags:u32(p,20)});
    }
    return map;
  }

  function parseTfhd(a,b){
    const p=payload(a,b),flags=fullFlags(p);let o=4;
    const out={flags,trackId:u32(p,o),baseDataOffset:null,desc:null,duration:null,size:null,sampleFlags:null};o+=4;
    if(flags&0x000001){out.baseDataOffset=u64(p,o);o+=8;}
    if(flags&0x000002){out.desc=u32(p,o);o+=4;}
    if(flags&0x000008){out.duration=u32(p,o);o+=4;}
    if(flags&0x000010){out.size=u32(p,o);o+=4;}
    if(flags&0x000020){out.sampleFlags=u32(p,o);o+=4;}
    return out;
  }
  function parseTfdt(a,b){if(!b)return 0n;const p=payload(a,b);return fullVersion(p)===1?u64(p,4):BigInt(u32(p,4));}
  function parseTrun(a,b,defaults,firstFlagsDefault){
    const p=payload(a,b),ver=fullVersion(p),flags=fullFlags(p);let o=4;
    const count=u32(p,o);o+=4;let dataOffset=null,firstSampleFlags=null;
    if(flags&0x000001){dataOffset=i32(p,o);o+=4;}
    if(flags&0x000004){firstSampleFlags=u32(p,o);o+=4;}
    const samples=[];
    for(let i=0;i<count;i++){
      let duration=defaults.duration||0,size=defaults.size||0,sflags=defaults.flags||firstFlagsDefault||0,cto=0;
      if(flags&0x000100){duration=u32(p,o);o+=4;}
      if(flags&0x000200){size=u32(p,o);o+=4;}
      if(flags&0x000400){sflags=u32(p,o);o+=4;}
      else if(i===0&&firstSampleFlags!==null)sflags=firstSampleFlags;
      if(flags&0x000800){cto=ver===1?i32(p,o):u32(p,o);o+=4;}
      samples.push({duration,size,flags:sflags,cto});
    }
    return {dataOffset,samples};
  }

  function rle(values){
    const out=[];for(const v of values){const last=out[out.length-1];if(last&&last.value===v)last.count++;else out.push({count:1,value:v});}return out;
  }
  function makeStts(samples){
    const runs=rle(samples.map(s=>s.duration));const p=new Uint8Array(8+runs.length*8);p.set(w32(runs.length),4);
    runs.forEach((r,i)=>{p.set(w32(r.count),8+i*8);p.set(w32(r.value),12+i*8);});return box('stts',p);
  }
  function makeCtts(samples){
    const vals=samples.map(s=>s.cto||0);if(!vals.some(v=>v!==0))return null;
    const signed=vals.some(v=>v<0),runs=rle(vals);const p=new Uint8Array(8+runs.length*8);p[0]=signed?1:0;p.set(w32(runs.length),4);
    runs.forEach((r,i)=>{p.set(w32(r.count),8+i*8);p.set(signed?wi32(r.value):w32(r.value),12+i*8);});return box('ctts',p);
  }
  function makeStsc(sampleCount){
    const p=new Uint8Array(20);p.set(w32(1),4);p.set(w32(1),8);p.set(w32(1),12);p.set(w32(1),16);return box('stsc',p);
  }
  function makeStsz(samples){
    const p=new Uint8Array(12+samples.length*4);p.set(w32(0),4);p.set(w32(samples.length),8);
    samples.forEach((s,i)=>p.set(w32(s.size),12+i*4));return box('stsz',p);
  }
  function makeStco(samples,base){
    const need64=samples.some(s=>BigInt(base)+BigInt(s.outputOffset)>0xffffffffn);
    const step=need64?8:4,p=new Uint8Array(8+samples.length*step);p.set(w32(samples.length),4);
    samples.forEach((s,i)=>{const v=BigInt(base)+BigInt(s.outputOffset);p.set(need64?w64(v):w32(Number(v)),8+i*step);});return box(need64?'co64':'stco',p);
  }
  function makeStss(samples){
    const sync=[];samples.forEach((s,i)=>{if((s.flags&0x00010000)===0)sync.push(i+1);});
    if(!sync.length||sync.length===samples.length)return null;
    const p=new Uint8Array(8+sync.length*4);p.set(w32(sync.length),4);sync.forEach((n,i)=>p.set(w32(n),8+i*4));return box('stss',p);
  }

  function rebuildClassicMp4(a){
    const top=parseBoxes(a);const ftyp=top.find(b=>b.type==='ftyp');const moov=top.find(b=>b.type==='moov');
    const moofs=top.filter(b=>b.type==='moof');
    if(!moov||!moofs.length) return a; // already classic MP4

    const mvhd=child(moov,'mvhd');if(!mvhd)throw new Error('Recorder MP4 missing mvhd');
    const movieScale=mvhdTimescale(a,mvhd);const trex=parseTrex(a,moov);
    const trackBoxes=(moov.children||[]).filter(b=>b.type==='trak');
    const tracks=new Map();
    for(const trak of trackBoxes){
      const id=getTrackId(a,trak),timescale=getMdhdTimescale(a,trak);
      tracks.set(id,{id,trak,timescale,kind:handler(a,trak),samples:[]});
    }

    const global=[];
    for(const moof of moofs){
      for(const traf of moof.children||[]){
        if(traf.type!=='traf')continue;
        const tfhdBox=child(traf,'tfhd'),tfdtBox=child(traf,'tfdt');if(!tfhdBox)continue;
        const tfhd=parseTfhd(a,tfhdBox),track=tracks.get(tfhd.trackId);if(!track)continue;
        const tx=trex.get(tfhd.trackId)||{};
        const defaults={duration:tfhd.duration??tx.duration??0,size:tfhd.size??tx.size??0,flags:tfhd.sampleFlags??tx.flags??0};
        let decodeTime=parseTfdt(a,tfdtBox);
        let implicitCursor=null;
        const base=tfhd.baseDataOffset!==null?Number(tfhd.baseDataOffset):moof.start;
        for(const trunBox of (traf.children||[]).filter(x=>x.type==='trun')){
          const trun=parseTrun(a,trunBox,defaults,defaults.flags);
          let cursor=trun.dataOffset!==null?base+trun.dataOffset:(implicitCursor??moof.end+8);
          for(const s0 of trun.samples){
            if(!s0.size)throw new Error('Recorder fragment has a sample with unknown size');
            if(cursor<0||cursor+s0.size>a.length)throw new Error('Recorder fragment points outside MP4 data');
            const s={...s0,decodeTime,sourceOffset:cursor,bytes:a.subarray(cursor,cursor+s0.size),trackId:track.id,outputOffset:0};
            track.samples.push(s);global.push(s);cursor+=s0.size;decodeTime+=BigInt(s0.duration||0);
          }
          implicitCursor=cursor;
        }
      }
    }
    if(!global.length)throw new Error('Recorder produced no MP4 samples');
    global.sort((x,y)=>x.sourceOffset-y.sourceOffset);
    let outOff=0;for(const s of global){s.outputOffset=outOff;outOff+=s.bytes.length;}
    const mdatPayload=cat(global.map(s=>s.bytes));

    function rebuildStbl(track,base){
      const oldStbl=path(track.trak,['mdia','minf','stbl']);if(!oldStbl)throw new Error('Recorder track missing stbl');
      const stsd=child(oldStbl,'stsd');if(!stsd)throw new Error('Recorder track missing stsd');
      const parts=[raw(a,stsd),makeStts(track.samples)];
      const ctts=makeCtts(track.samples);if(ctts)parts.push(ctts);
      if(track.kind==='vide'){const stss=makeStss(track.samples);if(stss)parts.push(stss);}
      parts.push(makeStsc(track.samples.length),makeStsz(track.samples),makeStco(track.samples,base));
      return box('stbl',cat(parts));
    }
    function rebuildTrack(track,base){
      const mdia=child(track.trak,'mdia'),tkhd=child(track.trak,'tkhd');if(!mdia||!tkhd)throw new Error('Recorder track structure incomplete');
      const mdhd=child(mdia,'mdhd'),hdlr=child(mdia,'hdlr'),minf=child(mdia,'minf');if(!mdhd||!hdlr||!minf)throw new Error('Recorder media structure incomplete');
      const duration=track.samples.reduce((n,s)=>n+BigInt(s.duration||0),0n);
      const movieDuration=Math.round(Number(duration)*movieScale/track.timescale);
      const minfParts=[];for(const c of minf.children||[]){minfParts.push(c.type==='stbl'?rebuildStbl(track,base):raw(a,c));}
      const newMinf=box('minf',cat(minfParts));
      const mdiaParts=[];for(const c of mdia.children||[]){
        if(c.type==='mdhd')mdiaParts.push(patchDuration(a,c,Number(duration)));
        else if(c.type==='minf')mdiaParts.push(newMinf);
        else mdiaParts.push(raw(a,c));
      }
      const newMdia=box('mdia',cat(mdiaParts));
      const trakParts=[];for(const c of track.trak.children||[]){
        if(c.type==='tkhd')trakParts.push(patchDuration(a,c,movieDuration));
        else if(c.type==='mdia')trakParts.push(newMdia);
        else if(c.type==='edts'){} // fresh recorder timeline does not need edit lists
        else trakParts.push(raw(a,c));
      }
      return {bytes:box('trak',cat(trakParts)),movieDuration};
    }

    const f=ftyp?raw(a,ftyp):new Uint8Array();
    function buildMoov(base){
      let maxMovieDuration=0;const rebuilt=[];
      for(const t of trackBoxes){const tr=tracks.get(getTrackId(a,t));const r=rebuildTrack(tr,base);rebuilt.push(r.bytes);maxMovieDuration=Math.max(maxMovieDuration,r.movieDuration);}
      const parts=[];
      for(const c of moov.children||[]){
        if(c.type==='mvhd')parts.push(patchDuration(a,c,maxMovieDuration));
        else if(c.type==='trak'){
          const idx=trackBoxes.indexOf(c);parts.push(rebuilt[idx]);
        } else if(c.type==='mvex'){} else parts.push(raw(a,c));
      }
      return box('moov',cat(parts));
    }
    const probe=buildMoov(0);const base=f.length+probe.length+8;const finalMoov=buildMoov(base);
    if(finalMoov.length!==probe.length)throw new Error('Classic MP4 rebuild changed size unexpectedly');
    return cat([f,finalMoov,box('mdat',mdatPayload)]);
  }

  async function waitEvent(target,name){return new Promise((resolve,reject)=>{const ok=e=>{clean();resolve(e);},bad=e=>{clean();reject(new Error(target.error?.message||`Media ${name} failed`));};const clean=()=>{target.removeEventListener(name,ok);target.removeEventListener('error',bad);};target.addEventListener(name,ok,{once:true});target.addEventListener('error',bad,{once:true});});}

  function parseSourceFps(bytes){
    try{
      const top=parseBoxes(bytes,0,bytes.length);
      const moov=top.find(b=>b.type==='moov');
      if(!moov)return 60;
      const traks=(moov.children||[]).filter(b=>b.type==='trak');
      for(const trak of traks){
        const mdia=(trak.children||[]).find(b=>b.type==='mdia');
        if(!mdia)continue;
        const hdlr=(mdia.children||[]).find(b=>b.type==='hdlr');
        if(!hdlr)continue;
        const hp=payload(bytes,hdlr);
        if(hp.length<12||fourcc(hp,8)!=='vide')continue;
        const mdhd=(mdia.children||[]).find(b=>b.type==='mdhd');
        const minf=(mdia.children||[]).find(b=>b.type==='minf');
        const stbl=minf&&(minf.children||[]).find(b=>b.type==='stbl');
        const stts=stbl&&(stbl.children||[]).find(b=>b.type==='stts');
        if(!mdhd||!stts)continue;
        const mp=payload(bytes,mdhd); const version=mp[0];
        const timescale=version===1?u32(mp,20):u32(mp,12);
        const sp=payload(bytes,stts); const n=u32(sp,4);
        let totalSamples=0,totalTicks=0;
        for(let i=0,o=8;i<n&&o+8<=sp.length;i++,o+=8){const c=u32(sp,o),d=u32(sp,o+4);totalSamples+=c;totalTicks+=c*d;}
        if(timescale>0&&totalTicks>0&&totalSamples>0){
          const fps=totalSamples*timescale/totalTicks;
          if(fps>10&&fps<241)return fps;
        }
      }
    }catch(_){ }
    return 60;
  }

  async function nativeReencode(file,{onProgress=()=>{},onLog=()=>{}}={}){
    if(!window.MediaRecorder)throw new Error('Chrome MediaRecorder is unavailable');
    const sourceBytes=new Uint8Array(await file.arrayBuffer());
    const detectedFps=parseSourceFps(sourceBytes);
    const targetFps=detectedFps>45?60:(detectedFps>27?30:Math.max(24,Math.round(detectedFps)));
    const candidates=[
      'video/mp4;codecs="avc1.64002a,mp4a.40.2"',
      'video/mp4;codecs="avc1.4d002a,mp4a.40.2"',
      'video/mp4;codecs="avc1.42E02A,mp4a.40.2"',
      'video/mp4;codecs="avc1,mp4a.40.2"',
      'video/mp4'
    ];
    const mime=candidates.find(x=>MediaRecorder.isTypeSupported(x));
    if(!mime)throw new Error('This Chrome build cannot encode MP4/H.264 in-browser');

    const url=URL.createObjectURL(file);const v=document.createElement('video');
    v.preload='auto';v.playsInline=true;v.controls=false;v.muted=true;v.volume=0;
    v.style.cssText='position:fixed;width:1px;height:1px;opacity:0;pointer-events:none;left:-10px;top:-10px';
    v.src=url;document.body.appendChild(v);
    let drawHandle=0;
    try{
      await waitEvent(v,'loadedmetadata');
      if(!Number.isFinite(v.duration)||v.duration<=0)throw new Error('Could not read source duration');
      const sourceCapture=v.captureStream?.bind(v)||v.webkitCaptureStream?.bind(v);
      if(!sourceCapture)throw new Error('Chrome media capture is unavailable');
      const srcStream=sourceCapture();
      const audioTrack=srcStream.getAudioTracks()[0];
      if(!audioTrack)throw new Error('kir0Method requires an AAC audio track');

      const canvas=document.createElement('canvas');
      canvas.width=Math.max(2,v.videoWidth||1920);canvas.height=Math.max(2,v.videoHeight||1080);
      canvas.style.cssText='position:fixed;width:1px;height:1px;opacity:0;pointer-events:none;left:-10px;top:-10px';
      document.body.appendChild(canvas);
      const ctx=canvas.getContext('2d',{alpha:false,desynchronized:true});
      if(!ctx)throw new Error('Canvas encoder surface unavailable');
      const canvasStream=canvas.captureStream(targetFps);
      const videoTrack=canvasStream.getVideoTracks()[0];
      if(!videoTrack)throw new Error('Could not create browser video track');
      const mixed=new MediaStream([videoTrack,audioTrack]);

      onLog(`Native Chrome encoder: ${mime}`);
      onLog(`Source ${detectedFps.toFixed(2)} FPS → recording target ${targetFps} FPS`);
      const chunks=[];
      let recorder;
      const opts={mimeType:mime,videoBitsPerSecond:22000000,audioBitsPerSecond:192000,videoKeyFrameIntervalDuration:1000};
      try{recorder=new MediaRecorder(mixed,opts);}catch(_){delete opts.videoKeyFrameIntervalDuration;recorder=new MediaRecorder(mixed,opts);}
      recorder.addEventListener('dataavailable',e=>{if(e.data&&e.data.size)chunks.push(e.data);});
      const stopped=new Promise((resolve,reject)=>{recorder.addEventListener('stop',resolve,{once:true});recorder.addEventListener('error',e=>reject(new Error(e.error?.message||'Browser encoder failed')),{once:true});});
      const tick=()=>{if(v.duration>0)onProgress(Math.min(0.99,v.currentTime/v.duration));};
      const draw=()=>{try{ctx.drawImage(v,0,0,canvas.width,canvas.height);}catch(_){} if('requestVideoFrameCallback' in v)drawHandle=v.requestVideoFrameCallback(draw);};
      if('requestVideoFrameCallback' in v)drawHandle=v.requestVideoFrameCallback(draw); else v.addEventListener('timeupdate',()=>ctx.drawImage(v,0,0,canvas.width,canvas.height));
      v.addEventListener('timeupdate',tick);
      v.addEventListener('ended',()=>{tick();if(recorder.state!=='inactive')recorder.stop();},{once:true});
      const encodeStarted=performance.now();
      recorder.start();
      await v.play();
      await stopped;
      v.removeEventListener('timeupdate',tick);onProgress(1);
      if(drawHandle&&'cancelVideoFrameCallback' in v)try{v.cancelVideoFrameCallback(drawHandle);}catch(_){ }
      videoTrack.stop();
      const recorded=new Uint8Array(await new Blob(chunks,{type:mime}).arrayBuffer());
      if(recorded.length<1024)throw new Error('Browser encoder returned an empty MP4');
      onLog(`Native encode complete: ${(recorded.length/1048576).toFixed(1)} MB in ${((performance.now()-encodeStarted)/1000).toFixed(1)}s`);
      const classic=rebuildClassicMp4(recorded);
      onLog(`Classic MP4 rebuild complete: ${(classic.length/1048576).toFixed(1)} MB`);
      canvas.remove();
      return classic;
    } finally {
      try{v.pause();}catch(_){} v.remove();URL.revokeObjectURL(url);
    }
  }

  window.KQBrowserPrep={reencode:nativeReencode,rebuildClassicMp4};
})();
