function workMetrics(work,moneyCents=0,distanceMeters=0,now=Date.now()){
  if(!work)return {workedMs:0,pausedMs:0,incomeCents:moneyCents,distanceMeters,averagePerHour:0,averagePerKm:distanceMeters?moneyCents/100/(distanceMeters/1000):0,status:'Pronto para iniciar'};
  const end=work.endedAt??now;
  const pausedMs=work.pauses.reduce((sum,p)=>sum+Math.max(0,Math.min(p.end??end,end)-Math.max(p.start,work.startedAt)),0);
  const workedMs=Math.max(0,end-work.startedAt-pausedMs);
  const incomeCents=work.endedAt!==null?work.finalIncomeCents:moneyCents,meters=work.endedAt!==null?work.finalDistanceMeters:distanceMeters;
  return {workedMs,pausedMs,incomeCents,distanceMeters:meters,averagePerHour:workedMs?incomeCents/100/(workedMs/3600000):0,averagePerKm:meters?incomeCents/100/(meters/1000):0,status:work.endedAt!==null?'Terminado':work.pauses.at(-1)?.end===null?'Em pausa':'A trabalhar'};
}
function lisbonStartTime(arg,work,now){
  const match=arg.match(/^(?:(\d{4}-\d{2}-\d{2})\s+)?([01]?\d|2[0-3]):([0-5]\d)$/);
  if(!match)throw Error('Usa !inicio HH:MM ou !inicio AAAA-MM-DD HH:MM.');
  const parts=new Intl.DateTimeFormat('en-CA',{timeZone:'Europe/Lisbon',year:'numeric',month:'2-digit',day:'2-digit'}).formatToParts(new Date(work.startedAt));
  const get=k=>parts.find(p=>p.type===k).value,day=match[1]||`${get('year')}-${get('month')}-${get('day')}`;
  const hour=match[2].padStart(2,'0');
  const utc=Date.parse(`${day}T${hour}:${match[3]}:00Z`);
  const candidates=[utc,utc-3600000].filter(ts=>{
    if(!Number.isFinite(ts)||ts>(work.endedAt??now))return false;
    const f=new Intl.DateTimeFormat('en-GB',{timeZone:'Europe/Lisbon',year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit',hourCycle:'h23'}).formatToParts(new Date(ts));
    const value=key=>f.find(p=>p.type===key)?.value;
    return `${value('year')}-${value('month')}-${value('day')}`===day&&value('hour')===hour&&value('minute')===match[3];
  });
  if(!candidates.length)throw Error('A hora de início é inválida ou está no futuro.');
  return Math.max(...candidates);
}
function applyWorkCommand(work,raw,moneyCents,distanceMeters,now=Date.now(),id='work-'+now){
  const normalized=String(raw).normalize('NFD').replace(/[\u0300-\u036f]/g,'').trim();
  const m=normalized.match(/^!(trabalho|p|fim|inicio|incio|km)(?:\s+(.+))?$/i);
  if(!m)return null;
  const command=m[1].toLowerCase()==='incio'?'inicio':m[1].toLowerCase(),arg=(m[2]||'').trim();
  let next=work?JSON.parse(JSON.stringify(work)):null,meters=distanceMeters;
  if(command==='km'){
    if(!/^[+-]?\d+(?:[.,]\d{1,3})?$/.test(arg))throw Error('Usa !km 150 ou !km +10.');
    const n=Math.round(Number(arg.replace(',','.'))*1000);
    meters=/^[+-]/.test(arg)?meters+n:n;
    if(!Number.isSafeInteger(meters)||meters<0||meters>1000000000)throw Error('Quilómetros inválidos.');
    if(next?.endedAt!==null&&next)throw Error('O dia está terminado. Inicia um novo dia para alterar os quilómetros.');
  }else if(command==='trabalho'){
    if(arg)throw Error('Usa !inicio HH:MM para corrigir o início.');
    if(!next||next.endedAt!==null)next={id,startedAt:now,endedAt:null,pauses:[],finalIncomeCents:null,finalDistanceMeters:null};
    else if(next.pauses.at(-1)?.end===null)next.pauses.at(-1).end=now;
  }else{
    if(!next&&command==='inicio')next={id,startedAt:now,endedAt:null,pauses:[],finalIncomeCents:null,finalDistanceMeters:null};
    if(!next)throw Error('Inicia primeiro com !trabalho.');
    if(command==='inicio')next.startedAt=lisbonStartTime(arg,next,now);
    else if(command==='p'){
      if(next.endedAt!==null)return {work:next,distanceMeters:meters};
      if(next.pauses.at(-1)?.end!==null)next.pauses.push({start:now,end:null});
    }else if(command==='fim'){
      if(next.endedAt===null){if(next.pauses.at(-1)?.end===null)next.pauses.at(-1).end=now;next.endedAt=now;next.finalIncomeCents=moneyCents;next.finalDistanceMeters=meters;}
    }
  }
  return {work:next,distanceMeters:meters};
}
function validateWorkSnapshot(value,now=Date.now()){
  const integer=(v,max=1000000000000)=>Number.isSafeInteger(v)&&v>=0&&v<=max;
  if(!value||!integer(value.revision,Number.MAX_SAFE_INTEGER)||!integer(value.moneyCents)||!integer(value.distanceMeters,1000000000))throw Error('Valores de sincronização inválidos.');
  const work=value.work;
  if(work!==null){
    if(!work||typeof work.id!=='string'||!/^[\w-]{1,100}$/.test(work.id)||!integer(work.startedAt,now+300000)||!Array.isArray(work.pauses)||work.pauses.length>10000)throw Error('Contador inválido.');
    const end=work.endedAt??now;
    if(!integer(end,now+300000)||end<work.startedAt)throw Error('Fim inválido.');
    let previous=0;
    work.pauses.forEach((p,i)=>{if(!integer(p.start,end)||p.start<previous||(p.end===null?(work.endedAt!==null||i!==work.pauses.length-1):(!integer(p.end,end)||p.end<p.start)))throw Error('Pausa inválida.');previous=p.end??end});
    if(work.endedAt!==null&&(!integer(work.finalIncomeCents)||!integer(work.finalDistanceMeters,1000000000)))throw Error('Totais finais inválidos.');
  }
  return {revision:value.revision,moneyCents:value.moneyCents,distanceMeters:value.distanceMeters,work,feedback:String(value.feedback||'').slice(0,240)};
}

const STORE_KEY='lisbonbusdriverwidgetv1';
const MONEY_COOLDOWN_MS=6000;
let moneyReadyAt=0;
const $=id=>document.getElementById(id);
let fields={},ready=false,currentCarMode=null,tick=null,audioContext=null,limitAlertedFor=0;
let state={status:'LIVRE',elapsedBase:0,elapsedStartedAt:0,totalBase:0,totalStartedAt:0,moneyCents:0,targetCents:10000,limitSeconds:0};
function secondsNow(base,start){return Math.max(0,Math.floor(Number(base||0)+(start?(Date.now()-start)/1000:0)))}
function clockText(seconds,alwaysHours=false){seconds=Math.max(0,Math.floor(seconds||0));const h=Math.floor(seconds/3600),m=Math.floor(seconds%3600/60),s=seconds%60,p=n=>String(n).padStart(2,'0');return alwaysHours||h?[p(h),p(m),p(s)].join(':'):[p(m),p(s)].join(':')}
function moneyText(cents){return(cents/100).toLocaleString('pt-PT',{minimumFractionDigits:2,maximumFractionDigits:2})+' €'}
const ALERT_TONES={l:[[660,0,.11],[880,.12,.13]],v:[[494,0,.12],[659,.14,.12],[880,.28,.18]],p:[[587,0,.12],[392,.15,.2]],m:[[440,0,.18]],r:[[523,0,.1],[392,.15,.16]],g:[[784,0,.1],[988,.11,.16]],meta:[[587,0,.1],[784,.12,.15]],goal:[[784,0,.12],[988,.14,.12],[1318,.29,.28]],limit:[[880,0,.12],[659,.15,.13],[440,.31,.24]]};
function playAlert(name){if(fields.soundEnabled===false||String(fields.soundEnabled).toLowerCase()==='false')return;const Ctx=window.AudioContext||window.webkitAudioContext;if(!Ctx)return;try{audioContext=audioContext||new Ctx();if(audioContext.state==='suspended')audioContext.resume().catch(()=>{});const volume=Math.max(0,Math.min(1,Number(fields.soundVolume??40)/100));if(!volume)return;const start=audioContext.currentTime;for(const [frequency,offset,duration] of ALERT_TONES[name]||[]){const oscillator=audioContext.createOscillator(),gain=audioContext.createGain(),when=start+offset;oscillator.type='sine';oscillator.frequency.setValueAtTime(frequency,when);gain.gain.setValueAtTime(.0001,when);gain.gain.exponentialRampToValueAtTime(.16*volume,when+.012);gain.gain.exponentialRampToValueAtTime(.0001,when+duration);oscillator.connect(gain);gain.connect(audioContext.destination);oscillator.start(when);oscillator.stop(when+duration+.02)}}catch(e){}}
function parseMoney(raw){const normalized=String(raw||'').replace(/€/g,'').trim().replace(/\.(?=\d{3}(?:\D|$))/g,'').replace(',','.');const n=Number(normalized);return Number.isFinite(n)&&n>=0&&n<=1000000?Math.round(n*100):null}
function parseDuration(raw){const v=String(raw||'').trim().toLowerCase().replace(/\s+/g,'').replace(',','.');let n=0;let m=v.match(/^(\d+(?:\.\d+)?)(s|sec|seg|m|min|h|hr|hora|horas)$/);if(m){const amount=Number(m[1]),unit=m[2];n=amount*(unit==='s'||unit==='sec'||unit==='seg'?1:unit==='h'||unit==='hr'||unit==='hora'||unit==='horas'?3600:60)}else if((m=v.match(/^(\d+)h(?:(\d+)m)?$/)))n=Number(m[1])*3600+Number(m[2]||0)*60;else if((m=v.match(/^(\d+):(\d{1,2})(?::(\d{1,2}))?$/))){n=m[3]!==undefined?Number(m[1])*3600+Number(m[2])*60+Number(m[3]):Number(m[1])*60+Number(m[2])}return Number.isFinite(n)&&n>0&&n<=86400?Math.floor(n):null}
function parseTotalTime(raw){const match=String(raw||'').trim().match(/^(\d+):([0-5]\d):([0-5]\d)$/);if(!match)return null;const seconds=Number(match[1])*3600+Number(match[2])*60+Number(match[3]);return Number.isSafeInteger(seconds)?seconds:null}
function render(){const root=$('widget'),elapsed=secondsNow(state.elapsedBase,state.elapsedStartedAt),total=secondsNow(state.totalBase,state.totalStartedAt),money=Number(state.moneyCents)||0,target=Math.max(1,Number(state.targetCents)||10000);$('status').textContent=state.status;root.classList.toggle('travel',state.status==='EM VIAGEM');root.classList.toggle('paused',state.status==='PAUSA');root.classList.toggle('muted',state.status==='MUTE');setCar(state.status==='EM VIAGEM'?'on':'off');$('elapsed').textContent=clockText(elapsed);$('total').textContent=clockText(total,true);$('tripLimit').textContent=state.limitSeconds?' / '+clockText(state.limitSeconds):'';const overLimit=Boolean(state.limitSeconds&&elapsed>state.limitSeconds);$('currentStat').classList.toggle('over-limit',overLimit);if(overLimit){if(limitAlertedFor!==state.limitSeconds){playAlert('limit');limitAlertedFor=state.limitSeconds}}else limitAlertedFor=0;$('money').textContent=moneyText(money);$('moneyTargetLabel').textContent=moneyText(target);const complete=money>=target;$('moneyProgress').style.width=Math.max(0,Math.min(100,money/target*100))+'%';$('moneyProgress').parentElement.classList.toggle('complete',complete);document.querySelector('.stat.objective').classList.toggle('goal-met',complete)}
function saveOriginal(){if(ready&&window.SE_API&&SE_API.store)try{SE_API.store.set(STORE_KEY,state)}catch(e){}}
function syncTimers(){state.elapsedBase=secondsNow(state.elapsedBase,state.elapsedStartedAt);state.totalBase=secondsNow(state.totalBase,state.totalStartedAt);if(state.elapsedStartedAt)state.elapsedStartedAt=Date.now();if(state.totalStartedAt)state.totalStartedAt=Date.now()}
function legacyCommand(raw){const text=String(raw||'').trim().replace(/^!g-(?=\s*\d)/i,'!g -'),match=text.match(/^!(l|v|p|m|r|g|meta|total)(?:\s+(.+))?$/i);if(!match)return false;const cmd=match[1].toLowerCase(),arg=(match[2]||'').trim(),now=Date.now(),previousStatus=state.status,goalWasReached=Number(state.moneyCents)>=Number(state.targetCents);if(cmd==='l'){syncTimers();state.status='LIVRE';state.elapsedBase=0;state.elapsedStartedAt=0;state.totalStartedAt=0;state.limitSeconds=0}else if(cmd==='v'){const limit=arg?parseDuration(arg):null;if(arg&&!limit)return false;syncTimers();state.status='EM VIAGEM';if(limit)state.limitSeconds=limit;if(!state.elapsedStartedAt)state.elapsedStartedAt=now;if(!state.totalStartedAt)state.totalStartedAt=now}else if(cmd==='p'){syncTimers();state.status='PAUSA';state.elapsedStartedAt=0;state.totalStartedAt=0}else if(cmd==='m'){state.status='MUTE'}else if(cmd==='r'){state.status='LIVRE';state.elapsedBase=0;state.elapsedStartedAt=0;state.totalBase=0;state.totalStartedAt=0;state.moneyCents=0;state.limitSeconds=0}else if(cmd==='g'){const subtract=arg.startsWith('-'),amount=parseMoney(subtract?arg.slice(1).trim():arg);if(amount===null)return false;if(now<moneyReadyAt)return false;moneyReadyAt=now+MONEY_COOLDOWN_MS;state.moneyCents=Math.max(0,(Number(state.moneyCents)||0)+(subtract?-amount:amount))}else if(cmd==='total'){const seconds=parseTotalTime(arg);if(seconds===null)return false;state.totalBase=seconds;state.totalStartedAt=state.totalStartedAt?now:0}else if(cmd==='meta'){const amount=parseMoney(arg);if(amount===null||amount===0)return false;state.targetCents=amount}let sound=cmd;if(['l','v','p','m'].includes(cmd)&&previousStatus===state.status)sound='';if(!goalWasReached&&Number(state.moneyCents)>=Number(state.targetCents))sound='goal';if(sound)playAlert(sound);render();save();return true}
function isAllowed(message){const nick=String(message.nick||message.username||message.displayName||'').replace(/^@/,'').toLowerCase(),channel=String(fields.channelName||'').replace(/^#/,'').trim().toLowerCase(),tags=message.tags||{},badgeList=Array.isArray(message.badges)?message.badges.map(b=>String((b&&b.type)||b||'')).join(','):String(tags.badges||'');return nick===channel||String(tags.mod||'')==='1'||/(^|[,/])moderator([,/]|$)|(^|[,/])broadcaster([,/]|$)/i.test(badgeList)}
function applyMessage(data){const message=data&&data.data?data.data:data;if(message&&isAllowed(message))command(message.text||'')}
function imageUrl(value){if(Array.isArray(value))value=value[0];if(value&&typeof value==='object')value=value.url||value.src||'';return typeof value==='string'?value:''}
function setCar(mode){if(currentCarMode===mode)return;currentCarMode=mode;const image=$('carImage'),off=imageUrl(fields.carOffImage),on=imageUrl(fields.carOnImage),src=mode==='on'?(on||off):(off||on);if(src){image.src=src;image.style.visibility='visible'}else image.style.visibility='hidden'}
window.addEventListener('onWidgetLoad',async event=>{fields=event.detail.fieldData||{};$('brand1').textContent=String(fields.brand1||'LISBON').toUpperCase();$('brand2').textContent=String(fields.brand2||'BUSDRIVER').toUpperCase();$('subtitle').textContent=String(fields.subtitle||'TELEMETRY SYSTEM //').toUpperCase();const coinSource=imageUrl(fields.coinImage);if(coinSource)$('coinImage').src=coinSource;state.targetCents=parseMoney(fields.moneyTarget||100)||10000;setCar('off');const panel=$('editorTests');if(panel){if(fields.showTestButtons===true||String(fields.showTestButtons).toLowerCase()==='true')panel.classList.add('visible');panel.addEventListener('click',e=>{const b=e.target.closest('[data-command]');if(b)command(b.dataset.command)})}try{if(window.SE_API&&SE_API.store){const saved=await SE_API.store.get(STORE_KEY);if(saved&&typeof saved==='object')state={...state,...saved}}}catch(e){}state.targetCents=Math.max(1,Number(state.targetCents)||10000);state.moneyCents=Math.max(0,Number(state.moneyCents)||0);state.limitSeconds=Math.max(0,Number(state.limitSeconds)||0);ready=true;render();save();if(tick)clearInterval(tick);tick=setInterval(render,250)});
window.addEventListener('onEventReceived',event=>{const d=event.detail||{};if(d.listener==='message')applyMessage(d.event);if(d.listener==='widget-button'){const e=d.event||{},map={testLivre:'!l',testViagem:'!v',testPause:'!p',testMute:'!m',testReset:'!r',testMoney:'!g 5',testGoal:'!g 100',testMeta:'!meta 100'};const cmd=map[String(e.field||'')]||String(e.value||'');if(cmd)command(cmd)}});



// Work commands are separate from the existing trip/status commands.
function command(raw){
  if(!ready)return false;
  const text=String(raw||'').normalize('NFD').replace(/[\u0300-\u036f]/g,'').trim();
  const isWork=/^!(trabalho|fim|inicio|incio|km|p)(?:\s|$)/i.test(text);
  if(isWork){
    try{
      const result=applyWorkCommand(state.workday||null,text,Number(state.moneyCents)||0,Number(state.distanceMeters)||0,Date.now(),'work-'+Date.now());
      if(!result)return false;
      if(/^!p(?:\s|$)/i.test(text))legacyCommand(text);
      state.workday=result.work;state.distanceMeters=result.distanceMeters;
      state.workFeedback=/^!(inicio|incio)\s/i.test(text)?'Início corrigido: '+new Date(state.workday.startedAt).toLocaleString('pt-PT',{timeZone:'Europe/Lisbon'}):'Comando de trabalho recebido.';
      state.workCodeVersion=3;
      render();save();return true;
    }catch(error){state.workFeedback='Erro: '+error.message;state.workCodeVersion=3;save();console.warn('LisbonBusDriver: '+error.message);return false}
  }
  return legacyCommand(text);
}
let syncPending=null,syncBusy=false,syncRetry=null,syncBackoff=5000;
function queueWorkSync(){
  if(!ready)return;
  // Never send money or a private key to an arbitrary URL from field data.
  const token=String(fields.workSyncKey||'').trim();
  if(!/^[a-f0-9]{64}$/.test(token))return;
  state.workRevision=Math.max(Date.now(),Number(state.workRevision||0)+1);
  const snapshot={revision:state.workRevision,moneyCents:Number(state.moneyCents)||0,distanceMeters:Number(state.distanceMeters)||0,work:state.workday?JSON.parse(JSON.stringify(state.workday)):null,feedback:state.workFeedback||''};
  const workId=snapshot.work?.id||'no-work';
  state.workOutbox=(Array.isArray(state.workOutbox)?state.workOutbox:[]).filter(s=>(s.work?.id||'no-work')!==workId);
  state.workOutbox.push(snapshot);
  syncPending=state.workOutbox[0];
  void sendWorkSync();
}
async function sendWorkSync(){
  if(syncBusy||!syncPending||syncRetry)return;
  syncBusy=true;const sending=syncPending;
  try{
    const response=await fetch('https://lisbonbusdriver-viagens.pages.dev/api/widget-sync',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+String(fields.workSyncKey||'').trim()},body:JSON.stringify(sending)});
    if(!response.ok)throw Error('HTTP '+response.status);
    state.workOutbox=(state.workOutbox||[]).filter(s=>s.revision!==sending.revision);
    syncPending=state.workOutbox[0]||null;saveOriginal();
    syncBackoff=5000;
  }catch(error){
    console.warn('LisbonBusDriver: não foi possível sincronizar o widget. Verifica a chave e a ligação.');
    syncRetry=setTimeout(()=>{syncRetry=null;void sendWorkSync()},syncBackoff);syncBackoff=Math.min(60000,syncBackoff*2);
  }finally{syncBusy=false;if(syncPending&&!syncRetry)void sendWorkSync()}
}
function save(){
  queueWorkSync();saveOriginal();
}
