<?php
declare(strict_types=1);
require __DIR__.'/config.php';
ini_set('session.use_strict_mode','1');
ini_set('session.cookie_httponly','1');
ini_set('session.cookie_samesite','Lax');
if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ini_set('session.cookie_secure','1');
session_start();
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: same-origin');
header('Cache-Control: no-store');
if (!isset($_SESSION['csrf'])) $_SESSION['csrf']=bin2hex(random_bytes(24));
function db(): PDO {static $pdo; if(!$pdo){$pdo=new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4',DB_USER,DB_PASS,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC,PDO::ATTR_EMULATE_PREPARES=>false]);}return $pdo;}
function reply(array $data,int $status=200): never {http_response_code($status);header('Content-Type: application/json; charset=utf-8');echo json_encode($data,JSON_UNESCAPED_UNICODE);exit;}
function countAhead(int $id): int {$s=db()->prepare("SELECT COUNT(*) FROM fila_atendimentos WHERE id < ? AND status IN ('aguardando','atendendo')");$s->execute([$id]);return (int)$s->fetchColumn();}
function phone(string $v): string {return preg_replace('/\D/','',$v);}
if(isset($_GET['api'])) {
 try {
 $api=$_GET['api'];
 if($_SERVER['REQUEST_METHOD']==='POST'){
  if(!hash_equals($_SESSION['csrf'],(string)($_POST['csrf']??''))) reply(['erro'=>'Sessão expirada. Atualize a página.'],403);
  if($api==='register'){
   $name=trim((string)($_POST['nome']??''));$tel=phone((string)($_POST['telefone']??''));
   if(mb_strlen($name)<2||mb_strlen($name)>150||!preg_match('/^\d{10,11}$/',$tel))reply(['erro'=>'Informe nome e telefone com DDD.'],422);
   $q=db()->prepare('INSERT INTO fila_atendimentos (nome,telefone) VALUES (?,?)');
   try{$q->execute([$name,$tel]);}catch(PDOException $e){if($e->getCode()==='23000')reply(['erro'=>'Este telefone já está cadastrado. Use Consultar fila.'],409);throw $e;}
   $id=(int)db()->lastInsertId();reply(['numero'=>$id,'frente'=>countAhead($id)]);
  }
  if($api==='login'){
   if(!isset($_SESSION['login_attempts']))$_SESSION['login_attempts']=0;
   if($_SESSION['login_attempts']>=10)reply(['erro'=>'Muitas tentativas nesta sessão. Feche o navegador e tente mais tarde.'],429);
   if(!hash_equals(ADMIN_PASSWORD,(string)($_POST['senha']??''))){$_SESSION['login_attempts']++;usleep(250000);reply(['erro'=>'Senha incorreta.'],403);}
   session_regenerate_id(true);$_SESSION['admin']=true;$_SESSION['login_attempts']=0;reply(['ok'=>true]);
  }
  if($api==='logout'){unset($_SESSION['admin']);reply(['ok'=>true]);}
  if($api==='update'){
   if(empty($_SESSION['admin']))reply(['erro'=>'Acesso restrito.'],403);
   $id=(int)($_POST['numero']??0);$status=(string)($_POST['status']??'');
   if($id<1||!in_array($status,['atendendo','concluido','cancelado'],true))reply(['erro'=>'Dados inválidos.'],422);
   $q=db()->prepare("UPDATE fila_atendimentos SET status=? WHERE id=? AND status IN ('aguardando','atendendo')");$q->execute([$status,$id]);reply(['ok'=>true]);
  }
 }
 if($api==='position'){
  $id=(int)($_GET['numero']??0);$tel=phone((string)($_GET['telefone']??''));
  if($id<1||!preg_match('/^\d{10,11}$/',$tel))reply(['erro'=>'Informe seu número e telefone.'],422);
  $q=db()->prepare('SELECT id,status FROM fila_atendimentos WHERE id=? AND telefone=?');$q->execute([$id,$tel]);$r=$q->fetch();if(!$r)reply(['erro'=>'Cadastro não encontrado.'],404);
  reply(['numero'=>(int)$r['id'],'status'=>$r['status'],'frente'=>countAhead($id)]);
 }
 if($api==='list'){
  if(empty($_SESSION['admin']))reply(['erro'=>'Acesso restrito.'],403);
  $rows=db()->query('SELECT id,nome,telefone,status,criado_em FROM fila_atendimentos ORDER BY id ASC')->fetchAll();reply(['registros'=>$rows]);
 }
 reply(['erro'=>'Operação não encontrada.'],404);
 }catch(Throwable $e){error_log((string)$e);reply(['erro'=>'Erro no servidor. Verifique a configuração do banco.'],500);}
}
$csrf=htmlspecialchars($_SESSION['csrf'],ENT_QUOTES,'UTF-8');
?>
<!doctype html><html lang="pt-BR"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Fila Online | Leandro do Stilo</title><style>
:root{font-family:system-ui,Arial,sans-serif;color:#f7f7f7;background:#111}*{box-sizing:border-box}body{margin:0}.wrap{max-width:680px;margin:0 auto;padding:20px}header{background:linear-gradient(120deg,#1a1a1a,#5c0b0b);padding:30px 15px;text-align:center;border-bottom:3px solid #e22}h1{margin:0;font-size:26px}header p{color:#ddd}nav{display:flex;gap:8px;margin:20px 0}button{background:#e22;color:#fff;border:0;border-radius:10px;padding:13px 17px;font-weight:700;cursor:pointer}nav button{flex:1;background:#292929}nav button.active{background:#d22}.card{background:#202020;border:1px solid #3a3a3a;border-radius:14px;padding:20px;margin-bottom:15px}input{display:block;width:100%;margin:8px 0 15px;padding:13px;background:#111;border:1px solid #666;border-radius:9px;color:#fff;font-size:16px}label{font-size:14px;color:#ddd}.muted{color:#aaa}.hidden{display:none}.number{font-size:50px;font-weight:800;color:#ff4242;margin:8px 0}.item{padding:13px 0;border-bottom:1px solid #444;display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}.item button{padding:9px;font-size:12px}.ok{color:#76e6a0}.error{color:#ff9c9c}#msg{margin:15px 0}small{color:#bbb}
</style></head><body><header><h1>🚗 LEANDRO DO STILO</h1><p>Fila de atendimento online</p></header><main class="wrap"><nav><button class="active" onclick="tab('cadastro',this)">Cadastro</button><button onclick="tab('consulta',this)">Minha fila</button><button onclick="tab('admin',this)">Administração</button></nav><div id="msg" role="status"></div>
<section id="cadastro" class="card"><h2>Entrar na fila</h2><p class="muted">Cadastre-se e receba seu número de atendimento.</p><form onsubmit="send(event,'register')"><input type="hidden" name="csrf" value="<?=$csrf?>"><label>Nome completo<input name="nome" maxlength="150" required></label><label>WhatsApp com DDD<input name="telefone" type="tel" maxlength="20" required placeholder="(11) 99999-9999"></label><button>Gerar meu número</button></form><div id="ticket"></div></section>
<section id="consulta" class="card hidden"><h2>Consultar minha posição</h2><label>Número de atendimento<input id="num" type="number" min="1" placeholder="Ex.: 15"></label><label>WhatsApp cadastrado<input id="tel" type="tel" placeholder="(11) 99999-9999"></label><button onclick="position()">Consultar fila</button><div id="position"></div><small>Enquanto esta tela estiver aberta, a posição será atualizada a cada 10 segundos.</small></section>
<section id="admin" class="card hidden"><h2>Painel administrativo</h2><div id="login"><form onsubmit="send(event,'login')"><input type="hidden" name="csrf" value="<?=$csrf?>"><label>Senha administrativa<input name="senha" type="password" required></label><button>Entrar</button></form></div><div id="dashboard" class="hidden"><p id="stats"></p><button onclick="loadList()">Atualizar</button> <button onclick="exportCSV()">Exportar CSV</button> <button onclick="send(null,'logout')">Sair</button><div id="items"></div></div></section><p class="muted"><small>Seus dados serão usados apenas para organização da fila de atendimento. Não compartilhe seu telefone ou senha com terceiros.</small></p></main>
<script>
const csrf=<?=json_encode($_SESSION['csrf'])?>;let records=[],watch=null;const el=id=>document.getElementById(id);const esc=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));function message(s,error=false){el('msg').className=error?'error':'ok';el('msg').textContent=s}function tab(id,btn){document.querySelectorAll('main section').forEach(x=>x.classList.add('hidden'));el(id).classList.remove('hidden');document.querySelectorAll('nav button').forEach(x=>x.classList.remove('active'));btn.classList.add('active');if(id==='admin')loadList()}
async function api(name,data){const opts=data?{method:'POST',body:data}:{};const r=await fetch('?api='+name,opts);const j=await r.json();if(!r.ok)throw Error(j.erro||'Erro');return j}
async function send(e,name){if(e)e.preventDefault();let f=e?new FormData(e.target):new FormData();if(!e)f.set('csrf',csrf);try{const j=await api(name,f);message('Operação realizada!');if(name==='register'){el('ticket').innerHTML='<p>Seu número:</p><div class="number">'+String(j.numero).padStart(3,'0')+'</div><p>'+j.frente+' pessoa(s) na sua frente.</p><p>Guarde seu número e telefone para consultar a fila.</p>';el('num').value=j.numero;el('tel').value=f.get('telefone');e.target.reset()}if(name==='login'){el('login').classList.add('hidden');el('dashboard').classList.remove('hidden');loadList()}if(name==='logout'){el('login').classList.remove('hidden');el('dashboard').classList.add('hidden')}}catch(x){message(x.message,true)}}
async function position(){const n=el('num').value,t=el('tel').value;if(!n||!t)return;try{const j=await api('position&numero='+encodeURIComponent(n)+'&telefone='+encodeURIComponent(t));el('position').innerHTML='<div class="number">'+j.frente+'</div><p>Pessoa(s) na sua frente</p><p>Situação: <b>'+esc(j.status)+'</b></p>'}catch(x){el('position').textContent=x.message}}
setInterval(()=>{if(!el('consulta').classList.contains('hidden'))position();if(!el('dashboard').classList.contains('hidden'))loadList()},10000);
async function loadList(){try{const j=await api('list');records=j.registros;el('login').classList.add('hidden');el('dashboard').classList.remove('hidden');const pend=records.filter(x=>['aguardando','atendendo'].includes(x.status));el('stats').textContent=`Total: ${records.length} | Na fila: ${pend.length} | Concluídos: ${records.filter(x=>x.status==='concluido').length}`;el('items').innerHTML=records.map(x=>'<div class="item"><span><b>#'+String(x.id).padStart(3,'0')+' '+esc(x.nome)+'</b><br><small>'+esc(x.telefone)+' · '+esc(x.status)+'</small></span>'+(['aguardando','atendendo'].includes(x.status)?'<span><button onclick="update('+x.id+',\'atendendo\')">Chamar</button> <button onclick="update('+x.id+',\'concluido\')">Concluir</button> <button onclick="update('+x.id+',\'cancelado\')">Cancelar</button></span>':'')+'</div>').join('')}catch(x){if(!el('dashboard').classList.contains('hidden'))message(x.message,true)}}
async function update(id,status){if(status==='cancelado'&&!confirm('Cancelar este atendimento?'))return;const f=new FormData();f.set('csrf',csrf);f.set('numero',id);f.set('status',status);try{await api('update',f);await loadList()}catch(x){message(x.message,true)}}
function exportCSV(){const rows=[['Número','Nome','Telefone','Status','Criado em'],...records.map(x=>[x.id,x.nome,x.telefone,x.status,x.criado_em])];const csv='\ufeff'+rows.map(row=>row.map(v=>'"'+String(v).replace(/"/g,'""')+'"').join(';')).join('\r\n');const a=document.createElement('a');a.href=URL.createObjectURL(new Blob([csv],{type:'text/csv;charset=utf-8'}));a.download='fila-leandro-do-stilo.csv';a.click();URL.revokeObjectURL(a.href)}
</script></body></html>
