<?php
declare(strict_types=1);

/** Formatea un valor numerico como moneda COP. */
function money(?float $value): string
{
    return '$' . number_format((float) $value, 0, ',', '.');
}

/** Escapa texto para salida segura en HTML. */
function h(?string $value): string
{
    return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}

/**
 * Recalcula valor_neto, valor_iva, valor_total y num_items de una factura a partir
 * de sus lineas reales en ventas_detalle. Idempotente: se puede llamar tantas veces
 * como se quiera (tras importar, agregar, editar o eliminar lineas) sin duplicar totales.
 */
function recalcularFactura(mysqli $conn, string $factura): void
{
    $stmt = $conn->prepare(
        'UPDATE facturas f
         JOIN (
             SELECT factura, SUM(valor_neto) AS vn, SUM(valor_iva) AS vi, COUNT(*) AS n,
                    MIN(codigo_cliente) AS cc, MIN(fecha) AS fe, MIN(cod_vendedor) AS cv, MIN(cod_regional) AS cr
             FROM ventas_detalle WHERE factura = ? GROUP BY factura
         ) x ON x.factura = f.factura
         SET f.valor_neto = x.vn, f.valor_iva = x.vi, f.valor_total = x.vn + x.vi, f.num_items = x.n,
             f.codigo_cliente = COALESCE(x.cc, f.codigo_cliente), f.fecha = COALESCE(x.fe, f.fecha),
             f.cod_vendedor = COALESCE(x.cv, f.cod_vendedor), f.cod_regional = COALESCE(x.cr, f.cod_regional)
         WHERE f.factura = ?'
    );
    $stmt->bind_param('ss', $factura, $factura);
    $stmt->execute();
    $stmt->close();

    // Si ya no quedan lineas (se eliminaron todas), deja la factura en ceros: el UPDATE...JOIN
    // de arriba no toca la fila cuando no hay lineas que agregar.
    $stmtVacia = $conn->prepare(
        'UPDATE facturas SET valor_neto = 0, valor_iva = 0, valor_total = 0, num_items = 0
         WHERE factura = ? AND NOT EXISTS (SELECT 1 FROM ventas_detalle WHERE factura = facturas.factura)'
    );
    $stmtVacia->bind_param('s', $factura);
    $stmtVacia->execute();
    $stmtVacia->close();
}

/**
 * Igual que recalcularFactura(), pero para muchas facturas a la vez: agrupa en lotes
 * de 500 y hace una sola consulta UPDATE...JOIN por lote (WHERE factura IN (...)) en
 * vez de una consulta por factura. Pensada para importaciones masivas, donde llamar
 * recalcularFactura() en un bucle por cada factura generaba miles de round-trips a la
 * base de datos y agotaba el tiempo de ejecución del servidor con archivos grandes.
 */
function recalcularFacturasEnLote(mysqli $conn, array $facturas): void
{
    $facturas = array_values(array_unique(array_map('strval', $facturas)));

    foreach (array_chunk($facturas, 500) as $chunk) {
        $placeholders = implode(',', array_fill(0, count($chunk), '?'));
        $stmt = $conn->prepare(
            "UPDATE facturas f
             JOIN (
                 SELECT factura, SUM(valor_neto) AS vn, SUM(valor_iva) AS vi, COUNT(*) AS n,
                        MIN(codigo_cliente) AS cc, MIN(fecha) AS fe, MIN(cod_vendedor) AS cv, MIN(cod_regional) AS cr
                 FROM ventas_detalle WHERE factura IN ($placeholders) GROUP BY factura
             ) x ON x.factura = f.factura
             SET f.valor_neto = x.vn, f.valor_iva = x.vi, f.valor_total = x.vn + x.vi, f.num_items = x.n,
                 f.codigo_cliente = COALESCE(x.cc, f.codigo_cliente), f.fecha = COALESCE(x.fe, f.fecha),
                 f.cod_vendedor = COALESCE(x.cv, f.cod_vendedor), f.cod_regional = COALESCE(x.cr, f.cod_regional)"
        );
        $stmt->execute($chunk);
        $stmt->close();
    }
}

/** Convierte un valor de celda (texto, numero, o serial de Excel) a 'Y-m-d' o null. */
function parseFecha(?string $value): ?string
{
    $value = trim((string) $value);
    if ($value === '') {
        return null;
    }

    // Serial de fecha de Excel (dias desde 1899-12-30)
    if (ctype_digit($value)) {
        $timestamp = ((int) $value - 25569) * 86400;
        $fecha = gmdate('Y-m-d', $timestamp);
        return $fecha;
    }

    // Formatos comunes: Y-m-d, d/m/Y, d-m-Y
    foreach (['Y-m-d', 'd/m/Y', 'd-m-Y', 'm/d/Y'] as $formato) {
        $dt = DateTime::createFromFormat($formato, $value);
        if ($dt instanceof DateTime) {
            return $dt->format('Y-m-d');
        }
    }

    $timestamp = strtotime($value);
    return $timestamp !== false ? date('Y-m-d', $timestamp) : null;
}

/** Convierte texto numerico (con comas, espacios o vacio) a float. */
function parseDecimal(?string $value): float
{
    $value = trim((string) $value);
    if ($value === '') {
        return 0.0;
    }
    $value = str_replace([' ', '$'], '', $value);
    // Si usa coma como separador decimal y punto como miles (formato es-CO)
    if (preg_match('/^-?\d{1,3}(\.\d{3})*(,\d+)?$/', $value)) {
        $value = str_replace('.', '', $value);
        $value = str_replace(',', '.', $value);
    } else {
        $value = str_replace(',', '', $value);
    }
    return (float) $value;
}

/** Limpia espacios y caracteres invisibles de un codigo/identificador. */
function parseCodigo(?string $value): ?string
{
    $value = trim((string) $value);
    return $value === '' ? null : $value;
}

/** Genera un token CSRF y lo guarda en sesion. */
function csrfToken(): string
{
    if (session_status() !== PHP_SESSION_ACTIVE) {
        session_start();
    }
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

/** Valida el token CSRF recibido por POST. */
function csrfValidate(): bool
{
    if (session_status() !== PHP_SESSION_ACTIVE) {
        session_start();
    }
    $token = $_POST['csrf_token'] ?? '';
    return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}

/** Lee parametros estandar de DataTables server-side y retorna arreglo normalizado. */
function dtRequestParams(array $columns): array
{
    $draw   = (int) ($_GET['draw'] ?? 1);
    $start  = max(0, (int) ($_GET['start'] ?? 0));
    $length = (int) ($_GET['length'] ?? 25);
    $length = $length > 0 ? min($length, 500) : 25;
    $search = trim((string) ($_GET['search']['value'] ?? ''));

    $orderColIndex = (int) ($_GET['order'][0]['column'] ?? 0);
    $orderDir      = strtolower((string) ($_GET['order'][0]['dir'] ?? 'asc')) === 'desc' ? 'DESC' : 'ASC';
    $orderCol      = $columns[$orderColIndex] ?? $columns[0];

    return compact('draw', 'start', 'length', 'search', 'orderCol', 'orderDir');
}

/**
 * Construye el WHERE + parametros para el informe "Utilidad por Ventas" a partir de
 * los filtros en $_GET (cod_vendedor, cod_regional, ciudad, estado) y un texto de busqueda.
 * Se usa tanto en el DataTable server-side como en la exportacion, para no duplicar filtros.
 * Retorna [whereSql, params, types].
 */
function construirFiltrosUtilidadVentas(string $search = ''): array
{
    $where = [];
    $params = [];
    $types = '';

    if ($search !== '') {
        $where[] = '(vd.factura LIKE ? OR c.nombre LIKE ? OR v.nombre LIKE ?)';
        $like = '%' . $search . '%';
        array_push($params, $like, $like, $like);
        $types .= 'sss';
    }
    if (!empty($_GET['cod_vendedor'])) {
        $where[] = 'v.cod_vendedor = ?';
        $params[] = $_GET['cod_vendedor'];
        $types .= 's';
    }
    if (!empty($_GET['cod_regional'])) {
        $where[] = 'v.cod_regional = ?';
        $params[] = $_GET['cod_regional'];
        $types .= 's';
    }
    if (!empty($_GET['ciudad'])) {
        $where[] = 'c.ciudad = ?';
        $params[] = $_GET['ciudad'];
        $types .= 's';
    }
    if (!empty($_GET['desde'])) {
        $where[] = 'vd.fecha >= ?';
        $params[] = $_GET['desde'];
        $types .= 's';
    }
    if (!empty($_GET['hasta'])) {
        $where[] = 'vd.fecha <= ?';
        $params[] = $_GET['hasta'];
        $types .= 's';
    }
    if (!empty($_GET['estado']) && in_array($_GET['estado'], ['CANCELADA', 'PENDIENTE'], true)) {
        $where[] = $_GET['estado'] === 'CANCELADA'
            ? '(car.factura IS NULL OR car.saldo <= 0)'
            : '(car.factura IS NOT NULL AND car.saldo > 0)';
    }

    $whereSql = $where ? ('WHERE ' . implode(' AND ', $where)) : '';
    return [$whereSql, $params, $types];
}

/** Envia una respuesta JSON con formato esperado por DataTables. */
function dtRespond(int $draw, int $total, int $filtered, array $rows): void
{
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode([
        'draw' => $draw,
        'recordsTotal' => $total,
        'recordsFiltered' => $filtered,
        'data' => $rows,
    ], JSON_UNESCAPED_UNICODE);
    exit;
}

/**
 * Envia un CSV compatible con Excel (BOM UTF-8, delimitador ';') como descarga y termina la petición.
 * $encabezados: nombres de columna. $mapaFila: transforma cada fila de $result en un array indexado
 * en el mismo orden que $encabezados.
 */
function exportarCsv(string $nombreArchivo, array $encabezados, mysqli_result $result, callable $mapaFila): void
{
    header('Content-Type: text/csv; charset=utf-8');
    header('Content-Disposition: attachment; filename="' . $nombreArchivo . '"');
    header('Pragma: no-cache');
    header('Expires: 0');

    $out = fopen('php://output', 'w');
    fwrite($out, "\xEF\xBB\xBF"); // BOM: para que Excel detecte UTF-8 y muestre tildes/ñ correctamente
    fputcsv($out, $encabezados, ';');
    while ($fila = $result->fetch_assoc()) {
        fputcsv($out, $mapaFila($fila), ';');
    }
    fclose($out);
    exit;
}
