#!/usr/bin/env python3
"""Generate a single-file WebSerial flasher (flasher.html) with the firmware
embedded. Open in Chrome/Edge, one button flashes the RT-4D."""
import base64, os

# project root = parent of this script's directory (portable)
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
FW = f"{ROOT}/stock-fw/rt4d_ru_full.bin"
OUT = f"{ROOT}/dist/Ru-4D_flasher.html"

fw = open(FW, "rb").read()
b64 = base64.b64encode(fw).decode()

HTML = """<!doctype html><html lang="ru"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Ru-4D — Прошивка</title>
<style>
 :root{--a:#2b7;--b:#0a0e14;--c:#131a24;--t:#dfe7ef;--m:#7f8ea3}
 *{box-sizing:border-box}
 body{margin:0;background:var(--b);color:var(--t);font:15px/1.5 system-ui,Segoe UI,Roboto,sans-serif;
      min-height:100vh;display:flex;align-items:center;justify-content:center;padding:16px}
 .card{background:var(--c);border:1px solid #223;border-radius:16px;max-width:520px;width:100%;
       padding:26px 26px 22px;box-shadow:0 10px 40px #0008}
 h1{margin:0 0 2px;font-size:22px}
 .ver{color:var(--m);font-size:13px;margin-bottom:18px}
 ol{margin:0 0 18px;padding-left:20px;color:#c3cdd9}
 ol b{color:var(--t)}
 button{width:100%;border:0;border-radius:12px;background:var(--a);color:#04120b;font-weight:700;
        font-size:18px;padding:15px;cursor:pointer;transition:.15s}
 button:hover{filter:brightness(1.08)} button:disabled{opacity:.5;cursor:default}
 .bar{height:12px;background:#0c1219;border-radius:8px;overflow:hidden;margin:16px 0 8px;display:none}
 .bar>i{display:block;height:100%;width:0;background:var(--a);transition:.15s}
 #log{white-space:pre-wrap;font:12px/1.5 ui-monospace,Consolas,monospace;color:#9fb0c2;
      background:#0c1219;border-radius:10px;padding:10px 12px;margin-top:12px;max-height:170px;overflow:auto;display:none}
 .ok{color:#3d9} .err{color:#f77}
 .warn{background:#3a2410;border:1px solid #6a3;color:#fc9;border-radius:10px;padding:10px 12px;font-size:13px;margin-bottom:14px;display:none}
</style></head><body>
<div class="card">
 <h1>Ru-4D — Прошивка рации</h1>
 <div class="ver" id="ver"></div>
 <div class="warn" id="nowebserial">Откройте этот файл в <b>Google Chrome</b> или <b>Microsoft Edge</b> (нужен WebSerial).</div>
 <ol>
   <li><b>Выключите</b> рацию.</li>
   <li>Включите её, <b>удерживая тангенту PTT</b> (режим прошивки).</li>
   <li>Подключите рацию к ПК кабелем USB-C.</li>
   <li>Нажмите <b>«Прошить»</b> и выберите COM-порт рации.</li>
   <li><b>Не отключайте</b> кабель до конца записи.</li>
 </ol>
 <button id="go">Прошить</button>
 <div class="bar" id="bar"><i></i></div>
 <div id="log"></div>
</div>
<script>
const FW_B64="__B64__";
const FW_INFO="__INFO__";
document.getElementById('ver').textContent=FW_INFO;
const SEED=0x48, ACK=0x06;
const $=id=>document.getElementById(id);
const logEl=$('log'), barEl=$('bar'), barI=barEl.querySelector('i'), btn=$('go');
function log(m,c){logEl.style.display='block';const s=document.createElement('span');if(c)s.className=c;s.textContent=m+"\\n";logEl.appendChild(s);logEl.scrollTop=logEl.scrollHeight;}
function prog(f){barEl.style.display='block';barI.style.width=Math.round(f*100)+'%';}
function b64ToBytes(b){const s=atob(b),a=new Uint8Array(s.length);for(let i=0;i<s.length;i++)a[i]=s.charCodeAt(i);return a;}
function sleep(ms){return new Promise(r=>setTimeout(r,ms));}
function cksum(a){let s=SEED;for(const b of a)s=(s+b)&0xFF;return s;}

class Port{
  constructor(p){this.p=p;this.q=[];this.run=false;}
  async open(){await this.p.open({baudRate:115200});this.r=this.p.readable.getReader();this.w=this.p.writable.getWriter();this.run=true;this._loop();}
  async _loop(){try{while(this.run){const{value,done}=await this.r.read();if(done)break;if(value)for(const b of value)this.q.push(b);}}catch(e){}}
  async write(a){await this.w.write(new Uint8Array(a));}
  flush(){this.q.length=0;}
  async readByte(ms){const e=Date.now()+ms;while(Date.now()<e){if(this.q.length)return this.q.shift();await sleep(2);}return null;}
  async close(){this.run=false;try{await this.r.cancel();}catch(e){}try{this.r.releaseLock();this.w.releaseLock();}catch(e){}try{await this.p.close();}catch(e){}}
}

async function doFlash(){
  const fw=b64ToBytes(FW_B64);
  let port;
  try{port=new Port(await navigator.serial.requestPort());}catch(e){log('Порт не выбран.','err');return;}
  try{
    await port.open();
    log('Порт открыт. Синхронизация с бутлоадером...');
    port.flush();let ready=false;const end=Date.now()+7000;
    while(Date.now()<end){const b=await port.readByte(150);if(b===null){await port.write([0xFF]);}else if(b===0xFF){ready=true;break;}}
    if(!ready)throw new Error('Рация не отвечает. Выключите и включите её с зажатой тангентой PTT, затем повторите.');
    log('Бутлоадер найден. Стирание...');
    for(const trig of [0x10,0x55]){const p=[0x39,0x33,0x05,trig];p.push(cksum(p));port.flush();await port.write(p);
      let ok=false;const e2=Date.now()+4000;while(Date.now()<e2){if(await port.readByte(200)===ACK){ok=true;break;}}if(!ok)throw new Error('Ошибка стирания.');}
    log('Запись прошивки...');
    const total=Math.ceil(fw.length/1024)*1024;const data=new Uint8Array(total);data.fill(0xFF);data.set(fw);
    for(let off=0;off<total;off+=1024){const chunk=data.subarray(off,off+1024);
      const p=[0x57,(off>>8)&0xFF,off&0xFF];for(const b of chunk)p.push(b);p.push(cksum(p));
      port.flush();await port.write(p);
      let ok=false;const e3=Date.now()+3000;while(Date.now()<e3){if(await port.readByte(100)===ACK){ok=true;break;}}
      if(!ok)throw new Error('Ошибка записи на 0x'+off.toString(16)+'.');
      prog((off+1024)/total);}
    prog(1);log('✅ Готово! Выключите и включите рацию обычно.','ok');
  }catch(e){log('✖ '+e.message,'err');}
  finally{try{await port.close();}catch(e){}}
}

if(!('serial'in navigator)){$('nowebserial').style.display='block';btn.disabled=true;}
btn.onclick=async()=>{btn.disabled=true;logEl.innerHTML='';try{await doFlash();}finally{btn.disabled=false;}};
</script></body></html>"""

info = f"Прошивка Ru-4D V3.25 (русифицированная) · {len(fw)} байт"
html = HTML.replace("__B64__", b64).replace("__INFO__", info)
os.makedirs(os.path.dirname(OUT), exist_ok=True)
open(OUT, "w", encoding="utf-8").write(html)
print(f"firmware {len(fw)} B -> base64 {len(b64)} chars")
print(f"wrote {OUT} ({len(html)} bytes)")
