<?php
/**
 * (c) ININOUT & PYXIS. All Rights Reserved.
 * Wszelkie modyfikacje tego pliku wymagaja zgloszenia oraz zgody autorow.
 * Any modifications to this file require notification and consent of the authors.
 */
/**
 * i-BOK 5 - Funkcje agregacji danych do wykresów i statystyk
 */

/**
 * Agreguj kwoty faktur po miesiącach (ostatnie N miesięcy)
 *
 * @param array $faktury Lista faktur z vbk_read_faktury()
 * @param int $months Liczba miesięcy wstecz
 * @return array ['labels' => ['2025-03', ...], 'values' => [42.0, ...]]
 */
function stats_invoices_by_month($faktury, $months = 12) {
    $data = [];
    $now = time();

    // Przygotuj puste miesiące
    for ($i = $months - 1; $i >= 0; $i--) {
        $key = date('Y-m', strtotime("-{$i} months", $now));
        $data[$key] = 0.0;
    }

    foreach ($faktury as $f) {
        $date = $f['data_wystawienia'] ?? '';
        if (empty($date) || $date === '0000-00-00') continue;

        $ts = strtotime($date);
        if ($ts === false) continue;

        $key = date('Y-m', $ts);
        if (isset($data[$key])) {
            $data[$key] += parse_kwota($f['kwota_brutto'] ?? '0');
        }
    }

    return [
        'labels' => array_keys($data),
        'values' => array_values($data),
    ];
}

/**
 * Agreguj kwoty płatności po miesiącach
 *
 * @param array $oplaty Lista płatności z vbk_read_oplaty()
 * @param int $months Liczba miesięcy wstecz
 * @return array ['labels' => [...], 'values' => [...]]
 */
function stats_payments_by_month($oplaty, $months = 12) {
    $data = [];
    $now = time();

    for ($i = $months - 1; $i >= 0; $i--) {
        $key = date('Y-m', strtotime("-{$i} months", $now));
        $data[$key] = 0.0;
    }

    foreach ($oplaty as $o) {
        $date = $o['data_ksieg'] ?? '';
        if (empty($date) || $date === '0000-00-00') continue;

        $ts = strtotime($date);
        if ($ts === false) continue;

        $key = date('Y-m', $ts);
        if (isset($data[$key])) {
            $data[$key] += parse_kwota($o['kwota'] ?? '0');
        }
    }

    return [
        'labels' => array_keys($data),
        'values' => array_values($data),
    ];
}

/**
 * Rozkład taryf (grupowanie po dl_dzien)
 *
 * @param array $clients Mapa klientów z vbk_read_klienci()
 * @return array ['100M' => 15, '50M' => 8, ...]
 */
function stats_tariff_distribution($clients) {
    $dist = [];

    foreach ($clients as $c) {
        $tariff = trim($c['dl_dzien'] ?? '');
        if (empty($tariff)) $tariff = '?';
        if (!isset($dist[$tariff])) {
            $dist[$tariff] = 0;
        }
        $dist[$tariff]++;
    }

    arsort($dist);
    return $dist;
}

/**
 * Podsumowanie statusów klientów
 *
 * @param array $clients Mapa klientów z vbk_read_klienci()
 * @return array ['active' => N, 'inactive' => N, 'with_debt' => N]
 */
function stats_debt_summary($clients) {
    $active = 0;
    $inactive = 0;
    $with_debt = 0;

    foreach ($clients as $c) {
        if ($c['active_flag'] === '1') {
            $active++;
            if ((int)$c['wg_salda'] > 0 || $c['zalega'] === '1') {
                $with_debt++;
            }
        } else {
            $inactive++;
        }
    }

    return [
        'active'    => $active,
        'inactive'  => $inactive,
        'with_debt' => $with_debt,
    ];
}

/**
 * Agreguj przychód miesięczny ze wszystkich klientów (MRR)
 *
 * Iteruje po WSZYSTKICH klientach, czyta ich faktury i sumuje kwoty po miesiącach.
 *
 * @param int $months Liczba miesięcy wstecz
 * @return array ['labels' => ['2025-03', ...], 'values' => [42.0, ...]]
 */
function stats_revenue_by_month($months = 12) {
    if (function_exists('db_is_mysql_mode') && db_is_mysql_mode()) {
        return db_stats_revenue_by_month($months);
    }
    $data = [];
    $now = time();

    // Przygotuj puste miesiące
    for ($i = $months - 1; $i >= 0; $i--) {
        $key = date('Y-m', strtotime("-{$i} months", $now));
        $data[$key] = 0.0;
    }

    $clients = vbk_read_klienci();

    foreach ($clients as $cid => $c) {
        $faktury = vbk_read_faktury($cid);
        foreach ($faktury as $f) {
            $date = $f['data_wystawienia'] ?? '';
            if (empty($date) || $date === '0000-00-00') continue;

            $ts = strtotime($date);
            if ($ts === false) continue;

            $key = date('Y-m', $ts);
            if (isset($data[$key])) {
                $data[$key] += parse_kwota($f['kwota_brutto'] ?? '0');
            }
        }
    }

    return [
        'labels' => array_keys($data),
        'values' => array_values($data),
    ];
}

/**
 * Rozkład form płatności
 *
 * @param array $oplaty Lista płatności z vbk_read_oplaty()
 * @return array ['przelew' => N, 'karta' => N, ...]
 */
function stats_payment_methods($oplaty) {
    $methods = [];

    foreach ($oplaty as $o) {
        $method = trim($o['forma_platn'] ?? '');
        if (empty($method)) $method = '?';
        if (!isset($methods[$method])) {
            $methods[$method] = 0;
        }
        $methods[$method]++;
    }

    arsort($methods);
    return $methods;
}

/**
 * Przychód (faktury) vs wpłaty po miesiącach — SQL na PYXIS
 *
 * @param int $months Liczba miesięcy wstecz
 * @return array ['labels'=>[...], 'invoiced'=>[...], 'paid'=>[...]]
 */
function stats_invoices_vs_payments($months = 12) {
    $data = [];
    $now = time();
    for ($i = $months - 1; $i >= 0; $i--) {
        $key = date('Y-m', strtotime("-{$i} months", $now));
        $data[$key] = ['invoiced' => 0.0, 'paid' => 0.0];
    }

    $pdo = db_get_pdo();
    if (!$pdo) return ['labels' => array_keys($data), 'invoiced' => array_fill(0, $months, 0), 'paid' => array_fill(0, $months, 0)];

    $first_month = array_key_first($data) . '-01';

    try {
        // Faktury
        $stmt = $pdo->prepare(
            "SELECT LEFT(FV_DATAWYSTAWIENIA, 7) AS m, SUM(FV_SUMABRUTTO) AS total
             FROM vFvLista
             WHERE FV_DATAWYSTAWIENIA >= ? AND FV_DATAWYSTAWIENIA <> '0000-00-00'
             GROUP BY m ORDER BY m"
        );
        $stmt->execute([$first_month]);
        foreach ($stmt->fetchAll() as $row) {
            if (isset($data[$row['m']])) $data[$row['m']]['invoiced'] = (float)$row['total'];
        }

        // Wpłaty
        $stmt = $pdo->prepare(
            "SELECT LEFT(O_DATAWPLATY, 7) AS m, SUM(O_KWOTA) AS total
             FROM vOplatyLista
             WHERE O_DATAWPLATY >= ? AND O_DATAWPLATY <> '0000-00-00'
             GROUP BY m ORDER BY m"
        );
        $stmt->execute([$first_month]);
        foreach ($stmt->fetchAll() as $row) {
            if (isset($data[$row['m']])) $data[$row['m']]['paid'] = (float)$row['total'];
        }
    } catch (PDOException $e) {
        ibok_log("stats_invoices_vs_payments error: " . $e->getMessage(), 'error');
    }

    return [
        'labels'   => array_keys($data),
        'invoiced' => array_values(array_map(fn($d) => $d['invoiced'], $data)),
        'paid'     => array_values(array_map(fn($d) => $d['paid'], $data)),
    ];
}

/**
 * Trend sumy zaległości po miesiącach (z danych klientów — migawkowy)
 *
 * Ponieważ PYXIS nie przechowuje historycznych sald, zwracamy bieżące saldo
 * plus przybliżenie z faktur nieopłaconych w każdym miesiącu.
 *
 * @param array $clients Mapa klientów
 * @param int $months Liczba miesięcy wstecz
 * @return array ['labels'=>[...], 'values'=>[...]]
 */
function stats_debt_trend($clients, $months = 12) {
    $data = [];
    $now = time();
    for ($i = $months - 1; $i >= 0; $i--) {
        $key = date('Y-m', strtotime("-{$i} months", $now));
        $data[$key] = 0.0;
    }

    $pdo = db_get_pdo();
    if (!$pdo) return ['labels' => array_keys($data), 'values' => array_values($data)];

    $first_month = array_key_first($data) . '-01';

    try {
        // Suma kwot do rozliczenia wg miesiąca wystawienia (przybliżenie trendu)
        $stmt = $pdo->prepare(
            "SELECT LEFT(FV_DATAWYSTAWIENIA, 7) AS m, SUM(FV_KWOTADOROZLICZENIA) AS total
             FROM vFvLista
             WHERE FV_DATAWYSTAWIENIA >= ? AND FV_DATAWYSTAWIENIA <> '0000-00-00'
               AND FV_KWOTADOROZLICZENIA > 0
             GROUP BY m ORDER BY m"
        );
        $stmt->execute([$first_month]);
        foreach ($stmt->fetchAll() as $row) {
            if (isset($data[$row['m']])) $data[$row['m']] = (float)$row['total'];
        }
    } catch (PDOException $e) {
        ibok_log("stats_debt_trend error: " . $e->getMessage(), 'error');
    }

    return [
        'labels' => array_keys($data),
        'values' => array_values($data),
    ];
}

/**
 * Efektywność windykacji — % opłaconych faktur (kwota rozliczona / brutto)
 *
 * @param int $months Liczba miesięcy wstecz
 * @return array ['labels'=>[...], 'values'=>[...]] (wartości 0-100)
 */
function stats_collection_rate($months = 12) {
    $data = [];
    $now = time();
    for ($i = $months - 1; $i >= 0; $i--) {
        $key = date('Y-m', strtotime("-{$i} months", $now));
        $data[$key] = ['rozliczone' => 0.0, 'brutto' => 0.0];
    }

    $pdo = db_get_pdo();
    if (!$pdo) return ['labels' => array_keys($data), 'values' => array_fill(0, $months, 0)];

    $first_month = array_key_first($data) . '-01';

    try {
        $stmt = $pdo->prepare(
            "SELECT LEFT(FV_DATAWYSTAWIENIA, 7) AS m,
                    SUM(FV_KWOTAROZLICZONA) AS rozl, SUM(FV_SUMABRUTTO) AS brutto
             FROM vFvLista
             WHERE FV_DATAWYSTAWIENIA >= ? AND FV_DATAWYSTAWIENIA <> '0000-00-00'
             GROUP BY m ORDER BY m"
        );
        $stmt->execute([$first_month]);
        foreach ($stmt->fetchAll() as $row) {
            if (isset($data[$row['m']])) {
                $data[$row['m']]['rozliczone'] = (float)$row['rozl'];
                $data[$row['m']]['brutto']     = (float)$row['brutto'];
            }
        }
    } catch (PDOException $e) {
        ibok_log("stats_collection_rate error: " . $e->getMessage(), 'error');
    }

    $values = [];
    foreach ($data as $d) {
        $values[] = ($d['brutto'] > 0) ? round($d['rozliczone'] / $d['brutto'] * 100, 1) : 0;
    }

    return [
        'labels' => array_keys($data),
        'values' => $values,
    ];
}

/**
 * Rozkład statusów faktur (doughnut) — opłacona / częściowo / nieopłacona
 *
 * @param array $faktury Lista faktur
 * @return array ['paid'=>N, 'partial'=>N, 'unpaid'=>N]
 */
function stats_invoice_status_dist($faktury) {
    $result = ['paid' => 0, 'partial' => 0, 'unpaid' => 0];

    foreach ($faktury as $f) {
        $status = $f['status'] ?? '';
        if ($status === 'paid') {
            $result['paid']++;
        } elseif ($status === 'partial') {
            $result['partial']++;
        } else {
            $result['unpaid']++;
        }
    }

    return $result;
}

/**
 * Top N dłużników z kwotami
 *
 * @param array $clients Mapa klientów
 * @param int $limit Limit
 * @return array [['id'=>..., 'nazwisko'=>..., 'kwota'=>...], ...]
 */
function stats_top_debtors($clients, $limit = 10) {
    $debtors = [];
    foreach ($clients as $cid => $c) {
        $kwota = (float)str_replace(',', '.', $c['saldo_zalegle_raw'] ?? '0');
        if ($kwota > 0) {
            $debtors[] = ['id' => $cid, 'nazwisko' => $c['nazwisko'], 'kwota' => $kwota];
        }
    }
    usort($debtors, fn($a, $b) => $b['kwota'] <=> $a['kwota']);
    return array_slice($debtors, 0, $limit);
}

/**
 * Wzrost bazy klientów — nowi klienci/miesiąc (z umowa_od)
 *
 * @param array $clients Mapa klientów
 * @param int $months Liczba miesięcy wstecz
 * @return array ['labels'=>[...], 'values'=>[...]]
 */
function stats_client_growth($clients, $months = 12) {
    $data = [];
    $now = time();
    for ($i = $months - 1; $i >= 0; $i--) {
        $key = date('Y-m', strtotime("-{$i} months", $now));
        $data[$key] = 0;
    }

    foreach ($clients as $c) {
        $date = $c['umowa_od'] ?? '';
        if (empty($date) || $date === '0000-00-00') continue;
        $key = date('Y-m', strtotime($date));
        if (isset($data[$key])) $data[$key]++;
    }

    return [
        'labels' => array_keys($data),
        'values' => array_values($data),
    ];
}

/**
 * Kończące się umowy w najbliższych N miesiącach
 *
 * @param array $clients Mapa klientów
 * @param int $months Liczba miesięcy do przodu
 * @return array ['labels'=>[...], 'values'=>[...]]
 */
function stats_client_churn($clients, $months = 6) {
    $data = [];
    $now = time();
    for ($i = 0; $i < $months; $i++) {
        $key = date('Y-m', strtotime("+{$i} months", $now));
        $data[$key] = 0;
    }

    foreach ($clients as $c) {
        $date = $c['umowa_do'] ?? '';
        if (empty($date) || $date === '0000-00-00') continue;
        $key = date('Y-m', strtotime($date));
        if (isset($data[$key])) $data[$key]++;
    }

    return [
        'labels' => array_keys($data),
        'values' => array_values($data),
    ];
}

/**
 * Rozkład geograficzny klientów — top N miejscowości
 *
 * @param array $clients Mapa klientów
 * @param int $limit Limit
 * @return array ['Miasto' => count, ...]
 */
function stats_geo_distribution($clients, $limit = 15) {
    $cities = [];
    foreach ($clients as $c) {
        $city = trim($c['miejscowosc'] ?? '');
        if ($city === '') continue;
        $cities[$city] = ($cities[$city] ?? 0) + 1;
    }
    arsort($cities);

    $top = array_slice($cities, 0, $limit, true);
    $other = array_sum(array_slice($cities, $limit));
    if ($other > 0) {
        $top[__('analytics.label_other')] = $other;
    }
    return $top;
}

/**
 * Zlecenia serwisowe — otwarte vs zamknięte po miesiącach + średni czas
 *
 * @param array $zlecenia Lista zleceń
 * @param int $months Liczba miesięcy wstecz
 * @return array ['labels'=>[...], 'open'=>[...], 'closed'=>[...]]
 */
function stats_service_resolution($zlecenia, $months = 12) {
    $data = [];
    $now = time();
    for ($i = $months - 1; $i >= 0; $i--) {
        $key = date('Y-m', strtotime("-{$i} months", $now));
        $data[$key] = ['open' => 0, 'closed' => 0];
    }

    foreach ($zlecenia as $z) {
        $m = substr($z['data'] ?? '', 0, 7);
        if (isset($data[$m])) {
            if (empty($z['data_zakonczenia'])) {
                $data[$m]['open']++;
            } else {
                $data[$m]['closed']++;
            }
        }
    }

    return [
        'labels' => array_keys($data),
        'open'   => array_values(array_map(fn($d) => $d['open'], $data)),
        'closed' => array_values(array_map(fn($d) => $d['closed'], $data)),
    ];
}

/**
 * Aktywność logowań dziennie (z plików log)
 *
 * @param int $days Liczba dni wstecz
 * @return array ['labels'=>[...], 'values'=>[...]]
 */
function stats_login_activity($days = 30) {
    $data = [];
    $now = time();
    for ($i = $days - 1; $i >= 0; $i--) {
        $key = date('Y-m-d', strtotime("-{$i} days", $now));
        $data[$key] = 0;
    }

    $dir = defined('IBOK_LOGS_DIR') ? IBOK_LOGS_DIR : __DIR__ . '/../logs';
    $log_files = glob($dir . '/ibok_*.log');
    if (empty($log_files)) return ['labels' => array_keys($data), 'values' => array_values($data)];

    rsort($log_files);
    $files_to_read = array_slice($log_files, 0, 3);
    $pattern = '/^(\d{4}-\d{2}-\d{2}) \d{2}:\d{2}:\d{2} \[info\].*Login (admina|klienta) udany/';

    foreach ($files_to_read as $f) {
        $lines = @file($f, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        if (!$lines) continue;
        foreach ($lines as $line) {
            if (preg_match($pattern, $line, $m)) {
                $day = $m[1];
                if (isset($data[$day])) $data[$day]++;
            }
        }
    }

    return [
        'labels' => array_keys($data),
        'values' => array_values($data),
    ];
}

/**
 * Nieudane logowania trend (z plików log)
 *
 * @param int $days Liczba dni wstecz
 * @return array ['labels'=>[...], 'values'=>[...]]
 */
function stats_failed_logins_trend($days = 30) {
    $data = [];
    $now = time();
    for ($i = $days - 1; $i >= 0; $i--) {
        $key = date('Y-m-d', strtotime("-{$i} days", $now));
        $data[$key] = 0;
    }

    $dir = defined('IBOK_LOGS_DIR') ? IBOK_LOGS_DIR : __DIR__ . '/../logs';
    $log_files = glob($dir . '/ibok_*.log');
    if (empty($log_files)) return ['labels' => array_keys($data), 'values' => array_values($data)];

    rsort($log_files);
    $files_to_read = array_slice($log_files, 0, 3);
    $pattern = '/^(\d{4}-\d{2}-\d{2}) \d{2}:\d{2}:\d{2} \[warning\].*Login (admina|klienta) (nieudany|zablokowany)/';

    foreach ($files_to_read as $f) {
        $lines = @file($f, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        if (!$lines) continue;
        foreach ($lines as $line) {
            if (preg_match($pattern, $line, $m)) {
                $day = $m[1];
                if (isset($data[$day])) $data[$day]++;
            }
        }
    }

    return [
        'labels' => array_keys($data),
        'values' => array_values($data),
    ];
}

/**
 * Aging zaległości — podział długu po terminach: 0-30, 31-60, 61-90, 90+ dni
 *
 * @return array ['0-30'=>kwota, '31-60'=>kwota, '61-90'=>kwota, '90+'=>kwota]
 */
function stats_debt_aging() {
    $buckets = ['0-30' => 0.0, '31-60' => 0.0, '61-90' => 0.0, '90+' => 0.0];

    $pdo = db_get_pdo();
    if (!$pdo) return $buckets;

    try {
        $stmt = $pdo->query(
            "SELECT FV_PoTerminie, FV_KWOTADOROZLICZENIA
             FROM vFvLista
             WHERE FV_KWOTADOROZLICZENIA > 0
               AND FV_DATAWYSTAWIENIA <> '0000-00-00'"
        );
        foreach ($stmt->fetchAll() as $row) {
            $dni = (int)($row['FV_PoTerminie'] ?? 0);
            $kwota = (float)$row['FV_KWOTADOROZLICZENIA'];
            if ($dni <= 30) $buckets['0-30'] += $kwota;
            elseif ($dni <= 60) $buckets['31-60'] += $kwota;
            elseif ($dni <= 90) $buckets['61-90'] += $kwota;
            else $buckets['90+'] += $kwota;
        }
    } catch (PDOException $e) {
        ibok_log("stats_debt_aging error: " . $e->getMessage(), 'error');
    }

    return $buckets;
}

/**
 * Korekty faktur po miesiącach (faktury z ujemną kwotą brutto)
 */
function stats_corrections_by_month($months = 12) {
    $data = [];
    $now = time();
    for ($i = $months - 1; $i >= 0; $i--) {
        $key = date('Y-m', strtotime("-{$i} months", $now));
        $data[$key] = 0;
    }

    $pdo = db_get_pdo();
    if (!$pdo) return ['labels' => array_keys($data), 'values' => array_values($data)];

    $first_month = array_key_first($data) . '-01';
    try {
        $stmt = $pdo->prepare(
            "SELECT LEFT(FV_DATAWYSTAWIENIA, 7) AS m, COUNT(*) AS cnt
             FROM vFvLista
             WHERE FV_DATAWYSTAWIENIA >= ? AND FV_DATAWYSTAWIENIA <> '0000-00-00'
               AND FV_SUMABRUTTO < 0
             GROUP BY m ORDER BY m"
        );
        $stmt->execute([$first_month]);
        foreach ($stmt->fetchAll() as $row) {
            if (isset($data[$row['m']])) $data[$row['m']] = (int)$row['cnt'];
        }
    } catch (PDOException $e) {
        ibok_log("stats_corrections_by_month error: " . $e->getMessage(), 'error');
    }

    return ['labels' => array_keys($data), 'values' => array_values($data)];
}

/**
 * Prognoza przychodu — ekstrapolacja liniowa MRR na 3 miesiące
 */
function stats_revenue_forecast($rev_pay) {
    $labels = $rev_pay['labels'];
    $actual = $rev_pay['invoiced'];
    $n = count($actual);

    $use = min(6, $n);
    $x_vals = range($n - $use, $n - 1);
    $y_vals = array_slice($actual, -$use);

    $sum_x = array_sum($x_vals);
    $sum_y = array_sum($y_vals);
    $sum_xy = 0; $sum_x2 = 0;
    for ($i = 0; $i < $use; $i++) {
        $sum_xy += $x_vals[$i] * $y_vals[$i];
        $sum_x2 += $x_vals[$i] * $x_vals[$i];
    }

    $denom = $use * $sum_x2 - $sum_x * $sum_x;
    if ($denom == 0) { $slope = 0; $intercept = $sum_y / max($use, 1); }
    else { $slope = ($use * $sum_xy - $sum_x * $sum_y) / $denom; $intercept = ($sum_y - $slope * $sum_x) / $use; }

    $forecast = array_fill(0, $n, null);
    $now = time();
    for ($i = 1; $i <= 3; $i++) {
        $key = date('Y-m', strtotime("+{$i} months", $now));
        $labels[] = $key;
        $actual[] = null;
        $forecast[] = round(max(0, $intercept + $slope * ($n + $i - 1)), 2);
    }
    // Overlap point
    if ($n > 0) {
        $last = null;
        for ($j = $n - 1; $j >= 0; $j--) { if ($actual[$j] !== null) { $last = $actual[$j]; break; } }
        $forecast[$n - 1] = $last;
    }

    return ['labels' => $labels, 'actual' => $actual, 'forecast' => $forecast];
}

/**
 * Sezonowość wpłat — w które dni miesiąca klienci najczęściej płacą
 */
function stats_payment_day_distribution() {
    $days = array_fill(1, 31, 0);

    $pdo = db_get_pdo();
    if (!$pdo) return ['labels' => array_keys($days), 'values' => array_values($days)];

    try {
        $stmt = $pdo->query(
            "SELECT DAY(O_DATAWPLATY) AS d, COUNT(*) AS cnt
             FROM vOplatyLista
             WHERE O_DATAWPLATY >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH)
               AND O_DATAWPLATY <> '0000-00-00'
             GROUP BY d ORDER BY d"
        );
        foreach ($stmt->fetchAll() as $row) {
            $d = (int)$row['d'];
            if ($d >= 1 && $d <= 31) $days[$d] = (int)$row['cnt'];
        }
    } catch (PDOException $e) {
        ibok_log("stats_payment_day_distribution error: " . $e->getMessage(), 'error');
    }

    return ['labels' => array_keys($days), 'values' => array_values($days)];
}

/**
 * Retencja klientów — % aktywnych wg roku aktywacji (kohorty)
 */
function stats_client_retention($clients) {
    $cohorts = [];
    foreach ($clients as $c) {
        $od = $c['umowa_od'] ?? '';
        if (empty($od) || $od === '0000-00-00') continue;
        $year = date('Y', strtotime($od));
        if (!isset($cohorts[$year])) $cohorts[$year] = ['total' => 0, 'active' => 0];
        $cohorts[$year]['total']++;
        if ($c['active_flag'] === '1') $cohorts[$year]['active']++;
    }
    ksort($cohorts);
    $cohorts = array_slice($cohorts, -8, null, true);
    return [
        'labels' => array_keys($cohorts),
        'values' => array_values(array_map(fn($c) => $c['total'] > 0 ? round($c['active'] / $c['total'] * 100, 1) : 0, $cohorts)),
    ];
}

/**
 * Lifetime Value klienta — średni sumaryczny przychód per klient
 */
function stats_client_ltv($clients) {
    $pdo = db_get_pdo();
    if (!$pdo || empty($clients)) return 0;
    try {
        $stmt = $pdo->query(
            "SELECT U_NRKLIENTA, SUM(FV_SUMABRUTTO) AS total
             FROM vFvLista
             WHERE FV_DATAWYSTAWIENIA <> '0000-00-00' AND FV_SUMABRUTTO > 0
             GROUP BY U_NRKLIENTA"
        );
        $rows = $stmt->fetchAll();
        if (empty($rows)) return 0;
        return round(array_sum(array_column($rows, 'total')) / count($rows), 2);
    } catch (PDOException $e) { return 0; }
}

/**
 * Typ podmiotu — osoby fizyczne vs firmy
 */
function stats_client_type_distribution($clients) {
    $ind = 0; $comp = 0;
    foreach ($clients as $c) {
        if ((int)($c['podmiot'] ?? 0) === 1) $comp++; else $ind++;
    }
    return ['individuals' => $ind, 'companies' => $comp];
}

/**
 * Churn rate — % deaktywowanych klientów miesięcznie
 */
function stats_churn_rate($clients, $months = 12) {
    $data = [];
    $now = time();
    for ($i = $months - 1; $i >= 0; $i--) {
        $key = date('Y-m', strtotime("-{$i} months", $now));
        $data[$key] = 0;
    }

    $pdo = db_get_pdo();
    if (!$pdo) return ['labels' => array_keys($data), 'values' => array_values($data)];

    try {
        $first_month = array_key_first($data) . '-01';
        $stmt = $pdo->prepare(
            "SELECT LEFT(U_DATADEAKTYWACJI, 7) AS m, COUNT(*) AS cnt
             FROM vUserdb
             WHERE U_DATADEAKTYWACJI >= ? AND U_DATADEAKTYWACJI <> '0000-00-00'
             GROUP BY m ORDER BY m"
        );
        $stmt->execute([$first_month]);
        foreach ($stmt->fetchAll() as $row) {
            if (isset($data[$row['m']])) $data[$row['m']] = (int)$row['cnt'];
        }
    } catch (PDOException $e) {
        ibok_log("stats_churn_rate error: " . $e->getMessage(), 'error');
    }

    $total = max(count($clients), 1);
    $values = array_map(fn($cnt) => round($cnt / $total * 100, 2), $data);
    return ['labels' => array_keys($data), 'values' => array_values($values)];
}

/**
 * Rozkład nadpłat — przedziały kwot
 */
function stats_overpayment_distribution($clients) {
    $buckets = ['1-50' => 0, '51-200' => 0, '201-500' => 0, '500+' => 0];
    $total = 0.0; $count = 0;
    foreach ($clients as $c) {
        $n = (float)str_replace(',', '.', $c['nadplata'] ?? '0');
        if ($n <= 0) continue;
        $count++; $total += $n;
        if ($n <= 50) $buckets['1-50']++;
        elseif ($n <= 200) $buckets['51-200']++;
        elseif ($n <= 500) $buckets['201-500']++;
        else $buckets['500+']++;
    }
    return ['labels' => array_keys($buckets), 'values' => array_values($buckets), 'total' => $total, 'count' => $count];
}

/**
 * Długość umów — rozkład: bezterminowa, do 12 mies., 12-24, 24+
 */
function stats_contract_duration_distribution($clients) {
    $r = ['indefinite' => 0, 'up_to_12' => 0, '12_24' => 0, '24_plus' => 0];
    foreach ($clients as $c) {
        $od = $c['umowa_od'] ?? '';
        $do = $c['umowa_do'] ?? '';
        if (empty($od) || $od === '0000-00-00') continue;
        if (empty($do) || $do === '0000-00-00') { $r['indefinite']++; continue; }
        $ts_od = strtotime($od); $ts_do = strtotime($do);
        if ($ts_od === false || $ts_do === false) continue;
        $m = (int)round(($ts_do - $ts_od) / (30 * 86400));
        if ($m <= 12) $r['up_to_12']++;
        elseif ($m <= 24) $r['12_24']++;
        else $r['24_plus']++;
    }
    return $r;
}

/**
 * Średni czas realizacji zleceń w dniach, po miesiącach
 */
function stats_service_avg_resolution($zlecenia, $months = 12) {
    $data = [];
    $now = time();
    for ($i = $months - 1; $i >= 0; $i--) {
        $key = date('Y-m', strtotime("-{$i} months", $now));
        $data[$key] = ['sum' => 0, 'count' => 0];
    }
    foreach ($zlecenia as $z) {
        $m = substr($z['data'] ?? '', 0, 7);
        if (!isset($data[$m])) continue;
        $dni = (int)($z['dni'] ?? 0);
        if ($dni > 0 && !empty($z['data_zakonczenia'])) {
            $data[$m]['sum'] += $dni;
            $data[$m]['count']++;
        }
    }
    $values = array_map(fn($d) => $d['count'] > 0 ? round($d['sum'] / $d['count'], 1) : 0, $data);
    return ['labels' => array_keys($data), 'values' => array_values($values)];
}

/**
 * Zlecenia wg kategorii/zakresu — top 10
 */
function stats_service_category_distribution($zlecenia) {
    $cats = [];
    foreach ($zlecenia as $z) {
        $zakres = trim($z['tresc'] ?? '');
        if (empty($zakres)) $zakres = '?';
        if (mb_strlen($zakres) > 30) $zakres = mb_substr($zakres, 0, 30) . '…';
        $cats[$zakres] = ($cats[$zakres] ?? 0) + 1;
    }
    arsort($cats);
    return array_slice($cats, 0, 10, true);
}

/**
 * Aktywność logowań wg godziny dnia
 */
function stats_login_hourly($days = 30) {
    $hours = array_fill(0, 24, 0);
    $dir = defined('IBOK_LOGS_DIR') ? IBOK_LOGS_DIR : __DIR__ . '/../logs';
    $log_files = glob($dir . '/ibok_*.log');
    if (empty($log_files)) return ['labels' => array_keys($hours), 'values' => array_values($hours)];

    rsort($log_files);
    $cutoff = date('Y-m-d', strtotime("-{$days} days"));
    $pattern = '/^(\d{4}-\d{2}-\d{2}) (\d{2}):\d{2}:\d{2} \[info\].*Login (admina|klienta) udany/';

    foreach (array_slice($log_files, 0, 3) as $f) {
        $lines = @file($f, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        if (!$lines) continue;
        foreach ($lines as $line) {
            if (preg_match($pattern, $line, $m) && $m[1] >= $cutoff) {
                $hours[(int)$m[2]]++;
            }
        }
    }

    return ['labels' => array_keys($hours), 'values' => array_values($hours)];
}

/**
 * Klienci zagrożeni odejściem — zalegają i umowa kończy się w ciągu 90 dni
 */
function stats_at_risk_clients($clients) {
    $at_risk = [];
    $today_ts = strtotime('today');
    foreach ($clients as $cid => $c) {
        if ($c['active_flag'] !== '1') continue;
        $kwota = (float)str_replace(',', '.', $c['saldo_zalegle_raw'] ?? '0');
        if ($kwota <= 0) continue;
        $umowa_do = $c['umowa_do'] ?? '';
        if (empty($umowa_do) || $umowa_do === '0000-00-00') continue;
        $end_ts = strtotime($umowa_do);
        if ($end_ts === false) continue;
        $dni_do = (int)floor(($end_ts - $today_ts) / 86400);
        if ($dni_do >= 0 && $dni_do <= 90) {
            $at_risk[] = ['id' => $cid, 'nazwisko' => $c['nazwisko'], 'kwota' => $kwota, 'umowa_do' => $umowa_do, 'dni_do' => $dni_do];
        }
    }
    usort($at_risk, fn($a, $b) => $a['dni_do'] - $b['dni_do']);
    return $at_risk;
}

/**
 * Scoring klientów — dane do scatter chart (staż vs zaległość)
 */
function stats_client_scoring($clients, $limit = 50) {
    $points = [];
    $today_ts = strtotime('today');
    foreach ($clients as $cid => $c) {
        if ($c['active_flag'] !== '1') continue;
        $kwota = (float)str_replace(',', '.', $c['saldo_zalegle_raw'] ?? '0');
        if ($kwota <= 0) continue;
        $od = $c['umowa_od'] ?? '';
        if (empty($od) || $od === '0000-00-00') continue;
        $ts_od = strtotime($od);
        if ($ts_od === false) continue;
        $years = round(($today_ts - $ts_od) / (365.25 * 86400), 1);
        $radius = min(20, max(3, sqrt($kwota) / 2));
        $points[] = ['x' => $years, 'y' => $kwota, 'r' => round($radius, 1), 'label' => $c['nazwisko']];
    }
    usort($points, fn($a, $b) => $b['y'] <=> $a['y']);
    return array_slice($points, 0, $limit);
}

// === Nowe funkcje analityczne (batch 2) ===

/**
 * Przychód wg taryf — top 10 taryf z największym przychodem (12 mies.)
 */
function stats_revenue_by_tariff($clients) {
    $pdo = db_get_pdo();
    if (!$pdo) return [];
    $tmap = [];
    foreach ($clients as $cid => $c) {
        $t = trim($c['dl_dzien'] ?? '');
        $tmap[$cid] = ($t !== '') ? $t : '?';
    }
    try {
        $stmt = $pdo->query("SELECT U_NRKLIENTA, SUM(FV_SUMABRUTTO) AS total FROM vFvLista WHERE FV_DATAWYSTAWIENIA >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH) AND FV_DATAWYSTAWIENIA <> '0000-00-00' AND FV_SUMABRUTTO > 0 GROUP BY U_NRKLIENTA");
        $rev = [];
        foreach ($stmt->fetchAll() as $row) {
            $t = $tmap[(string)$row['U_NRKLIENTA']] ?? '?';
            $rev[$t] = ($rev[$t] ?? 0) + (float)$row['total'];
        }
        arsort($rev);
        return array_slice($rev, 0, 10, true);
    } catch (PDOException $e) { return []; }
}

/**
 * Średnia wartość faktury po miesiącach
 */
function stats_avg_invoice_value($months = 12) {
    $data = [];
    $now = time();
    for ($i = $months - 1; $i >= 0; $i--) { $k = date('Y-m', strtotime("-{$i} months", $now)); $data[$k] = 0; }
    $pdo = db_get_pdo();
    if (!$pdo) return ['labels' => array_keys($data), 'values' => array_values($data)];
    try {
        $stmt = $pdo->prepare("SELECT LEFT(FV_DATAWYSTAWIENIA, 7) AS m, AVG(FV_SUMABRUTTO) AS avg_val FROM vFvLista WHERE FV_DATAWYSTAWIENIA >= ? AND FV_DATAWYSTAWIENIA <> '0000-00-00' AND FV_SUMABRUTTO > 0 GROUP BY m ORDER BY m");
        $stmt->execute([array_key_first($data) . '-01']);
        foreach ($stmt->fetchAll() as $r) { if (isset($data[$r['m']])) $data[$r['m']] = round((float)$r['avg_val'], 2); }
    } catch (PDOException $e) {}
    return ['labels' => array_keys($data), 'values' => array_values($data)];
}

/**
 * Średni czas opłaty wg miesiąca (dni od faktury do wpłaty)
 */
function stats_payment_time_by_month($months = 12) {
    $data = [];
    $now = time();
    for ($i = $months - 1; $i >= 0; $i--) { $k = date('Y-m', strtotime("-{$i} months", $now)); $data[$k] = 0; }
    $pdo = db_get_pdo();
    if (!$pdo) return ['labels' => array_keys($data), 'values' => array_values($data)];
    try {
        $stmt = $pdo->prepare("SELECT LEFT(O_DATAWPLATY, 7) AS m, AVG(DATEDIFF(O_DATAWPLATY, FV_DATAWYSTAWIENIA)) AS avg_days FROM vFvLista f JOIN vOplatyLista o ON f.U_NRKLIENTA = o.U_NRKLIENTA WHERE O_DATAWPLATY >= ? AND FV_DATAWYSTAWIENIA <> '0000-00-00' AND O_DATAWPLATY <> '0000-00-00' AND O_DATAWPLATY >= FV_DATAWYSTAWIENIA GROUP BY m ORDER BY m");
        $stmt->execute([array_key_first($data) . '-01']);
        foreach ($stmt->fetchAll() as $r) { if (isset($data[$r['m']])) $data[$r['m']] = round((float)$r['avg_days'], 1); }
    } catch (PDOException $e) {}
    return ['labels' => array_keys($data), 'values' => array_values($data)];
}

/**
 * ARPU — średni przychód na aktywnego klienta miesięcznie
 */
function stats_arpu($clients, $months = 12) {
    $data = [];
    $now = time();
    for ($i = $months - 1; $i >= 0; $i--) { $k = date('Y-m', strtotime("-{$i} months", $now)); $data[$k] = 0; }
    $active = 0;
    foreach ($clients as $c) { if ($c['active_flag'] === '1') $active++; }
    if ($active == 0) return ['labels' => array_keys($data), 'values' => array_values($data)];
    $pdo = db_get_pdo();
    if (!$pdo) return ['labels' => array_keys($data), 'values' => array_values($data)];
    try {
        $stmt = $pdo->prepare("SELECT LEFT(FV_DATAWYSTAWIENIA, 7) AS m, SUM(FV_SUMABRUTTO) AS total FROM vFvLista WHERE FV_DATAWYSTAWIENIA >= ? AND FV_DATAWYSTAWIENIA <> '0000-00-00' AND FV_SUMABRUTTO > 0 GROUP BY m ORDER BY m");
        $stmt->execute([array_key_first($data) . '-01']);
        foreach ($stmt->fetchAll() as $r) { if (isset($data[$r['m']])) $data[$r['m']] = round((float)$r['total'] / $active, 2); }
    } catch (PDOException $e) {}
    return ['labels' => array_keys($data), 'values' => array_values($data)];
}

/**
 * Kumulatywny cash flow — narastająco: wpłaty minus korekty
 */
function stats_cumulative_cashflow($months = 12) {
    $data = [];
    $now = time();
    for ($i = $months - 1; $i >= 0; $i--) { $k = date('Y-m', strtotime("-{$i} months", $now)); $data[$k] = 0; }
    $pdo = db_get_pdo();
    if (!$pdo) return ['labels' => array_keys($data), 'values' => array_values($data)];
    $first = array_key_first($data) . '-01';
    try {
        $stmt = $pdo->prepare("SELECT LEFT(O_DATAWPLATY, 7) AS m, SUM(O_KWOTA) AS total FROM vOplatyLista WHERE O_DATAWPLATY >= ? AND O_DATAWPLATY <> '0000-00-00' GROUP BY m ORDER BY m");
        $stmt->execute([$first]);
        foreach ($stmt->fetchAll() as $r) { if (isset($data[$r['m']])) $data[$r['m']] = (float)$r['total']; }
        $stmt = $pdo->prepare("SELECT LEFT(FV_DATAWYSTAWIENIA, 7) AS m, SUM(ABS(FV_SUMABRUTTO)) AS total FROM vFvLista WHERE FV_DATAWYSTAWIENIA >= ? AND FV_DATAWYSTAWIENIA <> '0000-00-00' AND FV_SUMABRUTTO < 0 GROUP BY m ORDER BY m");
        $stmt->execute([$first]);
        foreach ($stmt->fetchAll() as $r) { if (isset($data[$r['m']])) $data[$r['m']] -= (float)$r['total']; }
    } catch (PDOException $e) {}
    $sum = 0; $cumul = [];
    foreach ($data as $k => $v) { $sum += $v; $cumul[$k] = round($sum, 2); }
    return ['labels' => array_keys($cumul), 'values' => array_values($cumul)];
}

/**
 * Staż klientów — rozkład: <1 rok, 1-3, 3-5, 5-10, 10+ lat
 */
function stats_client_tenure_dist($clients) {
    $b = ['<1' => 0, '1-3' => 0, '3-5' => 0, '5-10' => 0, '10+' => 0];
    $today = strtotime('today');
    foreach ($clients as $c) {
        $od = $c['umowa_od'] ?? '';
        if (empty($od) || $od === '0000-00-00') continue;
        $ts = strtotime($od);
        if ($ts === false) continue;
        $y = ($today - $ts) / (365.25 * 86400);
        if ($y < 1) $b['<1']++;
        elseif ($y < 3) $b['1-3']++;
        elseif ($y < 5) $b['3-5']++;
        elseif ($y < 10) $b['5-10']++;
        else $b['10+']++;
    }
    return $b;
}

/**
 * Status umów klientów — aktywna/wygasa/wygasła/bezterminowa
 */
function stats_contract_status_dist($clients) {
    $r = ['active' => 0, 'expiring' => 0, 'expired' => 0, 'indefinite' => 0];
    $today = strtotime('today');
    $t90 = strtotime('+90 days');
    foreach ($clients as $c) {
        if ($c['active_flag'] !== '1') continue;
        $do = $c['umowa_do'] ?? '';
        if (empty($do) || $do === '0000-00-00') { $r['indefinite']++; continue; }
        $end = strtotime($do);
        if ($end === false) continue;
        if ($end < $today) $r['expired']++;
        elseif ($end <= $t90) $r['expiring']++;
        else $r['active']++;
    }
    return $r;
}

/**
 * Heatmapa rejestracji — w jakich miesiącach roku przybywa klientów
 */
function stats_registration_heatmap($clients) {
    $months = array_fill(1, 12, 0);
    foreach ($clients as $c) {
        $od = $c['umowa_od'] ?? '';
        if (empty($od) || $od === '0000-00-00') continue;
        $m = (int)date('n', strtotime($od));
        if ($m >= 1 && $m <= 12) $months[$m]++;
    }
    return array_values($months);
}

/**
 * Top 10 klientów wg przychodu (suma faktur)
 */
function stats_top_revenue_clients($limit = 10) {
    $pdo = db_get_pdo();
    if (!$pdo) return [];
    try {
        $stmt = $pdo->query("SELECT f.U_NRKLIENTA, u.vU_NazwiskoImie AS nazwisko, SUM(f.FV_SUMABRUTTO) AS total FROM vFvLista f LEFT JOIN vUserdb u ON f.U_NRKLIENTA = u.U_NRKLIENTA WHERE f.FV_DATAWYSTAWIENIA <> '0000-00-00' AND f.FV_SUMABRUTTO > 0 GROUP BY f.U_NRKLIENTA, u.vU_NazwiskoImie ORDER BY total DESC LIMIT " . (int)$limit);
        $r = [];
        foreach ($stmt->fetchAll() as $row) { $r[] = ['nazwisko' => $row['nazwisko'] ?: '?', 'kwota' => round((float)$row['total'], 2)]; }
        return $r;
    } catch (PDOException $e) { return []; }
}

/**
 * Klienci bez aktywnej umowy (umowa wygasła, ale klient aktywny)
 */
function stats_clients_no_contract($clients) {
    $count = 0;
    $today = strtotime('today');
    foreach ($clients as $c) {
        if ($c['active_flag'] !== '1') continue;
        $do = $c['umowa_do'] ?? '';
        if (empty($do) || $do === '0000-00-00') continue;
        $end = strtotime($do);
        if ($end !== false && $end < $today) $count++;
    }
    return $count;
}

/**
 * Trend bazy klientów — kumulatywna liczba klientów (24 mies.)
 */
function stats_cumulative_clients($clients, $months = 24) {
    $data = [];
    $now = time();
    for ($i = $months - 1; $i >= 0; $i--) { $k = date('Y-m', strtotime("-{$i} months", $now)); $data[$k] = 0; }
    $first = array_key_first($data);
    $before = 0;
    $monthly = [];
    foreach ($clients as $c) {
        $od = $c['umowa_od'] ?? '';
        if (empty($od) || $od === '0000-00-00') { $before++; continue; }
        $cm = date('Y-m', strtotime($od));
        if ($cm < $first) $before++;
        else $monthly[$cm] = ($monthly[$cm] ?? 0) + 1;
    }
    $cum = $before;
    foreach ($data as $m => &$v) { $cum += ($monthly[$m] ?? 0); $v = $cum; }
    return ['labels' => array_keys($data), 'values' => array_values($data)];
}

/**
 * Zlecenia wg czasu realizacji — szybkie (<3 dni), normalne (3-14), długie (>14), otwarte
 */
function stats_tickets_by_priority($zlecenia) {
    $r = ['fast' => 0, 'normal' => 0, 'slow' => 0, 'open' => 0];
    foreach ($zlecenia as $z) {
        if (empty($z['data_zakonczenia'])) { $r['open']++; continue; }
        $dni = (int)($z['dni'] ?? 0);
        if ($dni <= 3) $r['fast']++;
        elseif ($dni <= 14) $r['normal']++;
        else $r['slow']++;
    }
    return $r;
}

/**
 * Zlecenia wg dnia tygodnia (Pon-Nd)
 */
function stats_tickets_by_weekday($zlecenia) {
    $days = array_fill(0, 7, 0);
    foreach ($zlecenia as $z) {
        $date = $z['data'] ?? '';
        if (empty($date) || $date === '0000-00-00') continue;
        $ts = strtotime($date);
        if ($ts === false) continue;
        $dow = (int)date('N', $ts) - 1;
        $days[$dow]++;
    }
    return array_values($days);
}

/**
 * Unikalne sesje administratorów dziennie (wg IP z logów)
 */
function stats_unique_admin_sessions($days = 30) {
    $data = [];
    $now = time();
    for ($i = $days - 1; $i >= 0; $i--) { $k = date('Y-m-d', strtotime("-{$i} days", $now)); $data[$k] = []; }
    $dir = defined('IBOK_LOGS_DIR') ? IBOK_LOGS_DIR : __DIR__ . '/../logs';
    $files = glob($dir . '/ibok_*.log');
    if (empty($files)) return ['labels' => array_keys($data), 'values' => array_fill(0, $days, 0)];
    rsort($files);
    $pattern = '/^(\d{4}-\d{2}-\d{2}) \d{2}:\d{2}:\d{2} \[info\].*Login admina udany.*?(\d+\.\d+\.\d+\.\d+)/';
    foreach (array_slice($files, 0, 3) as $f) {
        $lines = @file($f, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        if (!$lines) continue;
        foreach ($lines as $line) {
            if (preg_match($pattern, $line, $m)) {
                if (isset($data[$m[1]])) $data[$m[1]][$m[2]] = true;
            }
        }
    }
    $values = array_map(fn($ips) => count($ips), $data);
    return ['labels' => array_keys($data), 'values' => array_values($values)];
}

/**
 * Podsumowanie blacklisty — dane z głównego panelu administracyjnego
 */
function stats_blacklist_summary() {
    $cfg = IBOK_ROOT . '/../../cfg';
    $r = ['manual' => 0, 'feeds_active' => 0, 'feeds_total' => 0, 'feed_details' => []];
    $bl = $cfg . '/blacklist.cfg';
    if (file_exists($bl)) { $d = @json_decode(file_get_contents($bl), true); if (is_array($d)) $r['manual'] = count($d); }
    $ff = $cfg . '/blacklist_feeds.cfg';
    if (file_exists($ff)) {
        $d = @json_decode(file_get_contents($ff), true);
        if (is_array($d)) {
            foreach ($d as $name => $info) {
                $r['feeds_total']++;
                if (!empty($info['enabled'])) { $r['feeds_active']++; $r['feed_details'][$name] = (int)($info['count'] ?? 0); }
            }
        }
    }
    return $r;
}

/**
 * Zablokowane kraje z geo-blokady — dane z głównego panelu
 */
function stats_geoblock_countries() {
    $cfg = IBOK_ROOT . '/../../cfg';
    $f = $cfg . '/geoblock.cfg';
    if (!file_exists($f)) return [];
    $d = @json_decode(file_get_contents($f), true);
    if (!is_array($d)) return [];
    $countries = [];
    foreach ($d as $code => $info) {
        if (!empty($info['enabled'])) { $countries[strtoupper($code)] = (int)($info['count'] ?? 0); }
    }
    arsort($countries);
    return $countries;
}

/**
 * Status monitoringu urządzeń — dane z głównego panelu
 */
function stats_monit_devices() {
    $cfg = IBOK_ROOT . '/../../cfg';
    $r = ['online' => 0, 'offline' => 0, 'unknown' => 0, 'total' => 0];
    $mf = $cfg . '/monit.cfg';
    if (!file_exists($mf)) return $r;
    $monit = @json_decode(file_get_contents($mf), true);
    if (!is_array($monit)) return $r;
    $status = [];
    $sf = $cfg . '/monit_status.json';
    if (file_exists($sf)) { $status = @json_decode(file_get_contents($sf), true) ?: []; }
    foreach ($monit as $dev) {
        if (empty($dev['enabled'])) continue;
        $r['total']++;
        $ip = $dev['ip'] ?? '';
        $s = $status[$ip] ?? null;
        if ($s === null) $r['unknown']++;
        elseif (!empty($s['status']) && $s['status'] === 'up') $r['online']++;
        else $r['offline']++;
    }
    return $r;
}
