<?php
require_once __DIR__ . "/../config.php";

/* ===== Zona horaria consistente ===== */
date_default_timezone_set('America/Panama');

/* ===== UTF-8 seguro ===== */
mb_internal_encoding('UTF-8');
ini_set('default_charset','UTF-8');
if (!headers_sent()) {
  header('Content-Type: text/html; charset=UTF-8');
}

/* ======================== PDO / UTIL ======================== */
function pdo_m() {
  static $pdo=null;
  if($pdo===null){
    $opts = [
      PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
      PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
      PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci",
    ];
    $pdo = new PDO(DB_DSN, DB_USER, DB_PASS, $opts);
    $pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci");
  }
  return $pdo;
}
function e($s){ return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }

/* ======================== CSS común ======================== */
function print_common_css(){
echo '<style>
:root{--bd:#e5e5e5}
body{font-family:Arial,Helvetica,sans-serif;margin:0;padding:24px;background:#fafafa;color:#111}
.section{margin-bottom:16px}
.card{border:1px solid var(--bd);border-radius:8px;padding:16px;background:#fff}
.table-scroll{max-height:420px;min-height:180px;overflow:auto;border:1px solid var(--bd);border-radius:6px}
table.simple{width:100%;border-collapse:collapse}
table.simple th,table.simple td{border:1px solid var(--bd);padding:8px;text-align:left}
table.simple th{background:#f7f7f7}
table.simple tr:hover{background:#fbfbfb}
.filters{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-bottom:8px}
.filters select,.filters input{padding:6px}
.muted{color:#777;font-size:12px}
.warn{background:#FFF8E1;border:1px solid #FFE082;color:#8D6E63;padding:8px;border-radius:6px;margin:6px 0}
.badge{display:inline-block;font-size:12px;padding:2px 6px;border-radius:999px;background:#EEF2FF;color:#3730A3;margin-left:8px;vertical-align:middle}
.pager{display:flex;gap:6px;align-items:center;margin-top:8px}
.helper-note{color:#6b7280;font-size:12px;margin-top:-4px}
fieldset.inline{border:0;padding:0;margin:0;display:flex;gap:10px;align-items:center}
details summary{cursor:pointer;user-select:none;margin:0 0 4px}
details .adv-grid{display:flex;gap:8px;flex-wrap:wrap}
details .adv-grid > div{min-width:200px}
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace}
</style>';
}

/* ======================== Globales ======================== */
function fetch_sites(){
  return pdo_m()->query("SELECT idsite,name FROM matomo_site ORDER BY idsite ASC")->fetchAll();
}

/**
 * Lee filtros globales desde $_GET (canónicos)
 * Devuelve:
 *  - site, range, fromUi, toUi, has_any
 *  - src: 'all' | 'direct' | 'search:<name>' | 'ref:<name>' | 'camp:any' | 'camp:<name>'
 *  - camp_mode/camp_name (compat-legacy)
 *  - q_idvisit (int|null), q_idvisitor (HEX sin 0x), q_url (string|null)
 */
function get_global_filters(){
  $g_site  = (isset($_GET['g_site'])  && is_numeric($_GET['g_site'])) ? (int)$_GET['g_site'] : null;
  $g_range = $_GET['g_range']   ?? null;
  $g_from  = $_GET['g_from']    ?? null;
  $g_to    = $_GET['g_to']      ?? null;

  // campaña (UI antigua, mantenida por compatibilidad)
  $mode = $_GET['g_camp_mode'] ?? '';
  $name = trim((string)($_GET['g_campaign'] ?? ''));
  if($mode !== 'any' && $mode !== 'named'){ $mode = ''; }
  if($mode === 'named' && $name===''){ $mode=''; }

  // campaña/fuente (UI compacta): canonical
  $g_src = trim((string)($_GET['g_src'] ?? ($_GET['src'] ?? '')));

  // Filtros avanzados
  $q_idvisit   = isset($_GET['g_idvisit']) && ctype_digit($_GET['g_idvisit']) ? (int)$_GET['g_idvisit'] : null;
  $q_idvisitor = normalize_hex_visitor($_GET['g_idvisitor'] ?? '');
  $q_url       = trim((string)($_GET['g_url'] ?? ''));

  return [
    'site'      => $g_site,
    'range'     => $g_range,
    'fromUi'    => $g_from,
    'toUi'      => $g_to,
    'has_any'   => globals_present(),
    'camp_mode' => $mode,
    'camp_name' => $name,
    'src'       => $g_src ?: 'all',
    'q_idvisit' => $q_idvisit,
    'q_idvisitor' => $q_idvisitor, // HEX limpio (o null)
    'q_url'     => $q_url !== '' ? $q_url : null,
  ];
}
function globals_present():bool{
  return isset($_GET['g_site']) || isset($_GET['g_range']) || isset($_GET['g_from']) || isset($_GET['g_to']) ||
         isset($_GET['g_camp_mode']) || isset($_GET['g_campaign']) || isset($_GET['g_src']) || isset($_GET['src']) ||
         isset($_GET['g_idvisit']) || isset($_GET['g_idvisitor']) || isset($_GET['g_url']);
}

/* ======================== Fechas ======================== */
function build_range($inputRange,$fromUi,$toUi){
  $now=new DateTime('now');
  switch($inputRange){
    case 'week':  $from=(clone $now)->modify('-7 days')->format('Y-m-d 00:00:00'); $to=$now->format('Y-m-d 23:59:59'); break;
    case 'month': $from=(new DateTime(date('Y-m-01')))->format('Y-m-d 00:00:00');   $to=$now->format('Y-m-d 23:59:59'); break;
    case 'year':  $from=(new DateTime(date('Y-01-01')))->format('Y-m-d 00:00:00');  $to=$now->format('Y-m-d 23:59:59'); break;
    case 'custom':
      $from=($fromUi && preg_match('/^\d{4}-\d{2}-\d{2}$/',$fromUi))?$fromUi.' 00:00:00':null;
      $to  =($toUi   && preg_match('/^\d{4}-\d{2}-\d{2}$/',$toUi))  ?$toUi  .' 23:59:59':null;
      if(!$from||!$to){ $from=(new DateTime(date('Y-m-01')))->format('Y-m-d 00:00:00'); $to=$now->format('Y-m-d 23:59:59');}
      break;
    default:      $from=(new DateTime(date('Y-m-01')))->format('Y-m-d 00:00:00');    $to=$now->format('Y-m-d 23:59:59');
  }
  return [$from,$to];
}
function quick_range_to_dates($quick){
  $now=new DateTime('now');
  switch($quick){
    case 'ytd': $from=new DateTime(date('Y-01-01')); break;
    case '3m':  $from=(clone $now)->modify('-3 months');  $from->setTime(0,0,0); break;
    case '1m':  $from=(clone $now)->modify('-1 month');   $from->setTime(0,0,0); break;
    case '15d': $from=(clone $now)->modify('-15 days');   $from->setTime(0,0,0); break;
    case '1w':  $from=(clone $now)->modify('-7 days');    $from->setTime(0,0,0); break;
    default: return [null,null];
  }
  $to=$now; $to->setTime(23,59,59);
  return [$from->format('Y-m-d H:i:s'), $to->format('Y-m-d H:i:s')];
}

/* ======================== Segmentación por entrada ======================== */
function entry_condition_sql(){
  return "(ain.name LIKE :url1 OR ain.name LIKE :url2 OR ain.name LIKE '%utm_%')";
}
function allowed_url_params(){
  return [':url1'=>ALLOW_URL_1.'%', ':url2'=>ALLOW_URL_2.'%'];
}

/* ======================== Diccionarios ======================== */
function browser_human($code){
  $map = ['CH'=>'Chrome','CM'=>'Chrome (Mobile)','CR'=>'Chromium','ED'=>'Edge','ME'=>'Edge (Legacy)','IE'=>'Internet Explorer','FF'=>'Firefox','MF'=>'Firefox (Mobile)','SF'=>'Safari','OP'=>'Opera','OPR'=>'Opera','AN'=>'Android WebView','SM'=>'Samsung Internet','FB'=>'Facebook In-App','BR'=>'Brave'];
  $code = strtoupper((string)$code); return $map[$code] ?? ($code ?: 'Desconocido');
}
function os_human($code){
  $map = ['WIN'=>'Windows','MAC'=>'macOS','LIN'=>'Linux','IOS'=>'iOS','AND'=>'Android','CHR'=>'ChromeOS','UNK'=>'Desconocido'];
  $code = strtoupper((string)$code); return $map[$code] ?? ($code ?: 'Desconocido');
}
function device_human($t){
  $map=[0=>'Desktop',1=>'Smartphone',2=>'Tablet',3=>'Feature phone',4=>'Consola',5=>'TV',6=>'Car',7=>'Smart Display',8=>'Cámara'];
  return $map[(int)$t] ?? 'Otro';
}

/* ======================== Compras (si lo usas) ======================== */
function build_purchase_like_sql($aliasCat='cat',$aliasAct='act',$aliasNam='nam'){
  $terms = PURCHASE_MATCH_ANY; $conds=[];
  foreach($terms as $t){ $q = pdo_m()->quote('%'.strtolower($t).'%');
    $conds[]="LOWER($aliasCat.name) LIKE $q";
    $conds[]="LOWER($aliasAct.name) LIKE $q";
    $conds[]="LOWER($aliasNam.name) LIKE $q";
  }
  return '('.implode(' OR ',$conds).')';
}

/* ======================== Campañas / Fuentes ======================== */
/* Mapea GF['src'] a SQL para alias $alias (visits) */
/* Matomo referer_type: 1=direct, 2=search, 3=website, 6=campaign */
function add_campaign_to_where(array $GF, string $vAlias='v'): array {
  $mode = $GF['camp_mode'] ?? '';
  $name = trim((string)($GF['camp_name'] ?? ''));
  if($mode === 'any'){
    return [" AND {$vAlias}.referer_type = 6 ", []];
  }
  if($mode === 'named' && $name !== ''){
    return [" AND {$vAlias}.referer_type = 6 AND {$vAlias}.referer_name = :g_campaign ", [':g_campaign'=>$name]];
  }
  return ['', []];
}
function campaign_source_condition(array $GF, string $alias='v'): array {
  $sql = ''; $p = [];

  // 1) Canónico (prevalece si está definido y no es 'all')
  $raw = trim((string)($GF['src'] ?? ''));
  if ($raw && $raw !== 'all') {
    if ($raw === 'direct') { $sql .= " AND $alias.referer_type = 1 "; }
    elseif (strpos($raw,'search:') === 0) {
      $sql .= " AND $alias.referer_type = 2 ";
      $n = trim(substr($raw,7));
      if ($n !== '') { $sql .= " AND $alias.referer_name = :__srch "; $p[':__srch'] = $n; }
    }
    elseif (strpos($raw,'ref:') === 0) {
      $sql .= " AND $alias.referer_type = 3 ";
      $n = trim(substr($raw,4));
      if ($n !== '') { $sql .= " AND $alias.referer_name = :__ref ";  $p[':__ref']  = $n; }
    }
    elseif ($raw === 'camp:any') { $sql .= " AND $alias.referer_type = 6 "; }
    elseif (strpos($raw,'camp:') === 0) {
      $sql .= " AND $alias.referer_type = 6 ";
      $n = trim(substr($raw,5));
      if ($n !== '') { $sql .= " AND $alias.referer_name = :__camp "; $p[':__camp'] = $n; }
    }
    return [$sql,$p]; // no mezclar con legacy si hay canónico
  }

  // 2) Legacy (solo si no hay canónico)
  $mode = (string)($GF['camp_mode'] ?? '');
  $name = trim((string)($GF['camp_name]'] ?? '')); // tolerante si no existe
  if ($mode === 'any') {
    $sql .= " AND $alias.referer_type = 6 ";
  } elseif ($mode === 'named' && $name !== '') {
    $sql .= " AND $alias.referer_type = 6 AND $alias.referer_name = :__campname ";
    $p[':__campname'] = $name;
  }
  return [$sql,$p];
}

/* ======================== Filtros avanzados ======================== */
function normalize_hex_visitor($s){
  $s = trim((string)$s);
  if($s==='') return null;
  // admite "0xabc..." o solo "abc..."
  $s = strtolower($s);
  if (strpos($s,'0x')===0) $s = substr($s,2);
  // validación básica
  if (!preg_match('/^[0-9a-f]{1,32}$/',$s)) return null;
  return strtoupper($s);
}

/**
 * Construye condición avanzada:
 * - idvisit exacto
 * - idvisitor HEX exacto (usa HEX(v.idvisitor))
 * - URL de ENTRADA (entry) contiene texto (requiere JOIN a matomo_log_action como alias $ain)
 *
 * Retorna array: [sql, params, need_ain_join]
 * (Los módulos la aplican solo si quieren; NO es global.)
 */
function build_advanced_where(array $GF, string $vAlias='v', string $ainAlias='ain'): array {
  $sql = ''; $p = []; $need_ain = false;

  if (!empty($GF['q_idvisit'])) {
    $sql .= " AND {$vAlias}.idvisit = :__idvisit ";
    $p[':__idvisit'] = (int)$GF['q_idvisit'];
  }
  if (!empty($GF['q_idvisitor'])) {
    $sql .= " AND HEX({$vAlias}.idvisitor) = :__idvisitor ";
    $p[':__idvisitor'] = (string)$GF['q_idvisitor']; // HEX ya normalizado
  }
  if (!empty($GF['q_url'])) {
    $sql .= " AND {$ainAlias}.name LIKE :__urlpattern ";
    $p[':__urlpattern'] = '%'.$GF['q_url'].'%';
    $need_ain = true;
  }
  return [$sql, $p, $need_ain];
}

/* ===== Excluir UTM cuando el filtro es Directo (y opcionalmente search/ref) ===== */
function non_campaign_guard_sql(array $GF, string $ainAlias='ain', bool $applyAlsoToSR=false): array {
  $raw = (string)($GF['src'] ?? '');
  $needs = ($raw === 'direct') || ($applyAlsoToSR && (strpos($raw,'search:')===0 || strpos($raw,'ref:')===0));
  return $needs ? [" AND {$ainAlias}.name NOT LIKE '%utm_%' ", []] : ['', []];
}

/* ======================== Guard suave ======================== */
function require_filters_guard(array $GF){
  if(!$GF['has_any']){
    echo "<div class='warn'>Sin filtros aplicados. Selecciona un sitio y un periodo para empezar.</div>";
    return false;
  }
  return true;
}

/* ======================== Helper de paginación (faltaba) ======================== */
/**
 * client_pager_js($tableId, $perPage)
 * Crea un paginador muy simple para tablas HTML.
 * - No rompe si el elemento no existe.
 * - Se esconde a sí mismo si hay pocas filas.
 */
function client_pager_js(string $tableId, int $perPage=20){
  $perPage = max(1, $perPage);
  echo "<script>(function(){try{
    var tbl=document.getElementById('".e($tableId)."'); if(!tbl) return;
    var tbody=tbl.tBodies && tbl.tBodies[0]; if(!tbody) return;
    var rows=[].slice.call(tbody.rows).filter(function(tr){ return tr.style.display!== 'none'; });
    if(rows.length <= $perPage) return;

    var wrap=tbl.parentElement || tbl; 
    var bar=document.createElement('div'); bar.className='pager';
    var prev=document.createElement('button'); prev.textContent='‹ Anterior'; prev.type='button';
    var next=document.createElement('button'); next.textContent='Siguiente ›'; next.type='button';
    var info=document.createElement('span'); info.className='muted';
    bar.appendChild(prev); bar.appendChild(info); bar.appendChild(next);
    wrap.parentNode.insertBefore(bar, wrap.nextSibling);

    var page=1, total=Math.ceil(rows.length / $perPage);
    function render(){
      var i, start=(page-1)*$perPage, end=start+$perPage;
      for(i=0;i<rows.length;i++){ rows[i].style.display = (i>=start && i<end)?'':'none'; }
      info.textContent='Página '+page+' de '+total;
      prev.disabled = (page<=1); next.disabled = (page>=total);
    }
    prev.onclick=function(){ if(page>1){ page--; render(); } };
    next.onclick=function(){ if(page<total){ page++; render(); } };
    render();
  }catch(e){/* no romper UI */}})();</script>";
}

/* ======================== Hardening (no tumbar toda la página) ======================== */
set_exception_handler(function($e){
  echo "<div class='warn'>Se encontró un error al renderizar un widget (detalle técnico oculto).</div>";
});
set_error_handler(function($no,$str,$file,$line){
  echo "<div class='warn'>Se encontró un aviso al renderizar un widget.</div>";
  return true; // evita que se propague
});
