const money = n => Number(n || 0).toFixed(2);
const qty = n => Number(n || 0).toFixed(3).replace(/\.000$/,'').replace(/(\.\d*[1-9])0+$/,'$1');
const fmtDate = d => d.toISOString().slice(0,10);
const today = fmtDate(new Date());
let currentPage = 'dashboard';
const LANG_STORAGE_KEY = 'hiposReportLangManualV4';
function readCookieLang(){
  try{
    const m = document.cookie.match(/(?:^|;\s*)HiposReportLang=(zh|es)(?:;|$)/i);
    return m ? m[1].toLowerCase() : '';
  }catch(e){ return ''; }
}
function readSavedLang(){
  try{
    const saved = localStorage.getItem(LANG_STORAGE_KEY);
    if(saved === 'es' || saved === 'zh') return saved;
  }catch(e){}
  const cookieLang = readCookieLang();
  return cookieLang === 'es' ? 'es' : 'zh';
}
let lang = readSavedLang();
let baseStoreName = 'HIPOS';
let currentQuery = '';
let inventoryCategory = '';
let inventorySupplier = '';
let inventoryTax = '';
let inventoryStockMode = 'all';
let sensitiveType = '';
let sensitiveTypes = [];
let sensitiveEmployee = '';
let lastSensitiveRows = [];
let supplierNames = {};
const isAppMode = new URLSearchParams(location.search).get('tipo') === 'app';
const pagesWithoutTopSummary = new Set(['products','sensitive','inventory','purchases','suppliers','clients','staff','devices','tax']);
const storeName = document.getElementById('storeName');
const pageHint = document.getElementById('pageHint');
const from = document.getElementById('from');
const to = document.getElementById('to');
const cards = document.getElementById('cards');
const grandTotal = document.getElementById('grandTotal');
const ticketCount = document.getElementById('ticketCount');
const averageTicket = document.getElementById('averageTicket');
const discountAmount = document.getElementById('discountAmount');
const pageTitle = document.getElementById('pageTitle');
const main = document.getElementById('main');
const moreSheet = document.getElementById('moreSheet');
const modal = document.getElementById('modal');
const modalTitle = document.getElementById('modalTitle');
const modalBody = document.getElementById('modalBody');
if (isAppMode) document.documentElement.classList.add('app-mode');

const dict = {
  es:{
    excel:'Excel',pdf:'PDF',logout:'Salir',from:'Fecha inicial',to:'Fecha final',today:'Hoy',yesterday:'Ayer',week:'7 días',month:'Mes',lastMonth:'Mes anterior',quarter:'Trimestre',year:'Año',dayReport:'Diario',weekReport:'Semanal',monthReport:'Mensual',quarterReport:'Trimestral',yearReport:'Anual',refresh:'Actualizar',all:'Todo',
    dashboard:'Inicio',sales:'Tickets de venta',payments:'Pagos',products:'Productos más vendidos',sensitive:'Operaciones sensibles',inventory:'Stock',clients:'Clientes',staff:'Empleados',devices:'Dispositivos',analysis:'Análisis',analysisShort:'Informes',taxReq:'IVA / REQ',more:'Más',language:'Idioma',chinese:'Chino',spanish:'Español',
    totalSales:'Ventas',tickets:'Tickets',avgTicket:'Ticket medio',discount:'Descuento',tax:'IVA',req:'REQ',general:'Inicio',salesList:'Tickets de venta',paySummary:'Ventas por forma de pago',topProducts:'Productos más vendidos',sensitiveHint:'Descuentos, cambios de precio, devoluciones, cajón, etc.',stockHint:'Escanee o introduzca código, código de barras o nombre',clientHint:'Buscar por nombre, teléfono, correo electrónico o VIP',staffHint:'Usuarios o empleados',deviceHint:'Ventas por equipo, empleado y forma de pago',analysisHint:'Informes por usuario y hora',taxHint:'IVA y recargo de equivalencia',
    method:'Método',amount:'Importe',category:'Categoría',product:'Artículo',date:'Fecha',client:'Cliente',user:'Usuario',employee:'Empleado',ticket:'Ticket',code:'Código',barcode:'Código de barras',name:'Nombre',phone:'Teléfono',email:'Correo electrónico',active:'Activo',status:'Estado',role:'Rol',hour:'Hora',quantity:'Cant.',price:'Precio',cost:'Coste',balance:'Saldo',points:'Puntos',before:'Antes',after:'Después',diff:'Dif.',note:'Nota',device:'Equipo',noName:'(Sin nombre)',noData:'Sin datos',loadError:'Error al cargar datos: ',detail:'Detalle',change:'Cambio',paymentsTitle:'Pagos',chartPayments:'Ventas por forma de pago',chartCategories:'Ventas por categoría',chartProducts:'Productos más vendidos',chartTrend:'Tendencia de ventas',chartHours:'Ventas por hora',chartUsers:'Ventas por usuario',lastTickets:'Últimos tickets',base:'Base',taxRate:'IVA %',reqRate:'REQ %',taxAmount:'IVA',reqAmount:'REQ',total:'Total',subtotal:'Subtotal',yes:'Sí',no:'No',down:'Baja',birth:'Nacimiento',start:'Alta',search:'Buscar',scanOrSearch:'Escanear o buscar',clear:'Limpiar',receipt:'Ticket',allCategories:'Todas las categorías',allStock:'Todos',stockGe0:'Stock >= 0',stockEq0:'Stock = 0',stockLt0:'Stock < 0',stockGt0:'Stock > 0',copy:'Copiar',copied:'Correo electrónico copiado',view:'Ver',supplier:'Proveedores',purchases:'Entradas',purchaseOrders:'Entradas de mercancía',allSuppliers:'Todos los proveedores',allTaxes:'Todos los tipos de IVA',operationType:'Tipo de operación',supplierProductRank:'Ranking de artículos del proveedor',back:'Volver',productsCount:'N.º de artículos',filterEmployee:'Empleado',filterOperation:'Tipo de operación',allEmployees:'Todos los empleados',operation:'Operación',computer:'Equipo',stock:'Stock',salePrice:'Precio de venta',id:'ID',dni:'DNI',vip:'VIP', operationId:'ID', operationTypeLabel:'Tipo', deviceId:'ID del dispositivo', detailJson:'Detalle JSON'
  },
  zh:{
    excel:'Excel',pdf:'PDF',logout:'退出',from:'起始日期',to:'结束日期',today:'今天',yesterday:'昨天',week:'7天',month:'本月',lastMonth:'上月',quarter:'本季度',year:'本年',dayReport:'日报',weekReport:'周报',monthReport:'月报',quarterReport:'季度报',yearReport:'年报',refresh:'刷新',all:'全部',
    dashboard:'首页',sales:'销售',payments:'支付',products:'商品排行',sensitive:'敏感操作',inventory:'库存',clients:'客户',staff:'员工',devices:'设备',analysis:'统计分析',analysisShort:'统计',taxReq:'税额/REQ',more:'更多',language:'选择语言',chinese:'中文',spanish:'西语',
    totalSales:'销售额',tickets:'单数',avgTicket:'客单价',discount:'折扣',tax:'税额',req:'REQ',general:'综合概览',salesList:'销售单列表',paySummary:'按支付方式统计',topProducts:'按金额排行',sensitiveHint:'折扣、改价、退货、开钱箱等操作',stockHint:'扫码或输入编码、条码、名称查询',clientHint:'按名称、电话、邮箱、VIP号查询',staffHint:'用户或员工资料',deviceHint:'按设备、员工和支付方式统计',analysisHint:'按用户和小时统计',taxHint:'税额和 REQ 单独统计',
    method:'方式',amount:'金额',category:'分类',product:'商品',date:'日期',client:'客户',user:'用户',employee:'员工',ticket:'单号',code:'编码',barcode:'条码',name:'名称',phone:'电话',email:'邮箱',active:'启用',status:'状态',role:'角色',hour:'小时',quantity:'数量',price:'单价',cost:'成本',balance:'余额',points:'积分',before:'之前',after:'之后',diff:'差额',note:'备注',device:'设备',noName:'(无名称)',noData:'无数据',loadError:'读取数据失败：',detail:'明细',change:'找零',paymentsTitle:'支付',chartPayments:'支付统计',chartCategories:'分类统计',chartProducts:'商品排行',chartTrend:'销售趋势',chartHours:'小时销售',chartUsers:'用户销售',lastTickets:'最近单据',base:'基数',taxRate:'税率%',reqRate:'REQ%',taxAmount:'税额',reqAmount:'REQ',total:'合计',subtotal:'小计',yes:'是',no:'否',down:'离职',birth:'生日',start:'入职',search:'查询',scanOrSearch:'扫码或输入关键字',clear:'清空',receipt:'小票',allCategories:'全部分类',allStock:'全部',stockGe0:'库存>=0',stockEq0:'库存=0',stockLt0:'库存<0',stockGt0:'库存>0',copy:'复制',copied:'邮箱地址已复制',view:'查看',supplier:'供应商',purchases:'进货单',purchaseOrders:'进货单',allSuppliers:'全部供应商',allTaxes:'全部税率',operationType:'操作类别',supplierProductRank:'供应商商品销量排行',back:'返回',productsCount:'商品数',filterEmployee:'员工',filterOperation:'操作方式',allEmployees:'全部员工',operation:'类型',computer:'电脑',stock:'库存',salePrice:'售价',id:'ID',dni:'DNI',vip:'VIP', operationId:'ID', operationTypeLabel:'类型', deviceId:'设备ID', detailJson:'详细JSON'
  }
};
function t(k){return (dict[lang]&&dict[lang][k])||dict.es[k]||k;}
async function setLang(v){
  lang = v==='es' ? 'es' : 'zh';
  try{ localStorage.setItem(LANG_STORAGE_KEY,lang); }catch(e){}
  try{ document.cookie = 'HiposReportLang=' + lang + '; path=/; max-age=31536000; SameSite=Lax'; }catch(e){}
  applyLang();
  hideMore();
  updateHeaderTitle();
  try{ await loadCurrent(); }catch(e){ console.error(e); }
}
function applyLang(){
  document.documentElement.lang=lang;
  document.querySelectorAll('[data-i18n]').forEach(e=>e.textContent=t(e.dataset.i18n));

  // 底部导航单独强制同步，避免 APP WebView 保留 HTML 初始语言或旧节点文本。
  const bottomLabels = {
    dashboard: t('dashboard'),
    payments: t('payments'),
    analysis: t('analysisShort'),
    more: t('more')
  };
  const homeLabel = document.querySelector('.bottom-nav [data-page="dashboard"] em');
  const payLabel = document.querySelector('.bottom-nav [data-page="payments"] em');
  const analysisLabel = document.querySelector('.bottom-nav [data-page="analysis"] em');
  const moreLabel = document.querySelector('.bottom-nav [data-action="more"] em');
  if(homeLabel) homeLabel.textContent = bottomLabels.dashboard;
  if(payLabel) payLabel.textContent = bottomLabels.payments;
  if(analysisLabel) analysisLabel.textContent = bottomLabels.analysis;
  if(moreLabel) moreLabel.textContent = bottomLabels.more;

  document.querySelectorAll('[data-lang-choice]').forEach(e=>e.classList.toggle('active',e.dataset.langChoice===lang));
}
from.value=today; to.value=today; setQuickDateActive('day');
from.addEventListener('change',()=>setQuickDateActive(''));
to.addEventListener('change',()=>setQuickDateActive(''));

async function api(url){const r=await fetch(url,{cache:'no-store'}); if(r.status===401||r.redirected){location.href='/login.html';throw new Error('auth');} if(!r.ok) throw new Error(await r.text()); return await r.json();}
function range(){return `from=${encodeURIComponent(from.value)}&to=${encodeURIComponent(to.value)}`;}
function queryParam(){return 'q='+encodeURIComponent(currentQuery||'');}
function inventoryParam(){return queryParam()+'&categoryId='+encodeURIComponent(inventoryCategory||'')+'&supplierId='+encodeURIComponent(inventorySupplier||'')+'&taxRate='+encodeURIComponent(inventoryTax||'')+'&stockMode='+encodeURIComponent(inventoryStockMode||'all');}
function esc(s){return String(s ?? '').replace(/[&<>'"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#39;','"':'&quot;'}[c]));}
function shortDate(s){
  if(!s) return '';
  const str=String(s);
  const m=str.match(/(\d{4})[-/](\d{1,2})[-/](\d{1,2})(?:[ T](\d{1,2}):(\d{2}))?/);
  if(!m) return esc(str);
  const md=Number(m[2])+'/'+Number(m[3]);
  return m[4] ? md+' '+m[4]+':'+m[5] : md;
}
function tableId(){return 'tbl_'+currentPage+'_'+Math.random().toString(36).slice(2,8);}
function makeTable(headers, rows, opt={}){
  if(!rows || !rows.length) return `<div class="empty">${t('noData')}</div>`;
  const id=opt.id||tableId(), pageSize=opt.pageSize||50;
  const th=headers.map(h=>`<th class="${h.cls||''}">${esc(h.t)}</th>`).join('');
  const trs=rows.map((r,idx)=>`<tr data-row="${idx}" style="${idx>=pageSize?'display:none':''}">${r.map((c,i)=>`<td data-label="${esc(headers[i]?.t||'')}" class="${headers[i]?.cls||''}">${c ?? ''}</td>`).join('')}</tr>`).join('');
  const pages=Math.ceil(rows.length/pageSize);
  const pager=pages>1?`<div class="pager" data-total="${rows.length}" data-size="${pageSize}" data-page="1"><button onclick="pageTable('${id}',-1)">‹</button><span>1 / ${pages}</span><button onclick="pageTable('${id}',1)">›</button></div>`:'';
  return `<div class="table-wrap" id="${id}"><table><thead><tr>${th}</tr></thead><tbody>${trs}</tbody></table>${pager}</div>`;
}
function pageTable(id,dir){
  const box=document.getElementById(id); if(!box) return;
  const pager=box.querySelector('.pager'); if(!pager) return;
  const total=Number(pager.dataset.total||0), size=Number(pager.dataset.size||50);
  const pages=Math.max(1,Math.ceil(total/size)); let p=Number(pager.dataset.page||1)+dir;
  p=Math.max(1,Math.min(p,pages)); pager.dataset.page=p;
  box.querySelectorAll('tbody tr').forEach((tr,i)=>{tr.style.display=(i>=(p-1)*size&&i<p*size)?'':'none'});
  const span=pager.querySelector('span'); if(span) span.textContent=p+' / '+pages;
}
function currentTitleText(){return pageTitle?.textContent||'';}
function updateHeaderTitle(){const pg=currentTitleText();storeName.textContent=(window.innerWidth<=700&&pg)?(baseStoreName+' · '+pg):baseStoreName;}
function setTitle(k,h=''){pageTitle.textContent=t(k)||k;pageHint.textContent=h;updateHeaderTitle();}
let activeQuickDate = 'day';
function setQuickDateActive(type){
  activeQuickDate = type || 'day';
  document.querySelectorAll('.quick-dates button[data-quick]').forEach(b=>b.classList.toggle('active', b.dataset.quick===activeQuickDate));
}
function parseDateLocal(value){
  const m=String(value||'').match(/^(\d{4})-(\d{2})-(\d{2})$/);
  return m ? new Date(Number(m[1]),Number(m[2])-1,Number(m[3])) : new Date();
}
function formatDateLocal(d){
  const y=d.getFullYear(),m=String(d.getMonth()+1).padStart(2,'0'),day=String(d.getDate()).padStart(2,'0');
  return `${y}-${m}-${day}`;
}
function periodRange(type, anchor){
  const d=new Date(anchor.getFullYear(),anchor.getMonth(),anchor.getDate());
  let f=new Date(d), tt=new Date(d);
  if(type==='week'){
    const mondayOffset=(d.getDay()+6)%7;
    f.setDate(d.getDate()-mondayOffset);
    tt=new Date(f);tt.setDate(f.getDate()+6);
  }else if(type==='month'){
    f=new Date(d.getFullYear(),d.getMonth(),1);
    tt=new Date(d.getFullYear(),d.getMonth()+1,0);
  }else if(type==='quarter'){
    const q=Math.floor(d.getMonth()/3)*3;
    f=new Date(d.getFullYear(),q,1);
    tt=new Date(d.getFullYear(),q+3,0);
  }else if(type==='year'){
    f=new Date(d.getFullYear(),0,1);
    tt=new Date(d.getFullYear(),11,31);
  }
  return {from:f,to:tt};
}
function applyReportPeriod(type, anchor, reload=true){
  activeQuickDate=type||'day';
  const r=periodRange(activeQuickDate,anchor||new Date());
  from.value=formatDateLocal(r.from);to.value=formatDateLocal(r.to);
  setQuickDateActive(activeQuickDate);
  if(reload) loadCurrent();
}
function selectReportPeriod(type){applyReportPeriod(type,new Date(),true);}
function shiftReportPeriod(delta){
  let anchor=parseDateLocal(from.value);
  if(activeQuickDate==='day') anchor.setDate(anchor.getDate()+delta);
  else if(activeQuickDate==='week') anchor.setDate(anchor.getDate()+delta*7);
  else if(activeQuickDate==='month') anchor.setMonth(anchor.getMonth()+delta);
  else if(activeQuickDate==='quarter') anchor.setMonth(anchor.getMonth()+delta*3);
  else if(activeQuickDate==='year') anchor.setFullYear(anchor.getFullYear()+delta);
  applyReportPeriod(activeQuickDate,anchor,true);
}
function quickDate(type){
  const map={today:'day',week:'week',month:'month',quarter:'quarter',year:'year'};
  if(type==='yesterday'){const d=new Date();d.setDate(d.getDate()-1);applyReportPeriod('day',d,true);return;}
  if(type==='lastMonth'){const d=new Date();d.setMonth(d.getMonth()-1);applyReportPeriod('month',d,true);return;}
  applyReportPeriod(map[type]||'day',new Date(),true);
}
function colors(){return ['#d61f1f','#ef4438','#f36b63','#f59a94','#f8c5c1','#b91515'];}
function labelText(key,val){return String(key||'').toLowerCase().includes('date')||String(key||'').toLowerCase().includes('time') ? shortDate(val) : esc(val||'');}
function chartBar(title,data,labelKey,valueKey,valueFmt=money){data=data||[];const max=Math.max(...data.map(x=>Number(x[valueKey]||0)),0);const rows=data.slice(0,12).map(x=>{const v=Number(x[valueKey]||0),pct=max>0?Math.max(2,Math.round(v/max*100)):0;return `<div class="bar-row"><div class="bar-label">${labelText(labelKey,x[labelKey]||'')}</div><div class="bar-track"><div class="bar-fill" style="width:${pct}%"></div></div><div class="bar-value">${valueFmt(v)}</div></div>`}).join('');return `<div class="chart-card"><h3>${esc(title)}</h3>${rows||`<div class="empty">${t('noData')}</div>`}</div>`;}
function chartVBar(title,data,labelKey,valueKey){data=(data||[]).slice(0,18);const max=Math.max(...data.map(x=>Number(x[valueKey]||0)),0);const bars=data.map(x=>{const v=Number(x[valueKey]||0),pct=max>0?Math.max(3,Math.round(v/max*100)):3,label=String(labelText(labelKey,x[labelKey]??'')).slice(0,5);return `<div class="vbar-item" title="${labelText(labelKey,x[labelKey])}: ${money(v)}"><div class="vbar-fill" style="height:${pct}%"></div><span class="vbar-label">${esc(label)}</span></div>`}).join('');return `<div class="chart-card"><h3>${esc(title)}</h3><div class="vbar-wrap">${bars||`<div class="empty">${t('noData')}</div>`}</div></div>`;}
function chartPie(title,data,labelKey,valueKey){data=(data||[]).slice(0,6);const vals=data.map(x=>Math.max(0,Number(x[valueKey]||0))),total=vals.reduce((a,b)=>a+b,0),cs=colors();let deg=0;const stops=vals.map((v,i)=>{const a=total>0?v/total*360:360/(data.length||1),part=`${cs[i%cs.length]} ${deg}deg ${deg+a}deg`;deg+=a;return part}).join(',');const legend=data.map((x,i)=>`<div><span class="pie-dot" style="background:${cs[i%cs.length]}"></span><span>${labelText(labelKey,x[labelKey]||'')} · ${money(x[valueKey])}</span></div>`).join('');return `<div class="chart-card"><h3>${esc(title)}</h3><div class="pie-wrap"><div class="pie" style="background:conic-gradient(${stops||'#d7f3ff 0deg 360deg'})"></div><div class="pie-legend">${legend||`<div class="empty">${t('noData')}</div>`}</div></div></div>`;}
function chartStep(title,data,labelKey,valueKey){data=(data||[]).slice(0,24);const max=Math.max(...data.map(x=>Number(x[valueKey]||0)),0);const rows=data.map(x=>{const v=Number(x[valueKey]||0),pct=max>0?Math.max(3,Math.round(v/max*100)):3,label=String(labelText(labelKey,x[labelKey]??'')).slice(0,6);return `<div class="step" data-label="${esc(label)}" title="${labelText(labelKey,x[labelKey])}: ${money(v)}" style="height:${pct}%"></div>`}).join('');return `<div class="chart-card"><h3>${esc(title)}</h3><div class="step-chart">${rows||`<div class="empty">${t('noData')}</div>`}</div></div>`;}
async function loadStatus(){const s=await api('/api/status');baseStoreName=s.storeName||'HIPOS';updateHeaderTitle();}
async function loadSummary(){const s=await api('/api/summary?'+range());grandTotal.textContent=money(s.grandTotal);ticketCount.textContent=s.ticketCount||0;averageTicket.textContent=money(s.averageTicket);discountAmount.textContent=money(s.discountAmount);}
function renderOrders(data){return makeTable([{t:t('ticket')},{t:t('date')},{t:t('amount'),cls:'right'},{t:t('paymentsTitle')},{t:t('employee')}],(data||[]).map(x=>[`<a class="link" onclick="showOrder(${x.salesId})">${esc(x.salesNo)}</a>`,shortDate(x.salesDateTime),money(x.grandTotal),esc(x.paymentText||''),esc(x.userName||'')]),{id:'orders_'+currentPage});}
async function dashboard(){setTitle('dashboard',t('general'));const [payments,cats,products,orders,trend]=await Promise.all([api('/api/payments?'+range()),api('/api/categories?'+range()),api('/api/top-products?'+range()+'&take=10'),api('/api/orders?'+range()+'&take=10'),api('/api/trend?'+range())]);main.innerHTML=`<div class="chart-grid">${chartPie(t('chartPayments'),payments,'methodName','amount')}${chartBar(t('chartCategories'),cats,'categoryName','amount')}${chartVBar(t('chartProducts'),products,'productName','amount')}${chartStep(t('chartTrend'),trend,'saleDate','grandTotal')}</div><h3>${t('lastTickets')}</h3>${renderOrders(orders)}`;}
async function loadSales(){setTitle('sales',t('salesList'));main.innerHTML=renderOrders(await api('/api/orders?'+range()+'&take=500'));}
function pivot(rows,rowKey,colKey,valKey){const cols=[...new Set((rows||[]).map(x=>x[colKey]||t('noName')))];const map={};(rows||[]).forEach(x=>{const r=x[rowKey]||t('noName'),c=x[colKey]||t('noName');map[r]=map[r]||{};map[r][c]=(map[r][c]||0)+Number(x[valKey]||0);});return {cols, rows:Object.entries(map).map(([name,vals])=>({name,vals,total:Object.values(vals).reduce((a,b)=>a+b,0)})).sort((a,b)=>b.total-a.total)};}
async function loadPayments(){setTitle('payments',t('paySummary'));const x=await api('/api/payments-detail?'+range());const methods=x.methods||[], ep=x.employeePayments||[];const pv=pivot(ep,'userName','methodName','amount');const headers=[{t:t('employee')}].concat(pv.cols.map(c=>({t:c,cls:'right'}))).concat([{t:t('total'),cls:'right'}]);const rows=pv.rows.map(r=>[esc(r.name),...pv.cols.map(c=>money(r.vals[c]||0)),money(r.total)]);main.innerHTML=`<div class="chart-grid">${chartPie(t('chartPayments'),methods,'methodName','amount')}${chartBar(t('chartPayments'),methods,'methodName','amount')}</div><h3>${t('method')}</h3>${makeTable([{t:t('method')},{t:t('tickets'),cls:'right'},{t:t('amount'),cls:'right'}],methods.map(x=>[esc(x.methodName),x.ticketCount,money(x.amount)]))}<h3>${t('employee')} / ${t('method')}</h3>${makeTable(headers,rows)}`;}
async function loadTax(){setTitle('taxReq',t('taxHint'));const x=await api('/api/taxes?'+range());const s=x.summary||{};main.innerHTML=`<div class="cards tax-cards"><div class="card"><span>${t('subtotal')}</span><strong>${money(s.subtotal)}</strong></div><div class="card"><span>${t('taxAmount')}</span><strong>${money(s.taxTotal)}</strong></div><div class="card"><span>${t('reqAmount')}</span><strong>${money(s.reqTotal)}</strong></div><div class="card"><span>${t('total')}</span><strong>${money(s.grandTotal)}</strong></div></div><div class="chart-grid">${chartPie(t('taxAmount'),x.byRate||[],'taxRate','taxAmount')}${chartBar(t('reqAmount'),x.byRate||[],'reqRate','reqAmount')}</div>${makeTable([{t:t('taxRate'),cls:'right'},{t:t('reqRate'),cls:'right'},{t:t('tickets'),cls:'right'},{t:t('base'),cls:'right'},{t:t('taxAmount'),cls:'right'},{t:t('reqAmount'),cls:'right'},{t:t('total'),cls:'right'}],(x.byRate||[]).map(r=>[money(r.taxRate),money(r.reqRate),r.ticketCount,money(r.baseAmount),money(r.taxAmount),money(r.reqAmount),money(r.totalAmount)]))}`;}
function productCell(x){const img=`<img class="prod-img" onerror="this.style.display='none'" onclick="showProductImage(${x.productId},'${esc(x.productName)}')" src="/api/product-image/${x.productId}?v=1" loading="lazy">`;return `<div class="prod-cell">${img}<span>${esc(x.productName)}</span></div>`;}
async function loadProducts(){const data=await api('/api/top-products?'+range()+'&take=100');setTitle('products',t('topProducts'));main.innerHTML=`<div class="chart-grid">${chartVBar(t('chartProducts'),data,'productName','amount')}${chartBar(t('chartProducts'),data,'productName','amount')}</div>${makeTable([{t:t('product')},{t:t('quantity'),cls:'right'},{t:t('amount'),cls:'right'}],(data||[]).map(x=>[productCell(x),qty(x.qty),money(x.amount)]))}`;}
function sensitiveFilterBar(types, rows){
  const employees = [];
  const seenEmp = new Set();
  (rows||[]).forEach(x=>{
    const key = String(x.userID ?? '') || String(x.userName || '');
    const name = String(x.userName || '').trim();
    if(!key && !name) return;
    const v = key || name;
    if(seenEmp.has(v)) return;
    seenEmp.add(v);
    employees.push({value:v,name:name||v});
  });

  const empOpts = [`<option value="">${t('allEmployees')}</option>`]
    .concat(employees.map(e=>`<option value="${esc(e.value)}" ${sensitiveEmployee===e.value?'selected':''}>${esc(e.name)}</option>`));

  const selected = new Set(sensitiveTypes||[]);
  const typeOpts = (types||[]).map(x=>{
    const val = x.operationType || '';
    const text = (x.operationTitle || x.operationType || '') + (x.count!=null ? ` (${x.count})` : '');
    return `<option value="${esc(val)}" ${selected.has(val)?'selected':''}>${esc(text)}</option>`;
  });

  return `<div class="filterbar sensitive-filter">
    <label>${t('filterEmployee')}<select onchange="setSensitiveEmployee(this.value)">${empOpts.join('')}</select></label>
    <label>${t('filterOperation')}<select multiple size="4" onchange="setSensitiveTypesFromSelect(this)">${typeOpts.join('')}</select></label>
  </div>`;
}
function setSensitiveEmployee(v){sensitiveEmployee=v||'';loadCurrent();}
function setSensitiveTypesFromSelect(sel){sensitiveTypes=Array.from(sel.selectedOptions||[]).map(o=>o.value).filter(Boolean);loadCurrent();}
async function loadSensitive(){
  setTitle('sensitive',t('sensitiveHint'));
  const [types, rows]=await Promise.all([
    api('/api/sensitive-types?'+range()),
    api('/api/sensitive?'+range()+'&take=1000')
  ]);
  const selectedTypes = new Set(sensitiveTypes||[]);
  let filtered = rows || [];
  if(selectedTypes.size) filtered = filtered.filter(x=>selectedTypes.has(x.operationType || ''));
  if(sensitiveEmployee){
    filtered = filtered.filter(x=>{
      const key = String(x.userID ?? '') || String(x.userName || '');
      return key === sensitiveEmployee || String(x.userName || '') === sensitiveEmployee;
    });
  }
  lastSensitiveRows=filtered;
  main.innerHTML=sensitiveFilterBar(types, rows)+makeTable([{t:t('date')},{t:t('operation')},{t:t('user')},{t:t('computer')},{t:t('view')}],lastSensitiveRows.map((x,i)=>[shortDate(x.createTime),esc(x.operationTitle||x.operationType),esc(x.userName),esc(x.pcName),`<button class="mini-link" onclick="showSensitive(${i})">${t('view')}</button>`]));
}
function prettyJsonText(s){if(!s)return '';try{return JSON.stringify(JSON.parse(s),null,2);}catch{return s;}}
function jsonValueHtml(v){
  if(v===null||v===undefined||String(v)==='') return '';
  if(Array.isArray(v)){
    return `<div class="json-items">${v.map((it,idx)=>jsonItemReceiptHtml(it,idx)).join('')}</div>`;
  }
  if(typeof v==='object'){
    const rows=Object.entries(v).filter(([k,val])=>val!==null&&val!==undefined&&String(val)!=='').map(([k,val])=>`<div><b>${esc(k)}</b><span>${jsonValueHtml(val)}</span></div>`).join('');
    return rows?`<div class="json-grid inner">${rows}</div>`:'';
  }
  return esc(v);
}
function jsonItemReceiptHtml(it,idx){
  if(!it || typeof it!=='object' || Array.isArray(it)) return `<div class="json-item"><strong>#${idx+1}</strong>${jsonValueHtml(it)}</div>`;
  const pick=(...ks)=>{for(const k of ks){const hit=Object.keys(it).find(x=>x.toLowerCase()===k.toLowerCase());if(hit&&it[hit]!==null&&it[hit]!==undefined&&String(it[hit])!=='')return it[hit];}return ''};
  const name=pick('ProductName','productName','Name','name','Articulo','Artículo','ItemName') || ('#'+(idx+1));
  const code=pick('UserCode','userCode','Code','code','BarCode','Barcode','barcode');
  const qtyv=pick('Qty','qty','Quantity','quantity','Cant','Cantidad');
  const price=pick('UnitPrice','unitPrice','Price','price','Precio');
  const disc=pick('DiscountRate','discountRate','Discount','discount','Dto');
  const tax=pick('TaxRate','taxRate','Tax','tax','Iva');
  const total=pick('LineTotal','lineTotal','Total','total','Amount','amount','Importe');
  const used=new Set(['productname','name','articulo','artículo','itemname','usercode','code','barcode','barcode','qty','quantity','cant','cantidad','unitprice','price','precio','discountrate','discount','dto','taxrate','tax','iva','linetotal','total','amount','importe']);
  const rest=Object.entries(it).filter(([k,v])=>!used.has(k.toLowerCase())&&v!==null&&v!==undefined&&String(v)!=='');
  return `<div class="json-receipt-item"><div class="receipt-name">${esc(name)}</div><div class="receipt-meta"><span>${esc(code)}</span>${qtyv!==''?`<span>${qty(qtyv)}</span>`:''}${price!==''?`<span>${money(price)}</span>`:''}${disc!==''?`<span>${money(disc)}%</span>`:''}${tax!==''?`<span>${money(tax)}%</span>`:''}${total!==''?`<strong>${money(total)}</strong>`:''}</div>${rest.length?`<div class="json-grid inner">${rest.map(([k,v])=>`<div><b>${esc(k)}</b><span>${jsonValueHtml(v)}</span></div>`).join('')}</div>`:''}</div>`;
}
function prettyJsonHtml(s){
  if(!s) return '';
  try{
    const obj=JSON.parse(s);
    if(Array.isArray(obj)) return `<div class="json-items">${obj.map(jsonItemReceiptHtml).join('')}</div>`;
    if(obj && typeof obj==='object'){
      const entries=Object.entries(obj).filter(([k,v])=>v!==null&&v!==undefined&&String(v)!=='');
      const items=entries.filter(([k])=>String(k).toLowerCase()==='items');
      const others=entries.filter(([k])=>String(k).toLowerCase()!=='items');
      const rows=others.map(([k,v])=>`<div><b>${esc(k)}</b><span>${jsonValueHtml(v)}</span></div>`).join('');
      const itemHtml=items.map(([k,v])=>`<h3>${esc(k)}</h3>${Array.isArray(v)?`<div class="json-items">${v.map(jsonItemReceiptHtml).join('')}</div>`:jsonValueHtml(v)}`).join('');
      return `${rows?`<div class="json-grid">${rows}</div>`:''}${itemHtml}` || `<pre class="json-box">${esc(JSON.stringify(obj,null,2))}</pre>`;
    }
    return jsonValueHtml(obj) || `<pre class="json-box">${esc(JSON.stringify(obj,null,2))}</pre>`;
  }catch{return `<pre class="json-box">${esc(s)}</pre>`;}
}
function updateOverlayInsets(){
  const root=document.documentElement;
  const header=document.querySelector('.app-header');
  const bottom=document.querySelector('.bottom-nav');
  let top=0, bottomHeight=0;
  if(header){
    const cs=getComputedStyle(header);
    if(cs.display!=='none' && cs.visibility!=='hidden') top=Math.max(0,Math.ceil(header.getBoundingClientRect().height));
  }
  if(bottom){
    const cs=getComputedStyle(bottom);
    if(cs.display!=='none' && cs.visibility!=='hidden') bottomHeight=Math.max(0,Math.ceil(bottom.getBoundingClientRect().height));
  }
  root.style.setProperty('--overlay-top',top+'px');
  root.style.setProperty('--overlay-bottom',bottomHeight+'px');
}
function openModal(cls=''){modal.className='modal '+(cls||'');modal.classList.remove('hidden');document.body.classList.add('modal-open');}
function showSensitive(i){
  const x=lastSensitiveRows[i]; if(!x)return;
  modalTitle.textContent=(x.operationTitle||x.operationType||t('sensitive'))+' · '+shortDate(x.createTime);
  const fields=[
    [t('operationId'),x.logId],[t('operationTypeLabel'),x.operationType],[t('date'),shortDate(x.createTime)],[t('user'),x.userName],[t('computer'),x.pcName],[t('deviceId'),x.deviceID],
    [t('client'),x.clientName],[t('ticket'),x.salesNo||x.holdNo],[t('product'),x.productName],[t('barcode'),x.barcode],[t('code'),x.userCode],
    [t('quantity'),x.qty!=null?qty(x.qty):''],[t('before'),x.oldValue || (x.amountBefore!=null?money(x.amountBefore):'')],[t('after'),x.newValue || (x.amountAfter!=null?money(x.amountAfter):'')],
    [t('diff'),x.differenceAmount!=null?money(x.differenceAmount):''],[t('total'),x.cartTotal!=null?money(x.cartTotal):''],[t('note'),x.remark]
  ].filter(r=>r[1]!==undefined&&r[1]!==null&&String(r[1])!=='');
  const normalFields=fields.filter(r=>r[0]!==t('deviceId')); const deviceField=fields.find(r=>r[0]===t('deviceId')); modalBody.innerHTML=`<div class="detail-grid">${normalFields.map(r=>`<div><b>${esc(r[0])}</b><span>${esc(r[1])}</span></div>`).join('')}${deviceField?`<div class="full"><b>${esc(deviceField[0])}</b><span>${esc(deviceField[1])}</span></div>`:''}</div>${x.detailJson?`<h3>${t('detailJson')}</h3>${prettyJsonHtml(x.detailJson)}`:''}`;
  openModal('sensitive-modal');
}
function searchBox(placeholder){return `<div class="inline-search"><input id="pageSearch" autocomplete="off" placeholder="${esc(placeholder)}" value="${esc(currentQuery)}"><button onclick="doPageSearch()">${t('search')}</button><button onclick="clearPageSearch()">${t('clear')}</button></div>`;}
function doPageSearch(){currentQuery=(document.getElementById('pageSearch')?.value||'').trim();loadCurrent();}
function clearPageSearch(){currentQuery='';loadCurrent();}
function inventoryFilters(cats,sups,taxes){
  const catOpts=[`<option value="">${t('allCategories')}</option>`].concat((cats||[]).map(c=>`<option value="${c.id}" ${String(c.id)===String(inventoryCategory)?'selected':''}>${esc(c.name)}</option>`)).join('');
  const supOpts=[`<option value="">${t('allSuppliers')}</option>`].concat((sups||[]).map(c=>`<option value="${c.id}" ${String(c.id)===String(inventorySupplier)?'selected':''}>${esc(c.name)}</option>`)).join('');
  const taxOpts=[`<option value="">${t('allTaxes')}</option>`].concat((taxes||[]).map(c=>`<option value="${c.taxRate}" ${String(c.taxRate)===String(inventoryTax)?'selected':''}>${money(c.taxRate)}%</option>`)).join('');
  return `<div class="inline-search inventory-filter"><input id="pageSearch" autocomplete="off" placeholder="${esc(t('scanOrSearch'))}" value="${esc(currentQuery)}"><select id="invCategory" onchange="doInventoryFilter()">${catOpts}</select><select id="invSupplier" onchange="doInventoryFilter()">${supOpts}</select><select id="invTax" onchange="doInventoryFilter()">${taxOpts}</select><select id="invStock" onchange="doInventoryFilter()"><option value="all" ${inventoryStockMode==='all'?'selected':''}>${t('allStock')}</option><option value="ge0" ${inventoryStockMode==='ge0'?'selected':''}>${t('stockGe0')}</option><option value="eq0" ${inventoryStockMode==='eq0'?'selected':''}>${t('stockEq0')}</option><option value="lt0" ${inventoryStockMode==='lt0'?'selected':''}>${t('stockLt0')}</option><option value="gt0" ${inventoryStockMode==='gt0'?'selected':''}>${t('stockGt0')}</option></select><button onclick="doInventoryFilter()">${t('search')}</button><button onclick="clearInventoryFilter()">${t('clear')}</button></div>`;
}
function doInventoryFilter(){currentQuery=(document.getElementById('pageSearch')?.value||'').trim();inventoryCategory=document.getElementById('invCategory')?.value||'';inventorySupplier=document.getElementById('invSupplier')?.value||'';inventoryTax=document.getElementById('invTax')?.value||'';inventoryStockMode=document.getElementById('invStock')?.value||'all';loadCurrent();}
function clearInventoryFilter(){currentQuery='';inventoryCategory='';inventorySupplier='';inventoryTax='';inventoryStockMode='all';loadCurrent();}
async function loadInventory(){setTitle('inventory',t('stockHint'));const [cats,sups,taxes,data]=await Promise.all([api('/api/inventory/categories'),api('/api/inventory/suppliers'),api('/api/inventory/taxes'),api('/api/inventory?'+inventoryParam()+'&take=1000')]);main.innerHTML=inventoryFilters(cats,sups,taxes)+makeTable([{t:t('barcode')+' / '+t('code')},{t:t('product')+' / '+t('category')},{t:t('supplier')+' / '+t('cost')},{t:t('taxRate')},{t:t('stock'),cls:'right'},{t:t('salePrice'),cls:'right'}],data.map(x=>[`<div class="stack-cell"><strong>${esc(x.barcode)}</strong><small>${esc(x.userCode)}</small></div>`,`<div class="stack-cell"><strong>${esc(x.productName)}</strong><small>${esc(x.categoryName)}</small></div>`,`<div class="stack-cell"><strong>${money(x.pricePurchase)}</strong><small>${esc(x.supplierName)}</small></div>`,money(x.taxRate)+'%',qty(x.unitsInStock),money(x.unitPrice)]));setTimeout(()=>document.getElementById('pageSearch')?.focus(),50);}
function copyText(v){if(!v)return;if(navigator.clipboard)navigator.clipboard.writeText(v); alert(t('copied'));}
function clientCell(x){const phone=String(x.telefono||'').trim();return `<div class="client-cell"><strong>${esc(x.nombreES)}</strong>${phone?`<a href="tel:${esc(phone)}">${esc(phone)}</a>`:''}</div>`;}
function emailCell(x){const email=String(x.email||'').trim();return email?`<button class="email-copy wide" onclick="copyText('${esc(email)}')" title="${esc(email)}">✉</button>`:'';}
function renderClients(data){return makeTable([{t:t('id')},{t:t('client')},{t:t('email')},{t:t('vip')},{t:t('points'),cls:'right'},{t:t('balance'),cls:'right'}],(data||[]).map(x=>[x.clientId,clientCell(x),emailCell(x),esc(x.tarjetaSocioID),qty(x.puntos),money(x.balance)]));}
async function loadClients(){setTitle('clients',t('clientHint'));main.innerHTML=searchBox(t('clientHint'))+renderClients(await api('/api/clients?'+queryParam()+'&take=1000'));setTimeout(()=>document.getElementById('pageSearch')?.focus(),50);}
async function loadStaff(){setTitle('staff',t('staffHint'));const data=await api('/api/staff');if(data.length&&data[0].source==='users')main.innerHTML=makeTable([{t:t('id')},{t:t('user')},{t:t('name')},{t:t('role')},{t:t('phone')},{t:t('email')},{t:t('active')}],data.map(x=>[x.id,esc(x.loginName),esc(x.name),esc(x.role),esc(x.phone),esc(x.email),x.isActive?t('yes'):t('no')]));else main.innerHTML=makeTable([{t:t('id')},{t:t('name')},{t:t('dni')},{t:t('phone')},{t:t('birth')},{t:t('start')},{t:t('status')}],data.map(x=>[x.id,esc(x.name),esc(x.dni),esc(x.phone),shortDate(x.birthDate),shortDate(x.startWorkDate),x.disabled?t('down'):t('active')]));}
async function loadDevices(){setTitle('devices',t('deviceHint'));const x=await api('/api/devices?'+range());const summary=x.summary||[],detail=x.detail||[];main.innerHTML=`<div class="chart-grid">${chartPie(t('devices'),summary,'deviceName','total')}${chartBar(t('devices'),summary,'deviceName','total')}</div>${makeTable([{t:t('device')},{t:t('tickets'),cls:'right'},{t:t('totalSales'),cls:'right'}],summary.map(x=>[esc(x.deviceName||t('noName')),x.tickets,money(x.total)]))}<h3>${t('device')} / ${t('employee')} / ${t('method')}</h3>${makeTable([{t:t('device')},{t:t('employee')},{t:t('method')},{t:t('tickets'),cls:'right'},{t:t('amount'),cls:'right'}],detail.map(x=>[esc(x.deviceName||t('noName')),esc(x.userName||t('noName')),esc(x.methodName),x.tickets,money(x.amount)]))}`;}
async function loadAnalysis(){setTitle('analysis',t('analysisHint'));const x=await api('/api/analysis?'+range());main.innerHTML=`<div class="chart-grid">${chartPie(t('chartUsers'),x.users,'userName','total')}${chartVBar(t('chartUsers'),x.users,'userName','total')}${chartStep(t('chartHours'),x.hours,'saleHour','total')}</div><h3>${t('chartUsers')}</h3>${makeTable([{t:t('user')},{t:t('tickets'),cls:'right'},{t:t('totalSales'),cls:'right'},{t:t('discount'),cls:'right'}],x.users.map(u=>[esc(u.userName),u.tickets,money(u.total),money(u.discount)]))}<h3>${t('chartHours')}</h3>${makeTable([{t:t('hour')},{t:t('tickets'),cls:'right'},{t:t('totalSales'),cls:'right'}],x.hours.map(h=>[h.saleHour+':00',h.tickets,money(h.total)]))}`;}
async function loadSuppliers(){
  setTitle('supplier',t('supplier'));
  const data=await api('/api/suppliers');
  supplierNames={}; (data||[]).forEach(x=>supplierNames[x.supplierID]=x.supplierName);
  main.innerHTML=makeTable([{t:t('id')},{t:t('supplier')},{t:t('phone')},{t:t('productsCount'),cls:'right'},{t:t('stock'),cls:'right'}],(data||[]).map(x=>[x.supplierID,`<button type="button" class="link link-btn" onclick="showSupplierProducts(${x.supplierID})">${esc(x.supplierName)}</button>${x.contactName?`<br><small>${esc(x.contactName)}</small>`:''}`,esc(x.phone),x.productCount,qty(x.unitsInStock)]));
}
async function showSupplierProducts(id){
  const name=supplierNames[id]||t('supplier');
  setTitle('supplier',name);
  const rows=await api('/api/supplier/'+id+'/products?'+range()+'&take=500');
  main.innerHTML=`<div class="quick-buttons"><button onclick="loadSuppliers()">‹ ${t('back')}</button></div><div class="chart-grid">${chartBar(t('supplierProductRank'),rows,'productName','qty',qty)}${chartVBar(t('supplierProductRank'),rows,'productName','amount')}</div>${makeTable([{t:t('product')},{t:t('barcode')+' / '+t('code')},{t:t('category')},{t:t('quantity'),cls:'right'},{t:t('tickets'),cls:'right'},{t:t('amount'),cls:'right'}],(rows||[]).map(x=>[productCell(x),`<div class="stack-cell"><strong>${esc(x.barcode)}</strong><small>${esc(x.userCode)}</small></div>`,esc(x.categoryName),qty(x.qty),x.tickets,money(x.amount)]))}`;
}
async function loadPurchases(){
  setTitle('purchases',t('purchaseOrders'));
  const x=await api('/api/purchases?'+range()+'&take=1000');
  const headers=x.headers||[];
  main.innerHTML=makeTable([{t:t('ticket')},{t:t('date')},{t:t('supplier')},{t:t('quantity'),cls:'right'},{t:t('amount'),cls:'right'},{t:t('user')}],headers.map(x=>[`<button type="button" class="link link-btn" onclick="showPurchase(${x.stockInID})">${esc(x.docNo||('#'+x.stockInID))}</button>`,shortDate(x.createTime),esc(x.supplierName),qty(x.totalQty),money(x.totalAmount),esc(x.createdBy)]));
}
async function showPurchase(id){
  try{
    const x=await api('/api/purchase/'+id); const h=x.header||{};
    modalTitle.textContent=`${t('purchaseOrders')} ${esc(h.docNo||('#'+h.stockInID))}`;
    const lines=(x.details||[]).map(d=>`<div class="receipt-item"><div class="receipt-name">${esc(d.productName||d.barcode||'')}</div><div class="receipt-meta"><span>${esc(d.barcode||d.userCode||'')}</span><span>${qty(d.quantity)}</span><span>${money(d.pricePurchase)}</span><strong>${money(d.amount)}</strong></div></div>`).join('');
    modalBody.innerHTML=`<div class="receipt"><div class="receipt-top"><h3>${esc(h.docNo||('#'+h.stockInID))}</h3><p>${shortDate(h.createTime)}</p><p>${esc(h.supplierName||'-')} · ${esc(h.createdBy||'-')}</p></div>${lines||`<div class="empty">${t('noData')}</div>`}<div class="receipt-total"><div><span>${t('quantity')}</span><strong>${qty(h.totalQty)}</strong></div><div><span>${t('total')}</span><strong>${money(h.totalAmount)}</strong></div></div></div>`;
    openModal('receipt-modal');
  }catch(e){alert(t('loadError')+e.message);}
}
function lineDiscount(d){return Number(d.qty||0)*Number(d.unitPrice||0)*Number(d.discountRate||0)/100;}
function receiptLines(details){return (details||[]).map(d=>`<div class="receipt-item"><div class="receipt-name">${esc(d.productName)}${d.condimentText?` <small>${esc(d.condimentText)}</small>`:''}</div><div class="receipt-meta"><span>${esc(d.userCode||d.barcode||'')}</span><span>${qty(d.qty)}</span><span>${money(d.unitPrice)}</span><span>${money(d.discountRate)}%</span><span>${money(d.taxRate)}%</span><strong>${money(d.lineTotal)}</strong></div></div>`).join('');}
async function showOrder(id){const x=await api('/api/order/'+id);modalTitle.textContent=`${t('receipt')} ${x.header.salesNo}`;const h=x.header;const detailDiscount=(x.details||[]).reduce((a,d)=>a+lineDiscount(d),0);modalBody.innerHTML=`<div class="receipt"><div class="receipt-top"><h3>${esc(h.salesNo)}</h3><p>${shortDate(h.salesDateTime)}</p><p>${esc(h.clientName||'-')} · ${esc(h.userName||'-')}</p></div>${receiptLines(x.details)}<div class="receipt-total"><div><span>${t('subtotal')}</span><strong>${money(h.subtotal)}</strong></div><div><span>${t('discount')}</span><strong>${money(detailDiscount)}</strong></div><div><span>${t('tax')}</span><strong>${money(h.taxTotal)}</strong></div><div><span>${t('total')}</span><strong>${money(h.grandTotal)}</strong></div></div><h3>${t('paymentsTitle')}</h3>${makeTable([{t:t('method')},{t:t('hour')},{t:t('amount'),cls:'right'},{t:t('change'),cls:'right'}],(x.payments||[]).map(p=>[esc(p.methodName),shortDate(p.payTime),money(p.amount),money(p.change)]))}</div>`;openModal('receipt-modal');}
function showProductImage(id,name){modalTitle.textContent=name||t('product');modalBody.innerHTML=`<div class="image-preview"><img src="/api/product-image/${id}"></div>`;openModal('image-modal');}
function hideModal(){modal.className='modal hidden';document.body.classList.remove('modal-open');}
function closeModal(e){if(e.target.id==='modal')hideModal();}
async function logout(){await fetch('/api/logout',{method:'POST'});location.href='/login.html';}
function exportExcel(){const map={dashboard:'sales',sales:'sales',payments:'payments',products:'products',sensitive:'sensitive',inventory:'inventory',clients:'clients',devices:'devices',tax:'categories',analysis:'sales'};location.href='/api/export/xls?type='+(map[currentPage]||'sales')+'&'+range()+'&'+queryParam();}
function printPdf(){window.print();}
function updateTopSummaryVisibility(){
  const hidden = pagesWithoutTopSummary.has(currentPage);
  if (cards) cards.style.display = hidden ? 'none' : '';
}
async function loadCurrent(){
  try{
    updateTopSummaryVisibility();
    if(!pagesWithoutTopSummary.has(currentPage)) await loadSummary();
    const loaderMap={dashboard,sales:loadSales,payments:loadPayments,products:loadProducts,sensitive:loadSensitive,inventory:loadInventory,clients:loadClients,staff:loadStaff,devices:loadDevices,analysis:loadAnalysis,tax:loadTax,purchases:loadPurchases,suppliers:loadSuppliers};
    const loader=loaderMap[currentPage]||dashboard;
    await loader();
  }catch(e){console.error(e);main.innerHTML='<div class="error">'+t('loadError')+esc(e.message)+'</div>';}
}
function setActiveNav(page){document.querySelectorAll('[data-page]').forEach(b=>b.classList.toggle('active',b.dataset.page===page));}
function goPage(page){currentPage=page; if(page!=='inventory' && page!=='clients') currentQuery=''; updateTopSummaryVisibility(); setActiveNav(page); hideMore(); loadCurrent();}
function showMore(){moreSheet.classList.remove('hidden');}
function hideMore(){moreSheet.classList.add('hidden');}
function closeMore(e){if(e.target.id==='moreSheet')hideMore();}
function bindMainUiEvents(){
  const refreshReport=document.getElementById('refreshReport');
  if(refreshReport) refreshReport.addEventListener('click',e=>{e.preventDefault();loadCurrent();});

  document.querySelectorAll('.bottom-nav [data-page]').forEach(btn=>{
    btn.addEventListener('click',e=>{
      e.preventDefault();
      e.stopPropagation();
      goPage(btn.dataset.page);
    });
  });
  document.querySelectorAll('.more-grid [data-page]').forEach(btn=>{
    btn.addEventListener('click',e=>{
      e.preventDefault();
      e.stopPropagation();
      goPage(btn.dataset.page);
    });
  });
  document.querySelectorAll('[data-action]').forEach(btn=>btn.addEventListener('click',e=>{
    e.preventDefault();
    const action=btn.dataset.action;
    if(action==='excel'){exportExcel();hideMore();return;}
    if(action==='pdf'){printPdf();hideMore();return;}
    if(action==='more'){showMore();return;}
  }));
}

// WebView 强制点击入口：直接读取控件并更新，避免某些 APP WebView 不触发 addEventListener。
function appSelectPeriod(ev, type){
  if(ev){ ev.preventDefault(); ev.stopPropagation(); }
  const f=document.getElementById('from');
  const tbox=document.getElementById('to');
  if(!f || !tbox) return false;
  activeQuickDate=type || 'day';
  const r=periodRange(activeQuickDate,new Date());
  f.value=formatDateLocal(r.from);
  tbox.value=formatDateLocal(r.to);
  setQuickDateActive(activeQuickDate);
  window.setTimeout(function(){ loadCurrent(); },0);
  return false;
}
function appShiftPeriod(ev, delta){
  if(ev){ ev.preventDefault(); ev.stopPropagation(); }
  const f=document.getElementById('from');
  const tbox=document.getElementById('to');
  if(!f || !tbox) return false;
  let anchor=parseDateLocal(f.value);
  if(activeQuickDate==='day') anchor.setDate(anchor.getDate()+delta);
  else if(activeQuickDate==='week') anchor.setDate(anchor.getDate()+delta*7);
  else if(activeQuickDate==='month') anchor.setMonth(anchor.getMonth()+delta);
  else if(activeQuickDate==='quarter') anchor.setMonth(anchor.getMonth()+delta*3);
  else if(activeQuickDate==='year') anchor.setFullYear(anchor.getFullYear()+delta);
  const r=periodRange(activeQuickDate,anchor);
  f.value=formatDateLocal(r.from);
  tbox.value=formatDateLocal(r.to);
  setQuickDateActive(activeQuickDate);
  window.setTimeout(function(){ loadCurrent(); },0);
  return false;
}
function appGoPage(ev,page){
  if(ev){ ev.preventDefault(); ev.stopPropagation(); }
  goPage(page);
  return false;
}

// 暴露给内联 onclick，兼容 iOS/Android WebView。
Object.assign(window,{setQuickDateActive,selectReportPeriod,shiftReportPeriod,appSelectPeriod,appShiftPeriod,appGoPage,loadCurrent,goPage,showMore,hideMore,setLang,logout,exportExcel,printPdf,closeMore,hideModal,closeModal});

// 事件委托作为第二重兜底；内联 onclick 已经是主路径。
document.addEventListener('click',function(e){
  const btn=e.target && e.target.closest ? e.target.closest('button') : null;
  if(!btn) return;
  if(btn.id==='refreshReport'){e.preventDefault();loadCurrent();return;}
  if(btn.matches('.bottom-nav [data-page]')){e.preventDefault();goPage(btn.dataset.page);return;}
  if(btn.matches('.bottom-nav [data-action="more"]')){e.preventDefault();showMore();return;}
},true);

document.addEventListener('keydown',e=>{if(e.key==='Enter' && e.target && e.target.id==='pageSearch') doPageSearch();});
window.addEventListener('wheel',e=>{if(e.ctrlKey)e.preventDefault();},{passive:false});window.addEventListener('gesturestart',e=>e.preventDefault(),{passive:false});window.addEventListener('gesturechange',e=>e.preventDefault(),{passive:false});
window.addEventListener('resize',()=>{updateHeaderTitle();updateOverlayInsets();});
(async()=>{updateOverlayInsets();bindMainUiEvents();setQuickDateActive('day');applyLang();await loadStatus();setActiveNav(currentPage);await loadCurrent();})();
