<?php
/**
 * @author Ibrahim Al-Mansour
 * @version 2.0
 * 
 * نقطة نهاية موحدة لجلب المخطط الزمني والمسار المحاذي للطريق.
 * - تحل محل: get_locations.php, get_device_stops_optimized.php, snap_route.php
 * - تقلل عدد طلبات الشبكة من 3 إلى 1.
 * - تقبل إعدادات التصنيف من الواجهة الأمامية.
 */

// ✅ بدء output buffering لمنع أي محتوى إضافي قبل JSON
ob_start();

// ✅ دالة shutdown لمعالجة الأخطاء القاتلة (Fatal Errors)
register_shutdown_function(function() {
    $error = error_get_last();
    if ($error && ($error['type'] === E_ERROR || $error['type'] === E_PARSE || $error['type'] === E_CORE_ERROR || $error['type'] === E_COMPILE_ERROR)) {
        // تنظيف أي مخرجات سابقة
        if (ob_get_length()) ob_clean();
        
        http_response_code(500);
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode([
            'status' => 'error',
            'message' => 'حدث خطأ قاتل في الخادم: ' . $error['message'],
            'file' => $error['file'],
            'line' => $error['line']
        ], JSON_UNESCAPED_UNICODE);
        exit;
    }
});

session_start();
require_once __DIR__ . '/config.php';

// ✅ دالة مساعدة عامة لتنظيف وتنسيق حقل الوقت القادم من قاعدة البيانات
if (!function_exists('normalize_timestamp_string')) {
    function normalize_timestamp_string($value) {
        if ($value instanceof DateTimeInterface) {
            return $value->format('Y-m-d H:i:s');
        }
        $str = (string)$value;
        // إزالة الأحرف غير القابلة للطباعة (التي تسبب رموز غريبة)
        $str = preg_replace('/[^\x20-\x7E]/u', '', $str);
        $str = trim($str);

        if ($str === '') {
            return null;
        }

        // إذا كانت قيمة رقمية (ثواني يونكس)
        if (preg_match('/^\d{10}$/', $str)) {
            return date('Y-m-d H:i:s', (int)$str);
        }

        // إذا كانت بالفعل بصيغة قياسية
        if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $str)) {
            return $str;
        }

        // محاولة تفسير أي صيغة أخرى
        $ts = strtotime($str);
        if ($ts !== false) {
            return date('Y-m-d H:i:s', $ts);
        }

        // كحل أخير: إذا لم يكن النص قابلاً للتحويل إلى وقت صالح نعتبره غير صالح
        // حتى لا نعيد قيماً مثل 0.71 أو إحداثيات كنص وقت
        return null;
    }
}

// ✅ الحصول على instance قاعدة البيانات لإغلاقها لاحقاً
// ✅ يتم إنشاء $dbInstance في config.php، نحتاج فقط للوصول إليه

// ✅ تحسين: إعدادات PHP للأداء
ini_set('max_execution_time', 60); // ✅ زيادة timeout إلى 60 ثانية
ini_set('memory_limit', '512M'); // ✅ زيادة الذاكرة
set_time_limit(60); // ✅ timeout للعملية

// ✅ إيقاف عرض الأخطاء في output (لكن تسجيلها في error_log)
ini_set('display_errors', 0);
ini_set('log_errors', 1);

header('Content-Type: application/json; charset=utf-8');
header("Access-Control-Allow-Origin: *");

// التحقق من تسجيل الدخول
if (!isset($_SESSION['user_id'])) {
    http_response_code(401);
    echo json_encode(['status' => 'error', 'message' => 'غير مسموح، يرجى تسجيل الدخول']);
    exit;
}

$input = json_decode(file_get_contents('php://input'), true);

if (json_last_error() !== JSON_ERROR_NONE) {
    http_response_code(400);
    echo json_encode(['status' => 'error', 'message' => 'بيانات JSON غير صالحة: ' . json_last_error_msg()]);
    exit;
}

$deviceId = $input['device_id'] ?? null;
$fromDate = $input['from_date'] ?? null;
$toDate   = $input['to_date']   ?? null;
$date     = $input['date']      ?? null;
$config   = $input['config']    ?? [];

if (empty($deviceId)) {
    http_response_code(400);
    echo json_encode(['status' => 'error', 'message' => 'معرف الجهاز مطلوب']);
    exit;
}

class UnifiedTimelineService {
    private $conn;
    private $config;
    private $userPlaces = [];

    public function __construct($connection, $config) {
        $this->conn = $connection;
        // دمج الإعدادات الممررة مع القيم الافتراضية
        $this->config = array_merge([
            'stopMinDurationMin'      => 1.5,
            'stationaryThresholdM'    => 30,
            'stopMaxSpeedKmh'         => 4,
            'drivingMinSpeedKmh'      => 7,
            'drivingMinSegmentMin'    => 2,
            'drivingMinDistanceM'     => 120,
            'noiseMaxSpeedKmh'        => 220,
            'noiseMaxJumpMeters'      => 1500,
            'noiseReturnMeters'       => 200,
            'noiseReturnWindowSec'    => 300,
            // إعدادات تحكم من الواجهة الأمامية
            'enablePlaceNames'        => true,
            'enableMapMatching'       => true,
            'maxMapMatchingPoints'    => 500,
            'maxGeocodeStops'         => 10,
        ], $config);
    }

    public function getTimelineAndRoute($deviceId, $start, $end, $userId) {
        $startTime = microtime(true);
        
        // ✅ تحسين: تعيين timeout أطول للعملية
        set_time_limit(30); // 30 ثانية بدلاً من الافتراضي

        $this->loadUserPlaces($userId);
        $rawPoints = $this->fetchRawPoints($deviceId, $start, $end);
        
        // ✅ تحسين: تسجيل عدد النقاط الخام للتصحيح
        error_log("🔍 [UnifiedTimeline] Device: $deviceId, Raw points fetched: " . count($rawPoints) . ", Time range: $start to $end");
        
        // ✅ تحسين: التحقق من صحة النقاط قبل المعالجة
        $validRawPoints = [];
        foreach ($rawPoints as $point) {
            $lat = isset($point['latitude']) ? floatval($point['latitude']) : 0;
            $lng = isset($point['longitude']) ? floatval($point['longitude']) : 0;
            
            // ✅ التحقق من أن الإحداثيات صالحة (ليست 0,0 وليست خارج النطاق المعقول)
            if ($lat != 0 && $lng != 0 && 
                $lat >= -90 && $lat <= 90 && 
                $lng >= -180 && $lng <= 180 &&
                isset($point['timestamp'])) {
                $validRawPoints[] = $point;
            }
        }
        
        error_log("🔍 [UnifiedTimeline] Valid raw points: " . count($validRawPoints) . " (filtered invalid: " . (count($rawPoints) - count($validRawPoints)) . ")");
        
        // إذا تم تصفية جميع النقاط تقريباً بسبب الإحداثيات، لكن لدينا نقاط خام، نستخدم النقاط الخام بدلاً من إرجاع لا شيء
        if (count($validRawPoints) < 2) {
            if (count($rawPoints) >= 2) {
                error_log("⚠️ [UnifiedTimeline] Not enough valid points after coordinate filter, falling back to raw points (may include 0,0 or out-of-range coords)");
                $validRawPoints = $rawPoints;
            } else {
                return $this->buildEmptyResponse($startTime, [
                    'raw_points' => count($rawPoints),
                    'valid_raw_points' => count($validRawPoints),
                    'filtered_points' => 0,
                    'noise_removed' => 0,
                    'message' => 'لا توجد نقاط كافية في الفترة الزمنية المحددة'
                ]);
            }
        }
        
        // ✅ تحسين: تقليل عدد النقاط المعالجة إذا كانت كثيرة جداً
        if (count($validRawPoints) > 2000) {
            // أخذ عينة من النقاط (كل نقطة N)
            $step = ceil(count($validRawPoints) / 1500);
            $validRawPoints = array_filter($validRawPoints, function($key) use ($step) {
                return $key % $step === 0;
            }, ARRAY_FILTER_USE_KEY);
            $validRawPoints = array_values($validRawPoints); // إعادة ترقيم المصفوفة
        }
        
        $cleanPoints = $this->filterGpsNoise($validRawPoints);
        $noiseRemoved = max(0, count($validRawPoints) - count($cleanPoints));
        
        error_log("🔍 [UnifiedTimeline] After noise filter: " . count($cleanPoints) . " points (removed: $noiseRemoved)");

        // ✅ تحسين: تقليل الحد الأدنى من 2 إلى 1 نقطة (يمكن عرض نقطة واحدة على الأقل)
        if (count($cleanPoints) < 1) {
            // ✅ إذا كانت هناك نقاط خام صالحة لكن تمت إزالتها كلها، نستخدم النقاط الخام
            if (count($validRawPoints) >= 1) {
                error_log("⚠️ [UnifiedTimeline] All points filtered as noise, using raw points instead");
                $cleanPoints = $validRawPoints;
            } else {
                return $this->buildEmptyResponse($startTime, [
                    'raw_points' => count($rawPoints),
                    'valid_raw_points' => count($validRawPoints),
                    'filtered_points' => count($cleanPoints),
                    'noise_removed' => $noiseRemoved,
                    'message' => 'لا توجد نقاط كافية بعد التصفية'
                ]);
            }
        }

        /**
         * ✅ ملاحظة مهمة:
         * في النظام القديم (الذي يعطيك التوقفات الصحيحة كما في الصور 1 و2)
         * كان اعتماد كشف التوقفات يتم مباشرة من قاعدة البيانات عبر GROUP BY
         * بدون أي خوارزميات حركة/سرعة معقدة.
         *
         * هنا نعيد استخدام نفس منطق النظام القديم (من get_device_stops_optimized.php)
         * لكي تكون نتائج التوقفات (أماكن + أوقات) مطابقة قدر الإمكان لما اعتدت عليه.
         */
        $segments = $this->fetchDbAggregatedStops($deviceId, $start, $end);

        // في حال لم تُرجع قاعدة البيانات أي توقفات (مثلاً فترة قصيرة جداً)
        // نرجع إلى الخوارزمية القديمة المبنية على نقاط الـ GPS كاحتياط
        if (empty($segments)) {
            $segments = $this->analyzePointsIntoSegments($cleanPoints);
        }
        
        // ✅ تحسين: جعل جلب أسماء الأماكن اختياري ويمكن التحكم به من الواجهة
        $enablePlaceNames = !empty($this->config['enablePlaceNames']);
        if ($enablePlaceNames) {
            $timeline = $this->enrichWithPlaceNames($segments, $userId);
        } else {
            $timeline = $segments;
            foreach ($timeline as &$entry) {
                if ($entry['type'] === 'stop' && !isset($entry['place_name'])) {
                    $entry['place_name'] = 'موقع غير محدد';
                }
            }
        }
        
        // ✅ تحسين: جعل Map Matching اختياري ويمكن التحكم به من الواجهة
        $enableMapMatching = !empty($this->config['enableMapMatching']);
        $maxMapPoints = isset($this->config['maxMapMatchingPoints']) ? (int)$this->config['maxMapMatchingPoints'] : 500;
        if ($enableMapMatching && count($cleanPoints) <= $maxMapPoints) { // فقط إذا كانت النقاط معقولة
            $snappedRoute = $this->fetchSnappedRoute($cleanPoints);
        } else {
            // ✅ استخدام المسار الخام إذا كانت النقاط كثيرة جداً
            $snappedRoute = $this->createRawRoute($cleanPoints);
        }

        // ✅ إضافة النقاط الفعلية مع الأوقات للاستخدام في المسار
        $routePoints = [];
        foreach ($cleanPoints as $point) {
            $timeRaw = $point['timestamp'];
            $time12h = null;
            $ts = strtotime($timeRaw);
            if ($ts !== false) {
                $t12 = date('g:i A', $ts);
                $t12 = str_replace(['AM', 'PM'], ['ص', 'م'], $t12);
                $time12h = $t12;
            }

            $routePoints[] = [
                'lat' => floatval($point['latitude']),
                'lng' => floatval($point['longitude']),
                'time' => $timeRaw,      // ✅ الوقت الفعلي من قاعدة البيانات
                'time_12h' => $time12h,  // ✅ الوقت بصيغة 12 ساعة (ص/م) للاستخدام في الواجهة
            ];
        }
        
        return [
            'status' => 'success',
            'timeline' => $timeline,
            'route' => $snappedRoute,
            'route_points' => $routePoints, // ✅ النقاط الفعلية مع الأوقات
            'meta' => [
                'raw_points' => count($rawPoints),
                'filtered_points' => count($cleanPoints),
                'noise_removed' => $noiseRemoved,
                'segments' => count($segments),
                'processing_time_ms' => round((microtime(true) - $startTime) * 1000, 2),
                // معلومات إضافية لمساعدة الواجهة الأمامية على الفهم
                'device_id' => $deviceId,
                'start' => $start,
                'end' => $end,
                'enable_place_names' => $enablePlaceNames,
                'enable_map_matching' => $enableMapMatching,
                'max_map_matching_points' => $maxMapPoints,
            ]
        ];
    }

    private function buildEmptyResponse($startTime, $extraMeta = []) {
        return [
            'status' => 'success',
            'timeline' => [],
            'route' => null,
            'meta' => array_merge([
                'message' => 'لا توجد نقاط كافية لعرض المسار.',
                'processing_time_ms' => round((microtime(true) - $startTime) * 1000, 2)
            ], $extraMeta)
        ];
    }

    private function fetchRawPoints($deviceId, $start, $end) {
        // ✅ استخدام Firebase إذا كان مفعلاً
        if (defined('USE_FIREBASE') && USE_FIREBASE) {
            $points = FirebaseService::getLocationsByTimeRange($deviceId, $start, $end);
            
            error_log("🔍 [UnifiedTimeline] Firebase returned " . count($points) . " points for device $deviceId");
            
            // تحويل البيانات إلى الصيغة المطلوبة
            $result = [];
            foreach ($points as $point) {
                // ✅ تحسين: التحقق من وجود الإحداثيات قبل الإضافة
                $lat = isset($point['latitude']) ? floatval($point['latitude']) : null;
                $lng = isset($point['longitude']) ? floatval($point['longitude']) : null;
                
                // ✅ تخطي النقاط بدون إحداثيات صالحة
                if ($lat === null || $lng === null || ($lat == 0 && $lng == 0)) {
                    continue;
                }
                
                $result[] = [
                    'latitude' => $lat,
                    'longitude' => $lng,
                    'speed' => isset($point['speed']) ? floatval($point['speed']) : 0,
                    'battery' => isset($point['battery']) ? $point['battery'] : null,
                    'timestamp' => isset($point['timestamp']) ? $point['timestamp'] : date('Y-m-d H:i:s')
                ];
            }
            
            error_log("🔍 [UnifiedTimeline] Converted to " . count($result) . " valid points");
            return $result;
        } else {
            // ✅ استخدام MySQL (الطريقة القديمة)
            // ✅ استخدام index على (device_id, timestamp) بدون قص اليوم
            // حتى لا نقطع جزءاً من اليوم (ما بعد الظهر / الليل)
            // ملاحظة: نعتمد على حقل timestamp كمرجع أساسي للوقت،
            // ولا نستخدم ts_date/ts_time إلا كاحتياطي إذا كان timestamp مفقوداً.
            $sql = "SELECT latitude, longitude, speed, battery, ts_date, ts_time, timestamp 
                    FROM locations 
                    WHERE device_id = ? AND timestamp BETWEEN ? AND ? 
                    ORDER BY timestamp ASC";

            // ✅ معالجة تحذيرات "String data, right truncated" كتحذيرات بدلاً من أخطاء قاتلة
            $oldErrMode = $this->conn->getAttribute(PDO::ATTR_ERRMODE);
            $this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);

            try {
                $stmt = $this->conn->prepare($sql);
                $stmt->execute([$deviceId, $start, $end]);
                $results = $stmt->fetchAll(PDO::FETCH_ASSOC);
            } finally {
                // استرجاع نمط الأخطاء الأصلي
                $this->conn->setAttribute(PDO::ATTR_ERRMODE, $oldErrMode);
            }

            // تنظيف وتوحيد حقل الوقت لكل نقطة قبل المعالجة
            foreach ($results as &$row) {
                $rawTs = $row['timestamp'] ?? null;

                // 1) المحاولة الأولى: الاعتماد على حقل timestamp كما هو
                if ($rawTs !== null) {
                    $row['timestamp'] = normalize_timestamp_string($rawTs);
                } else {
                    $row['timestamp'] = null;
                }

                // 2) إذا خرجت القيمة غير صالحة (null)، نجرب تركيبها من ts_date + ts_time إن توفرا
                if ($row['timestamp'] === null) {
                    $rawDate = $row['ts_date'] ?? null;
                    $rawTime = $row['ts_time'] ?? null;
                    if ($rawDate !== null || $rawTime !== null) {
                        $combined = trim(($rawDate ?? '') . ' ' . ($rawTime ?? ''));
                        $row['timestamp'] = normalize_timestamp_string($combined);
                    }
                }

                // 3) إذا بقيت null بعد كل المحاولات، نتركها null ليتم تجاهل النقطة لاحقاً

                // لا نحتاج هذه الحقول لاحقاً في المعالجة
                unset($row['ts_date'], $row['ts_time']);
            }
            unset($row);
            
            error_log("🔍 [UnifiedTimeline] MySQL returned " . count($results) . " points for device $deviceId");
            return $results;
        }
    }

    // ✅ تبسيط كبير: إلغاء تصفية النويز تماماً وإرجاع جميع النقاط كما هي
    // الهدف: إظهار *كل* النقاط/الأوقات الممكنة في المخطط الزمني والمسار،
    // حتى لو كان هناك بعض النويز في الـ GPS.
    private function filterGpsNoise($points) {
        return $points;
    }
    
    /**
     * ✅ دالة محسّنة: تصفية بسيطة وسريعة للنويز
     */
    private function applySimpleNoiseFilter($points) {
        if (count($points) < 3) return $points;
        
        $filtered = [$points[0]];
        $lastKept = $points[0];
        $lastKeptTime = strtotime($lastKept['timestamp']);
        
        for ($i = 1; $i < count($points); $i++) {
            $current = $points[$i];
            $currentTime = strtotime($current['timestamp']);
            
            if ($currentTime === false || $lastKeptTime === false) {
                $filtered[] = $current;
                $lastKept = $current;
                $lastKeptTime = $currentTime;
                continue;
            }
            
            $timeDiff = max(1, $currentTime - $lastKeptTime);
            $distance = $this->calculateDistance(
                $lastKept['latitude'],
                $lastKept['longitude'],
                $current['latitude'],
                $current['longitude']
            );
            $speedKmh = ($distance / $timeDiff) * 3.6;
            
            // ✅ تحسين: معايير أقل صرامة للكشف عن النويز (للسماح بمزيد من النقاط الصالحة)
            // ✅ فقط تجاهل النقاط المشبوهة جداً (سرعة عالية جداً أو قفزات كبيرة جداً)
            $isSuspiciousJump = (
                $speedKmh > 500 || // ✅ زيادة من 220 إلى 500 كم/س (أكثر تساهلاً)
                ($distance > 5000 && $timeDiff <= 60) || // ✅ قفزة كبيرة جداً في وقت قصير جداً
                ($distance > 2000 && $timeDiff < 30) // ✅ قفزة متوسطة في وقت قصير جداً
            );
            
            if ($isSuspiciousJump) {
                // فحص النقطة التالية للتأكد من أنها نويز
                $next = $points[$i + 1] ?? null;
                if ($next) {
                    $nextTime = strtotime($next['timestamp']);
                    if ($nextTime !== false) {
                        $returnDist = $this->calculateDistance(
                            $lastKept['latitude'],
                            $lastKept['longitude'],
                            $next['latitude'],
                            $next['longitude']
                        );
                        $returnTime = max(0, $nextTime - $currentTime);
                        
                        // ✅ إذا عادت النقطة التالية بسرعة، تجاهل النقطة الحالية (نويز مؤكد)
                        if ($returnDist <= $this->config['noiseReturnMeters'] && 
                            $returnTime <= $this->config['noiseReturnWindowSec']) {
                            continue; // تجاهل النقطة الشاذة
                        }
                    }
                }
                
                // ✅ تجاهل النقاط المشبوهة جداً فقط (أكثر صرامة)
                if ($distance > 10000 || $speedKmh > 500) {
                    continue;
                }
            }
            
            $filtered[] = $current;
            $lastKept = $current;
            $lastKeptTime = $currentTime;
        }
        
        return $filtered;
    }
    
    /**
     * ✅ دالة جديدة: تصفية متقدمة للنويز باستخدام خوارزمية متعددة المعايير
     */
    private function applyAdvancedNoiseFilter($points) {
        if (count($points) < 3) return $points;
        
        $filtered = [$points[0]];
        $lastKept = $points[0];
        $lastKeptTime = strtotime($lastKept['timestamp']);
        $window = []; // نافذة للنقاط السابقة للتحليل
        
        for ($i = 1; $i < count($points); $i++) {
            $current = $points[$i];
            $currentTime = strtotime($current['timestamp']);
            
            if ($currentTime === false || $lastKeptTime === false) {
                $filtered[] = $current;
                $lastKept = $current;
                $lastKeptTime = $currentTime;
                continue;
            }
            
            $timeDiff = max(1, $currentTime - $lastKeptTime);
            $distance = $this->calculateDistance(
                $lastKept['latitude'],
                $lastKept['longitude'],
                $current['latitude'],
                $current['longitude']
            );
            $speedKmh = ($distance / $timeDiff) * 3.6;
            
            // ✅ معايير متعددة للكشف عن النويز
            $isSuspiciousJump = false;
            $reasons = [];
            
            // 1. فحص السرعة غير المعقولة
            if ($speedKmh > $this->config['noiseMaxSpeedKmh']) {
                $isSuspiciousJump = true;
                $reasons[] = "سرعة عالية: {$speedKmh} كم/س";
            }
            
            // 2. فحص القفزات الكبيرة في وقت قصير
            if ($distance > $this->config['noiseMaxJumpMeters'] && $timeDiff <= 120) {
                $isSuspiciousJump = true;
                $reasons[] = "قفزة كبيرة: {$distance} متر في {$timeDiff} ثانية";
            }
            
            // 3. فحص القفزات المتوسطة في وقت قصير جداً
            if ($distance > 300 && $timeDiff < 30) {
                $isSuspiciousJump = true;
                $reasons[] = "قفزة متوسطة سريعة: {$distance} متر في {$timeDiff} ثانية";
            }
            
            // 4. فحص الاتجاه المفاجئ (زاوية حادة جداً)
            if (count($window) >= 2) {
                $prevPoint = $window[count($window) - 1];
                $prevPrevPoint = $window[count($window) - 2];
                
                $angle1 = $this->calculateBearing(
                    $prevPrevPoint['latitude'], $prevPrevPoint['longitude'],
                    $prevPoint['latitude'], $prevPoint['longitude']
                );
                $angle2 = $this->calculateBearing(
                    $prevPoint['latitude'], $prevPoint['longitude'],
                    $current['latitude'], $current['longitude']
                );
                
                $angleDiff = abs($angle2 - $angle1);
                if ($angleDiff > 180) $angleDiff = 360 - $angleDiff;
                
                // إذا كانت الزاوية أكبر من 150 درجة (استدارة حادة جداً) مع مسافة كبيرة
                if ($angleDiff > 150 && $distance > 100) {
                    $isSuspiciousJump = true;
                    $reasons[] = "استدارة حادة جداً: {$angleDiff} درجة";
                }
            }
            
            if ($isSuspiciousJump) {
                // فحص النقطة التالية للتأكد من أنها نويز
                $next = $points[$i + 1] ?? null;
                if ($next) {
                    $nextTime = strtotime($next['timestamp']);
                    if ($nextTime !== false) {
                        $returnDist = $this->calculateDistance(
                            $lastKept['latitude'],
                            $lastKept['longitude'],
                            $next['latitude'],
                            $next['longitude']
                        );
                        $returnTime = max(0, $nextTime - $currentTime);
                        
                        // إذا عادت النقطة التالية بسرعة، تجاهل النقطة الحالية
                        $returnsQuickly = (
                            $returnDist <= $this->config['noiseReturnMeters'] &&
                            $returnTime <= $this->config['noiseReturnWindowSec']
                        );
                        
                        if ($returnsQuickly) {
                            continue; // تجاهل النقطة الشاذة
                        }
                    }
                }
                
                // تجاهل النقاط المشبوهة جداً
                if ($distance > 1000 || $speedKmh > 300) {
                    continue;
                }
            }
            
            // إضافة النقطة إلى النافذة
            $window[] = $current;
            if (count($window) > 5) {
                array_shift($window); // الاحتفاظ بآخر 5 نقاط فقط
            }
            
            $filtered[] = $current;
            $lastKept = $current;
            $lastKeptTime = $currentTime;
        }
        
        return $filtered;
    }
    
    /**
     * ✅ دالة جديدة: تنعيم المسار باستخدام Moving Average
     */
    private function applyMovingAverageSmoothing($points, $windowSize = 3) {
        if (count($points) < $windowSize) return $points;
        
        $smoothed = [];
        $halfWindow = floor($windowSize / 2);
        
        for ($i = 0; $i < count($points); $i++) {
            if ($i < $halfWindow || $i >= count($points) - $halfWindow) {
                // النقاط في البداية والنهاية تبقى كما هي
                $smoothed[] = $points[$i];
                continue;
            }
            
            // حساب المتوسط للنقاط في النافذة
            $sumLat = 0;
            $sumLng = 0;
            $count = 0;
            
            for ($j = $i - $halfWindow; $j <= $i + $halfWindow; $j++) {
                if ($j >= 0 && $j < count($points)) {
                    $sumLat += $points[$j]['latitude'];
                    $sumLng += $points[$j]['longitude'];
                    $count++;
                }
            }
            
            $smoothedPoint = $points[$i];
            $smoothedPoint['latitude'] = $sumLat / $count;
            $smoothedPoint['longitude'] = $sumLng / $count;
            $smoothed[] = $smoothedPoint;
        }
        
        return $smoothed;
    }
    
    /**
     * ✅ دالة جديدة: حساب الاتجاه (Bearing) بين نقطتين
     */
    private function calculateBearing($lat1, $lon1, $lat2, $lon2) {
        $lat1Rad = deg2rad($lat1);
        $lat2Rad = deg2rad($lat2);
        $dLon = deg2rad($lon2 - $lon1);
        
        $y = sin($dLon) * cos($lat2Rad);
        $x = cos($lat1Rad) * sin($lat2Rad) - sin($lat1Rad) * cos($lat2Rad) * cos($dLon);
        
        $bearing = atan2($y, $x);
        $bearing = rad2deg($bearing);
        $bearing = fmod($bearing + 360, 360);
        
        return $bearing;
    }

    private function analyzePointsIntoSegments($points) {
        if (count($points) < 2) return [];

        $segments = [];
        $currentSegment = null;
        $prevTime = strtotime($points[0]['timestamp']);

        for ($i = 1; $i < count($points); $i++) {
            $p1 = $points[$i - 1];
            $p2 = $points[$i];

            $timeDiff = strtotime($p2['timestamp']) - $prevTime;
            if ($timeDiff <= 0) continue;

            $distance = $this->calculateDistance($p1['latitude'], $p1['longitude'], $p2['latitude'], $p2['longitude']);
            $speedKmh = ($distance / $timeDiff) * 3.6;

            $isMoving = $speedKmh > $this->config['stopMaxSpeedKmh'] || $distance > ($this->config['stationaryThresholdM'] / 2);
            $activityType = $isMoving ? 'driving' : 'stop';

            if ($currentSegment === null) {
                $currentSegment = $this->startNewSegment($activityType, $p1);
            }

            if ($currentSegment['type'] === $activityType) {
                $currentSegment['points'][] = $p2;
                $currentSegment['exit_time'] = $p2['timestamp'];
            } else {
                $segments[] = $this->finalizeSegment($currentSegment);
                $currentSegment = $this->startNewSegment($activityType, $p2);
            }
            $prevTime = strtotime($p2['timestamp']);
        }

        if ($currentSegment !== null) {
            $segments[] = $this->finalizeSegment($currentSegment);
        }

        return $segments;
    }

    private function startNewSegment($type, $point) {
        return [
            'type' => $type,
            'entry_time' => $point['timestamp'],
            'exit_time' => $point['timestamp'],
            'points' => [$point]
        ];
    }

    private function finalizeSegment($segment) {
        $pointCount = count($segment['points']);
        $firstPoint = $segment['points'][0];
        $lastPoint = $segment['points'][$pointCount - 1];

        $final = [
            'type' => $segment['type'],
            'entryTime' => $firstPoint['timestamp'],
            'exitTime' => $lastPoint['timestamp'],
            'duration' => round((strtotime($lastPoint['timestamp']) - strtotime($firstPoint['timestamp'])) / 60),
        ];

        // إضافة وقت الدخول والخروج بصيغة 12 ساعة مع (ص/م)
        $entryTs = strtotime($final['entryTime']);
        if ($entryTs !== false) {
            $entry12 = date('g:i A', $entryTs);
            $entry12 = str_replace(['AM', 'PM'], ['ص', 'م'], $entry12);
            $final['entryTime_12h'] = $entry12;
        }
        $exitTs = strtotime($final['exitTime']);
        if ($exitTs !== false) {
            $exit12 = date('g:i A', $exitTs);
            $exit12 = str_replace(['AM', 'PM'], ['ص', 'م'], $exit12);
            $final['exitTime_12h'] = $exit12;
        }

        $totalLat = 0; $totalLng = 0; $totalDist = 0;
        $batteries = [];

        for ($i = 0; $i < $pointCount; $i++) {
            $p = $segment['points'][$i];
            $totalLat += $p['latitude'];
            $totalLng += $p['longitude'];
            if ($p['battery'] !== null) $batteries[] = (int)$p['battery'];
            if ($i > 0) {
                $prev = $segment['points'][$i - 1];
                $totalDist += $this->calculateDistance($prev['latitude'], $prev['longitude'], $p['latitude'], $p['longitude']);
            }
        }

        $final['lat'] = $totalLat / $pointCount;
        $final['lng'] = $totalLng / $pointCount;
        $final['distance'] = round($totalDist);

        if (!empty($batteries)) {
            $final['battery_range'] = min($batteries) . '% - ' . max($batteries) . '%';
        }

        return $final;
    }

    /**
     * ✅ دالة جديدة: جلب التوقفات من قاعدة البيانات بنفس منطق
     * get_device_stops_optimized.php تقريباً، لكن مدمجة داخل الخدمة الموحدة.
     * الهدف: أن تكون التوقفات (الجدول العمودي) مطابقة للنظام القديم.
     */
    private function fetchDbAggregatedStops($deviceId, $start, $end) {
        // فلترة بالمدى الزمني القادم من الطلب الموحد
        $timeFilter = "AND timestamp BETWEEN ? AND ?";
        $params = [$deviceId, $start, $end];

        // أقل مدة توقف بالدقائق (من الإعدادات إن وُجدت، وإلا 5 دقائق كالنظام القديم)
        $minDurationMin = isset($this->config['stopMinDurationMin'])
            ? max(1, (int)ceil($this->config['stopMinDurationMin']))
            : 5;

        $sql = "
            SELECT 
                MIN(timestamp) as entry_time,
                MAX(timestamp) as exit_time,
                AVG(latitude) as lat,
                AVG(longitude) as lng,
                COUNT(*) as point_count,
                MIN(battery) as min_battery,
                MAX(battery) as max_battery
            FROM locations 
            WHERE device_id = ? 
            $timeFilter
            GROUP BY 
                ROUND(latitude, 4),
                ROUND(longitude, 4),
                DATE(timestamp),
                HOUR(timestamp)
            HAVING 
                TIMESTAMPDIFF(MINUTE, MIN(timestamp), MAX(timestamp)) >= :min_duration
            ORDER BY entry_time ASC
        ";

        try {
            $stmt = $this->conn->prepare($sql);
            $stmt->bindValue(1, $params[0]);
            $stmt->bindValue(2, $params[1]);
            $stmt->bindValue(3, $params[2]);
            $stmt->bindValue(':min_duration', $minDurationMin, PDO::PARAM_INT);
            $stmt->execute();
            $results = $stmt->fetchAll(PDO::FETCH_ASSOC);
        } catch (Exception $e) {
            // في حال حدوث أي خطأ نرجع مصفوفة فارغة لكي تعود الدالة الأم
            // لاستخدام الخوارزمية المبنية على النقاط
            error_log("UnifiedTimeline DB stops error: " . $e->getMessage());
            return [];
        }

        $stops = [];
        $processedStops = [];

        foreach ($results as $row) {
            $duration = (strtotime($row['exit_time']) - strtotime($row['entry_time'])) / 60; // بالدقائق
            if ($duration < $minDurationMin) {
                continue;
            }

            $entryTimestamp = strtotime($row['entry_time']);
            $exitTimestamp  = strtotime($row['exit_time']);

            // التحقق من التداخل مع التوقفات الموجودة (نفس منطق النظام القديم)
            $isDuplicate = false;
            foreach ($processedStops as $existing) {
                $existingEntry = strtotime($existing['entryTime']);
                $existingExit  = strtotime($existing['exitTime']);

                $timeOverlap = !($exitTimestamp <= $existingEntry || $entryTimestamp >= $existingExit);
                $locationSimilar = (
                    abs($row['lat'] - $existing['lat']) < 0.001 &&
                    abs($row['lng'] - $existing['lng']) < 0.001
                );

                if ($timeOverlap && $locationSimilar) {
                    $isDuplicate = true;
                    break;
                }
            }

            if ($isDuplicate) {
                continue;
            }

            // بناء نفس شكل العنصر المستخدم في المخطط الزمني الجديد
            $entryTime = $row['entry_time'];
            $exitTime  = $row['exit_time'];

            $seg = [
                'type'       => 'stop',
                'entryTime'  => $entryTime,
                'exitTime'   => $exitTime,
                'duration'   => (int)round($duration),
                'lat'        => (float)$row['lat'],
                'lng'        => (float)$row['lng'],
                'distance'   => 0,
            ];

            // أوقات 12 ساعة (ص/م) كما في finalizeSegment
            $entryTs = strtotime($entryTime);
            if ($entryTs !== false) {
                $entry12 = date('g:i A', $entryTs);
                $entry12 = str_replace(['AM', 'PM'], ['ص', 'م'], $entry12);
                $seg['entryTime_12h'] = $entry12;
            }
            $exitTs = strtotime($exitTime);
            if ($exitTs !== false) {
                $exit12 = date('g:i A', $exitTs);
                $exit12 = str_replace(['AM', 'PM'], ['ص', 'م'], $exit12);
                $seg['exitTime_12h'] = $exit12;
            }

            // مدى البطارية بنفس منطق get_device_stops_optimized.php
            if ($row['min_battery'] !== null && $row['max_battery'] !== null) {
                if ($row['min_battery'] == $row['max_battery']) {
                    $seg['battery_range'] = $row['min_battery'] . '%';
                } else {
                    $seg['battery_range'] = $row['min_battery'] . '% - ' . $row['max_battery'] . '%';
                }
            }

            $stops[] = $seg;
            $processedStops[] = $seg;
        }

        // ترتيب النتائج حسب وقت الدخول (الأحدث أولاً) كما في النظام القديم
        usort($stops, function($a, $b) {
            return strtotime($b['entryTime']) - strtotime($a['entryTime']);
        });

        // يمكن تقليل العدد إذا لزم الأمر (مثلاً 50 توقفاً كحد أقصى)
        if (count($stops) > 50) {
            $stops = array_slice($stops, 0, 50);
        }

        return $stops;
    }

    private function enrichWithPlaceNames($segments, $userId) {
        $timeline = [];
        $stopLocations = [];
        foreach ($segments as $seg) {
            if ($seg['type'] === 'stop' && $seg['duration'] >= $this->config['stopMinDurationMin']) {
                $userPlace = $this->checkUserPlaces($seg['lat'], $seg['lng']);
                if ($userPlace) {
                    $seg['place_name'] = "📍 " . $userPlace;
                    $seg['place_type'] = 'user_defined';
                } else {
                    $stopLocations[] = ['lat' => $seg['lat'], 'lng' => $seg['lng']];
                }
            }
            $timeline[] = $seg;
        }

        if (!empty($stopLocations)) {
            $placeNames = $this->batchGetHighAccuracyPlaceNames($stopLocations);
            foreach ($timeline as &$entry) {
                if ($entry['type'] === 'stop' && !isset($entry['place_name'])) {
                    $key = round($entry['lat'], 5) . ',' . round($entry['lng'], 5);
                    if (isset($placeNames[$key])) {
                        $entry['place_name'] = $placeNames[$key]['name'];
                        $entry['place_type'] = $placeNames[$key]['type'];
                    } else {
                        $entry['place_name'] = 'موقع غير محدد';
                    }
                }
            }
        }
        return $timeline;
    }

    private function fetchSnappedRoute($points) {
        global $mapbox_token;
        if (!$mapbox_token || count($points) < 2) {
            return $this->createRawRoute($points);
        }

        // ✅ تحسين: تقسيم ذكي للمسار مع مراعاة الاستدارات
        $chunks = $this->smartChunkSplit($points);
        $mergedCoordinates = [];

        foreach ($chunks as $chunk) {
            if (count($chunk) < 2) continue;
            
            // ✅ إضافة timestamps للنقاط لتحسين دقة Map Matching
            $coordPairs = [];
            foreach ($chunk as $idx => $p) {
                $timestamp = isset($p['timestamp']) ? strtotime($p['timestamp']) : null;
                $coordStr = floatval($p['longitude']) . ',' . floatval($p['latitude']);
                if ($timestamp !== false && $timestamp !== null) {
                    $coordStr .= ',' . $timestamp;
                }
                $coordPairs[] = $coordStr;
            }
            
            $coordsParam = implode(';', $coordPairs);
            
            // ✅ تحسين: إضافة معاملات لتحسين دقة Map Matching
            $url = "https://api.mapbox.com/matching/v5/mapbox/driving/{$coordsParam}?" . http_build_query([
                'geometries' => 'geojson',
                'overview' => 'full', // ✅ full للحصول على أقصى دقة
                'steps' => 'false',
                'tidy' => 'true', // ✅ تنظيف المسار تلقائياً
                'radiuses' => $this->generateRadiuses($chunk), // ✅ تحديد نصف قطر البحث لكل نقطة
                'access_token' => $mapbox_token
            ]);
            
            // ✅ تحسين: timeout أقصر و timeout للاتصال
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_TIMEOUT, 8); // ✅ تقليل من 20 إلى 8 ثوانٍ
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); // ✅ timeout الاتصال 3 ثوانٍ
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            $resp = curl_exec($ch);
            $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);

            if ($http_code !== 200 || $resp === false) {
                // في حالة الفشل، استخدم المسار الخام لهذه القطعة
                $mergedCoordinates = array_merge($mergedCoordinates, $this->createRawRoute($chunk)['geometry']['coordinates']);
                continue;
            }

            $json = json_decode($resp, true);
            if ($json && ($json['code'] ?? '') === 'Ok' && !empty($json['matchings'][0]['geometry']['coordinates'])) {
                // ✅ اختيار أفضل matching (أعلى confidence)
                $bestMatching = $json['matchings'][0];
                foreach ($json['matchings'] ?? [] as $matching) {
                    $confidence = $matching['confidence'] ?? 0;
                    $bestConfidence = $bestMatching['confidence'] ?? 0;
                    if ($confidence > $bestConfidence) {
                        $bestMatching = $matching;
                    }
                }
                
                // في حالة النجاح، استخدم المسار المحاذي للطريق
                $coords = $bestMatching['geometry']['coordinates'];
                
                // ✅ دمج ذكي: التأكد من الاتصال السلس بين القطع
                if (!empty($mergedCoordinates)) {
                    $lastCoord = end($mergedCoordinates);
                    $firstCoord = $coords[0];
                    
                    // إذا كانت النقطة الأولى قريبة جداً من الأخيرة، تخطها
                    $dist = $this->calculateDistance(
                        $lastCoord[1], $lastCoord[0],
                        $firstCoord[1], $firstCoord[0]
                    );
                    
                    if ($dist < 10) { // أقل من 10 متر
                        array_shift($coords);
                    }
                }
                
                $mergedCoordinates = array_merge($mergedCoordinates, $coords);
            } else {
                $mergedCoordinates = array_merge($mergedCoordinates, $this->createRawRoute($chunk)['geometry']['coordinates']);
            }
        }

        return [
            'provider' => 'mapbox',
            'geometry' => ['type' => 'LineString', 'coordinates' => $mergedCoordinates]
        ];
    }
    
    /**
     * ✅ دالة محسّنة: تقسيم بسيط وسريع للمسار
     */
    private function smartChunkSplit($points, $maxChunkSize = 100) {
        if (count($points) <= $maxChunkSize) {
            return [$points];
        }
        
        // ✅ تبسيط: تقسيم بسيط بدون فحص الاستدارات (أسرع)
        $chunks = [];
        for ($i = 0; $i < count($points); $i += $maxChunkSize) {
            $chunks[] = array_slice($points, $i, $maxChunkSize);
        }
        
        return $chunks;
    }
    
    /**
     * ✅ دالة جديدة: توليد نصف قطر البحث لكل نقطة (لتحسين دقة Map Matching)
     */
    private function generateRadiuses($chunk) {
        $radiuses = [];
        foreach ($chunk as $p) {
            // ✅ نصف قطر ديناميكي: أكبر في المناطق الحضرية، أصغر في الطرق السريعة
            // افتراضياً: 50 متر (يمكن تحسينه بناءً على نوع الطريق)
            $radiuses[] = 50;
        }
        return implode(';', $radiuses);
    }

    private function createRawRoute($points) {
        return [
            'provider' => 'raw',
            'geometry' => [
                'type' => 'LineString',
                'coordinates' => array_map(fn($p) => [floatval($p['longitude']), floatval($p['latitude'])], $points)
            ]
        ];
    }

    // --- Helper and Geocoding Functions (copied from get_device_stops_optimized.php) ---

    private function loadUserPlaces($userId) {
        try {
            $stmt = $this->conn->prepare("SELECT place_name, latitude, longitude, radius FROM user_places WHERE user_id = ?");
            $stmt->execute([$userId]);
            $this->userPlaces = $stmt->fetchAll(PDO::FETCH_ASSOC);
        } catch (PDOException $e) {
            // Ignore if table doesn't exist
        }
    }

    private function checkUserPlaces($lat, $lng) {
        foreach ($this->userPlaces as $place) {
            if ($this->calculateDistance($lat, $lng, $place['latitude'], $place['longitude']) <= $place['radius']) {
                return $place['place_name'];
            }
        }
        return null;
    }

    public function batchGetHighAccuracyPlaceNames($locations) {
        if (empty($locations)) return [];
        global $google_maps_api_key;

        // ✅ تحسين: تقليل عدد الطلبات (حد أقصى يمكن التحكم به من الإعدادات)
        $maxStops = isset($this->config['maxGeocodeStops']) ? (int)$this->config['maxGeocodeStops'] : 10;
        if ($maxStops > 0 && count($locations) > $maxStops) {
            $locations = array_slice($locations, 0, $maxStops);
        }

        $mh = curl_multi_init();
        $handles = [];
        $results = [];

        // ✅ تحسين: timeout أطول قليلاً للحصول على نتائج أفضل
        $curlTimeout = 5; // 5 ثوانٍ للحصول على نتائج أفضل

        // 1. إعداد جميع طلبات cURL (Geocoding + Places API)
        foreach ($locations as $location) {
            $lat = $location['lat'];
            $lng = $location['lng'];
            $key = round($lat, 5) . ',' . round($lng, 5);

            // Geocoding API - للحصول على المكونات الإدارية
            $geoUrl = "https://maps.googleapis.com/maps/api/geocode/json?" . http_build_query([
                'latlng' => "{$lat},{$lng}", 
                'language' => 'ar', 
                'key' => $google_maps_api_key,
                'result_type' => 'premise|point_of_interest|street_address|route|neighborhood|sublocality|locality|administrative_area_level_3|administrative_area_level_2|administrative_area_level_1|political'
            ]);
            $ch_geo = curl_init($geoUrl);
            curl_setopt($ch_geo, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch_geo, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch_geo, CURLOPT_TIMEOUT, $curlTimeout);
            curl_setopt($ch_geo, CURLOPT_CONNECTTIMEOUT, 2);
            curl_multi_add_handle($mh, $ch_geo);
            $handles[$key]['geo'] = $ch_geo;

            // Places API - للحصول على المعالم القريبة (POI)
            $placesUrl = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?" . http_build_query([
                'location' => "{$lat},{$lng}",
                'radius' => 150, // 150 متر
                'language' => 'ar',
                'key' => $google_maps_api_key
            ]);
            $ch_places = curl_init($placesUrl);
            curl_setopt($ch_places, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch_places, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch_places, CURLOPT_TIMEOUT, $curlTimeout);
            curl_setopt($ch_places, CURLOPT_CONNECTTIMEOUT, 2);
            curl_multi_add_handle($mh, $ch_places);
            $handles[$key]['places'] = $ch_places;
        }

        // 2. تنفيذ جميع الطلبات بشكل متوازٍ مع timeout
        $running = null;
        $startTime = microtime(true);
        $maxTime = 8; // ✅ حد أقصى 8 ثوانٍ لجميع الطلبات
        
        do {
            curl_multi_exec($mh, $running);
            curl_multi_select($mh, 0.1);
            
            if ((microtime(true) - $startTime) > $maxTime) {
                break;
            }
        } while ($running > 0);

        // 3. معالجة النتائج بعد اكتمالها
        foreach ($locations as $location) {
            $lat = $location['lat'];
            $lng = $location['lng'];
            $key = round($lat, 5) . ',' . round($lng, 5);

            $geoResponse = curl_multi_getcontent($handles[$key]['geo'] ?? null);
            $geoData = json_decode($geoResponse, true);
            
            $placesResponse = curl_multi_getcontent($handles[$key]['places'] ?? null);
            $placesData = json_decode($placesResponse, true);
            
            // ✅ استخدام Geocoding + Places API للحصول على أفضل النتائج
            $placeInfo = $this->combineApiResults($geoData, $placesData, $lat, $lng);
            $results[$key] = $placeInfo;

            if (isset($handles[$key]['geo'])) {
                curl_multi_remove_handle($mh, $handles[$key]['geo']);
            }
            if (isset($handles[$key]['places'])) {
                curl_multi_remove_handle($mh, $handles[$key]['places']);
            }
        }

        curl_multi_close($mh);
        return $results;
    }

    private function getHighAccuracyPlaceName($lat, $lng) {
        global $google_maps_api_key;
        $defaultResult = ['name' => 'موقع غير محدد', 'type' => 'unknown', 'confidence' => 0];

        // 1. Check user-defined places first (already done, but as a principle)
        $userPlace = $this->checkUserPlaces($lat, $lng);
        if ($userPlace) {
            return ['name' => "📍 " . $userPlace, 'type' => 'user_defined', 'confidence' => 100];
        }

        // [تم التعديل] هذه الدالة لم تعد تقوم بطلبات الشبكة مباشرة
        // بل أصبحت مجرد غلاف يستدعي الطلبات المتوازية لموقع واحد
        $results = $this->batchGetHighAccuracyPlaceNames([['lat' => $lat, 'lng' => $lng]]);
        $key = round($lat, 5) . ',' . round($lng, 5);
        return $results[$key] ?? $defaultResult;
    }

    private function combineApiResults($geocodingResults, $nearestPoiResult, $lat, $lng) {
        $defaultResult = ['name' => 'موقع غير محدد', 'type' => 'unknown', 'confidence' => 0];

        $addressComponents = [];
        if ($geocodingResults && ($geocodingResults['status'] ?? 'ZERO_RESULTS') === 'OK') {
            $addressComponents = $this->extractAddressComponentsDetailed($geocodingResults['results']);
        }

        // بناء الأجزاء بترتيب الأولوية: المحافظة، القضاء، المدينة، الحي، المعلم
        $parts = [];
        
        // 1. المحافظة (administrative_area_level_1)
        if (!empty($addressComponents['administrative_area_level_1'])) {
            $parts[] = $addressComponents['administrative_area_level_1'];
        }
        
        // 2. القضاء (administrative_area_level_2) أو المدينة (locality)
        $cityOrDistrict = $addressComponents['administrative_area_level_2'] ?? $addressComponents['locality'] ?? null;
        if (!empty($cityOrDistrict)) {
            $parts[] = $cityOrDistrict;
        }
        
        // 3. الحي (neighborhood أو sublocality)
        $neigh = $addressComponents['neighborhood'] ?? $addressComponents['sublocality'] ?? null;
        if (!empty($neigh)) {
            $parts[] = $neigh;
        }
        
        // 4. المعلم (POI) من Places API
        $poiName = null;
        $poiType = 'unknown';
        if ($nearestPoiResult && ($nearestPoiResult['status'] ?? 'ZERO_RESULTS') === 'OK' && !empty($nearestPoiResult['results'])) {
            // اختيار أقرب POI (الأول في النتائج عادةً هو الأقرب)
            $poi = $nearestPoiResult['results'][0];
            $poiName = $poi['name'] ?? null;
            $poiType = $this->determinePlaceType($poi['types'] ?? []);
            if ($poiName) {
                $parts[] = $poiName;
            }
        }

        // 5. تنظيف وإزالة التكرارات
        if (!empty($parts)) {
            $seen = [];
            $uniqueParts = [];
            foreach ($parts as $p) {
                $key = trim(mb_strtolower($p));
                if (empty($key) || isset($seen[$key])) continue;

                // التحقق من التكرار الجزئي
                $isRedundant = false;
                foreach ($seen as $s_key => $val) {
                    // إذا كان أحد النصوص جزءاً من الآخر، نتجاهل الأقصر
                    if (mb_strlen($key) > mb_strlen($s_key) && mb_strpos($key, $s_key) !== false) {
                        // استبدال النص الأقصر بالنص الأطول
                        $index = array_search($val, $uniqueParts);
                        if ($index !== false) {
                            unset($uniqueParts[$index]);
                            unset($seen[$s_key]);
                        }
                        $uniqueParts = array_values($uniqueParts); // إعادة ترقيم
                        break;
                    } elseif (mb_strlen($s_key) > mb_strlen($key) && mb_strpos($s_key, $key) !== false) {
                        $isRedundant = true;
                        break;
                    }
                }
                if (!$isRedundant) {
                    $seen[$key] = $p;
                    $uniqueParts[] = $p;
                }
            }

            if (!empty($uniqueParts)) {
                // بناء الاسم النهائي: المحافظة، القضاء، المدينة، الحي، المعلم
                $finalName = implode('، ', array_slice($uniqueParts, 0, 5)); // حد أقصى 5 أجزاء
                return [
                    'name' => $finalName,
                    'type' => $poiType !== 'unknown' ? $poiType : 'location',
                    'confidence' => 95
                ];
            }
        }

        // Fallback: استخدام أول نتيجة من Geocoding
        if ($geocodingResults && !empty($geocodingResults['results'][0])) {
            $fallback = $this->selectBestAddressFromResult($geocodingResults['results']);
            if ($fallback['name'] !== 'موقع غير محدد') {
                return $fallback;
            }
        }

        // Fallback نهائي: استخدام الإحداثيات مع معلومات إدارية أساسية
        if (!empty($addressComponents['administrative_area_level_1'])) {
            $fallbackParts = [];
            if (!empty($addressComponents['administrative_area_level_1'])) {
                $fallbackParts[] = $addressComponents['administrative_area_level_1'];
            }
            if (!empty($addressComponents['administrative_area_level_2'])) {
                $fallbackParts[] = $addressComponents['administrative_area_level_2'];
            }
            if (!empty($fallbackParts)) {
                return [
                    'name' => implode('، ', $fallbackParts),
                    'type' => 'location',
                    'confidence' => 60
                ];
            }
        }

        return $defaultResult;
    }

    private function selectBestAddressFromResult($results) {
        // Priority for types
        $priority = ['point_of_interest', 'premise', 'street_address', 'route', 'neighborhood', 'sublocality'];
        
        foreach ($priority as $type) {
            foreach ($results as $result) {
                if (in_array($type, $result['types'])) {
                    return [
                        'name' => $this->simplifyAddress($result['formatted_address']),
                        'type' => $this->determinePlaceType($result['types'])
                    ];
                }
            }
        }

        // Fallback to the first result
        if (!empty($results[0])) {
            return [
                'name' => $this->simplifyAddress($results[0]['formatted_address']),
                'type' => $this->determinePlaceType($results[0]['types'] ?? [])
            ];
        }

        return ['name' => 'موقع غير محدد', 'type' => 'unknown'];
    }

    private function extractAddressComponentsDetailed($geocodingResults) {
        $components = [
            'route' => null, 
            'neighborhood' => null, 
            'sublocality' => null, 
            'sublocality_level_1' => null,
            'sublocality_level_2' => null,
            'locality' => null, 
            'administrative_area_level_3' => null,
            'administrative_area_level_2' => null, 
            'administrative_area_level_1' => null,
            'country' => null
        ];

        // جمع المكونات من جميع النتائج
        foreach ($geocodingResults as $result) {
            if (empty($result['address_components'])) continue;
            foreach ($result['address_components'] as $component) {
                $types = $component['types'] ?? [];
                $name = $component['long_name'] ?? '';
                if ($name === '') continue;
                
                foreach ($types as $type) {
                    // تجاهل الطرق بدون اسم
                    if ($type === 'route' && (mb_stripos($name, 'unnamed') !== false || mb_stripos($name, 'بدون اسم') !== false || mb_stripos($name, 'طريق غير مسمى') !== false)) {
                        continue;
                    }
                    
                    // حفظ المكون إذا كان موجوداً في القائمة ولم يتم حفظه بعد
                    if (array_key_exists($type, $components) && $components[$type] === null) {
                        $components[$type] = $name;
                        break; // حفظ أول قيمة فقط لكل نوع
                    }
                }
            }
        }
        
        // تحسين: إذا لم يوجد neighborhood، استخدم sublocality_level_1 أو sublocality_level_2
        if ($components['neighborhood'] === null) {
            $components['neighborhood'] = $components['sublocality_level_1'] ?? $components['sublocality_level_2'] ?? $components['sublocality'] ?? null;
        }
        
        // تحسين: إذا لم يوجد locality، استخدم administrative_area_level_3
        if ($components['locality'] === null) {
            $components['locality'] = $components['administrative_area_level_3'] ?? null;
        }
        
        return $components;
    }

    private function determinePlaceType($types) {
        $typeMapping = [
            'user_defined' => 'user_defined', 'home' => 'home', 'work' => 'work', 'office' => 'office',
            'restaurant' => 'restaurant', 'cafe' => 'cafe', 'store' => 'store', 'shopping_mall' => 'mall',
            'supermarket' => 'supermarket', 'bank' => 'bank', 'gas_station' => 'gas_station',
            'parking' => 'parking', 'school' => 'school', 'hospital' => 'hospital', 'mosque' => 'mosque'
        ];
        foreach ($types as $type) {
            if (isset($typeMapping[$type])) return $typeMapping[$type];
        }
        return 'point_of_interest';
    }

    private function makeApiRequest($url) {
        $context = stream_context_create(['http' => ['ignore_errors' => true, 'timeout' => 7]]);
        $response = @file_get_contents($url, false, $context);
        if ($response) {
            $data = json_decode($response, true);
            if (json_last_error() === JSON_ERROR_NONE) {
                return $data;
            }
        }
        return null;
    }

    private function simplifyAddress($address) {
        $parts = explode('،', $address);
        // Take the first 2-3 parts for a cleaner name
        return implode('، ', array_slice($parts, 0, 2));
    }

    private function calculateDistance($lat1, $lon1, $lat2, $lon2) {
        $earth_radius = 6371000;
        $dLat = deg2rad($lat2 - $lat1);
        $dLon = deg2rad($lon2 - $lon1);
        $a = sin($dLat / 2) * sin($dLat / 2) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon / 2) * sin($dLon / 2);
        $c = 2 * atan2(sqrt($a), sqrt(1 - $a));
        return $earth_radius * $c;
    }
}

// --- Main Execution ---

// ✅ تنظيف أي output قبل إرسال JSON (مع التحقق من وجود buffer لتجنب تحذيرات PHP)
if (ob_get_length()) ob_clean();

// ✅ التأكد من وجود اتصال قاعدة البيانات (MySQL من السيرفر وليس Firebase)
if (!isset($conn) || $conn === null) {
    http_response_code(500);
    if (ob_get_length()) ob_clean();
    echo json_encode(['status' => 'error', 'message' => 'اتصال قاعدة البيانات غير متاح. تأكد من إعدادات السيرفر (MySQL).'], JSON_UNESCAPED_UNICODE);
    exit;
}

try {
    // حساب النطاق الزمني
    $nowDay = date('Y-m-d');
    if ($fromDate || $toDate) {
        $start = $fromDate ?: (($date ?: $nowDay) . ' 00:00:00');
        $end   = $toDate   ?: (($date ?: $nowDay) . ' 23:59:59');
    } else {
        $day = $date ?: $nowDay;
        $start = $day . ' 00:00:00';
        $end   = $day . ' 23:59:59';
    }

    $service = new UnifiedTimelineService($conn, $config);
    $result = $service->getTimelineAndRoute($deviceId, $start, $end, $_SESSION['user_id']);
    
    // ✅ التأكد من أن JSON صحيح قبل الإرسال (مع استبدال الأحرف غير الصالحة في UTF-8 بدلاً من فشل الترميز)
    $json = json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE);
    if ($json === false) {
        throw new Exception('خطأ في ترميز JSON: ' . json_last_error_msg());
    }
    
    // ✅ إغلاق الاتصال بعد الانتهاء لتقليل عدد الاتصالات النشطة
    // ✅ إغلاق الاتصال بشكل قسري لإجبار إغلاقه فوراً
    if (isset($GLOBALS['dbInstance']) && method_exists($GLOBALS['dbInstance'], 'forceCloseConnection')) {
        try {
            $GLOBALS['dbInstance']->forceCloseConnection();
            DatabaseConnection::resetInstance();
        } catch (Exception $e) {
            // تجاهل الأخطاء أثناء الإغلاق
        }
    }
    
    echo $json;
    exit;

} catch (Exception $e) {
    http_response_code(500);
    error_log("Unified Timeline Error: " . $e->getMessage() . " in " . $e->getFile() . ":" . $e->getLine());
    
    // ✅ إغلاق الاتصال حتى في حالة الخطأ بشكل قسري
    if (isset($GLOBALS['dbInstance']) && method_exists($GLOBALS['dbInstance'], 'forceCloseConnection')) {
        try {
            $GLOBALS['dbInstance']->forceCloseConnection();
            DatabaseConnection::resetInstance();
        } catch (Exception $closeError) {
            // تجاهل الأخطاء أثناء الإغلاق
        }
    }
    
    // ✅ مسح أي مخرجات سابقة ثم إرجاع JSON فقط
    if (ob_get_length()) ob_clean();
    $errorMsg = $e->getMessage();
    $errorJson = json_encode(['status' => 'error', 'message' => 'حدث خطأ داخلي في الخادم: ' . $errorMsg], JSON_UNESCAPED_UNICODE);
    echo $errorJson;
    exit;
}