<?php
// ============================================================
//  MediCore — Stock Sync API
//  File: api/stock-sync.php
//
//  Lightweight fast-poll endpoint (see assets/stock-sync.js) that
//  lets every open terminal/tab pick up stock changes made by OTHER
//  terminals — sales, purchases, sale returns, purchase returns —
//  within well under a second, without a page reload and without a
//  persistent connection (WebSocket or SSE).
//
//  Persistent push (SSE) was deliberately tried and reverted
//  elsewhere in this exact app (see api/status.php / assets/topbar.js):
//  XAMPP's default Windows Apache build has a small worker-thread
//  pool, and every open tab holding one PHP request open continuously
//  (even a "bounded, auto-reconnecting" SSE stream) can exhaust it,
//  making unrelated requests across the WHOLE app hang with no error.
//  Short one-shot polls avoid that failure mode structurally — a
//  request completes and releases its worker in milliseconds either
//  way, no matter how many tabs/terminals are open.
//
//  What makes this fast enough to poll aggressively (assets/stock-
//  sync.js polls every ~600ms, not the old 2.5s): every stock-
//  mutating endpoint (sales.php, purchases.php, sale-returns.php,
//  purchase-returns.php) now writes a row to `stock_events`
//  (includes/db.php: publish_stock_event()) inside the SAME
//  transaction as its `products` UPDATE. That table's auto-increment
//  `id` is a strictly monotonic, gap-free cursor — `WHERE id > ?` is
//  a trivial indexed range scan, unlike the old `WHERE updated_at > ?`
//  scan over the whole `products` table.
// ============================================================
require_once '../includes/db.php';
require_once '../includes/auth.php';
require_login();

// Release the session lock immediately — this fires several times a
// second from every open tab; without this, it would serialize behind
// (and force every other tab's requests to queue behind) PHP's
// default file-based session lock.
session_write_close();

header('Content-Type: application/json');

try {
    ensure_soft_delete('products');
    ensure_stock_events_table();

    $sinceId = max(0, (int) ($_GET['since_id'] ?? 0));

    // First-ever poll from a freshly loaded page: there's no cursor yet.
    // Jump straight to "now" instead of replaying the entire event
    // history — a client that just opened doesn't need every stock
    // change since the table was created, only ones from here on.
    if ($sinceId === 0 && trim((string)($_GET['since_id'] ?? '')) === '') {
        $sinceId = (int) (db_val("SELECT MAX(id) FROM stock_events") ?? 0);
        echo json_encode(['success' => true, 'since_id' => $sinceId, 'changed' => [],
            'low_stock' => null, 'out_of_stock' => null, 'stock_value' => null]);
        exit;
    }

    $rows = db_rows(
        "SELECT se.id, se.product_id, se.event_type, se.source_type, se.new_stock,
                p.name, p.low_stock_alert, p.sale_price
           FROM stock_events se
           JOIN products p ON p.id = se.product_id
          WHERE se.id > ?
          ORDER BY se.id ASC
          LIMIT 500",
        [$sinceId]
    );

    // Collapse to one row per product (keep the latest — the current
    // figures are what the UI needs, not a change log) and track the
    // highest id seen so the client's next poll picks up right where
    // this one left off, even if several events for the same product
    // landed in this single batch.
    $nextSinceId = $sinceId;
    $changedByProduct = [];
    foreach ($rows as $r) {
        $nextSinceId = max($nextSinceId, (int) $r['id']);
        $changedByProduct[(int) $r['product_id']] = [
            'id'              => (int) $r['product_id'],
            'name'            => $r['name'],
            'stock_pcs'       => (int) $r['new_stock'],
            'low_stock_alert' => (int) $r['low_stock_alert'],
            'sale_price'      => (float) $r['sale_price'],
            'event_type'      => $r['event_type'],
            'source_type'     => $r['source_type'],
        ];
    }
    $changed = array_values($changedByProduct);

    // Aggregate counts are only recomputed when something actually
    // changed in this batch — otherwise every terminal polling every
    // ~600ms would run 3 full aggregate queries a second each for no
    // reason. `null` here means "unchanged, keep whatever you already
    // have" (see assets/stock-sync.js) rather than "zero".
    $lowStock = $outStock = $stockValue = null;
    if ($changed) {
        $lowStock = (int) db_val(
            "SELECT COUNT(*) FROM products
              WHERE is_active = 1 AND is_deleted = 0 AND stock_pcs > 0
                AND low_stock_alert > 0 AND stock_pcs < low_stock_alert"
        );
        $outStock = (int) db_val(
            "SELECT COUNT(*) FROM products
              WHERE is_active = 1 AND is_deleted = 0 AND stock_pcs <= 0"
        );
        $stockValue = (float) db_val(
            "SELECT COALESCE(SUM(stock_pcs * sale_price),0) FROM products
              WHERE is_active = 1 AND is_deleted = 0"
        );
    }

    echo json_encode([
        'success'      => true,
        'since_id'     => $nextSinceId,
        'changed'      => $changed,
        'low_stock'    => $lowStock,
        'out_of_stock' => $outStock,
        'stock_value'  => $stockValue,
    ]);
} catch (Throwable $e) {
    echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
