<?php
/**
 * Klasa Playlist - zarządzanie playlistami
 */

class Playlist
{
    private Database $db;
    private Logger $logger;

    // Typy playlist
    const TYPE_XTREAM = 'xtream';
    const TYPE_M3U = 'm3u';
    const TYPE_STALKER = 'stalker';

    public function __construct()
    {
        $this->db = Database::getInstance();
        $this->logger = new Logger();
    }

    /**
     * Pobierz playlisty urządzenia
     */
    public function getByDeviceId(int $deviceId): array
    {
        return $this->db->fetchAll(
            "SELECT p.*, 
                    (SELECT COUNT(*) FROM entity_notes en WHERE en.entity_type = 'playlist' AND en.entity_id = p.id) as notes_count
             FROM playlists p 
             WHERE p.device_id = :device_id 
             ORDER BY p.sort_order ASC, p.created_at ASC",
            ['device_id' => $deviceId]
        );
    }

    /**
     * Pobierz playlisty użytkownika (wszystkie urządzenia)
     */
    public function getByUserId(int $userId, int $limit = ITEMS_PER_PAGE, int $offset = 0, ?string $search = null): array
    {
        $sql = "SELECT p.*, d.mac, d.code, d.name as device_name,
                    (SELECT COUNT(*) FROM entity_notes en WHERE en.entity_type = 'playlist' AND en.entity_id = p.id) as notes_count
             FROM playlists p
             JOIN devices d ON p.device_id = d.id
             WHERE d.user_id = :user_id";
        $params = ['user_id' => $userId];
        
        if ($search) {
            $sql .= " AND (p.name LIKE :search OR d.mac LIKE :search2 OR d.code LIKE :search3)";
            $params['search'] = "%{$search}%";
            $params['search2'] = "%{$search}%";
            $params['search3'] = "%{$search}%";
        }
        
        $sql .= " ORDER BY p.created_at DESC LIMIT :limit OFFSET :offset";
        $params['limit'] = $limit;
        $params['offset'] = $offset;
        
        return $this->db->fetchAll($sql, $params);
    }

    /**
     * Pobierz wszystkie playlisty (dla admina)
     */
    public function getAll(int $limit = ITEMS_PER_PAGE, int $offset = 0, ?int $deviceId = null, ?string $search = null): array
    {
        $sql = "SELECT p.*, d.mac, d.code, d.name as device_name, u.username,
                       (SELECT COUNT(*) FROM entity_notes en WHERE en.entity_type = 'playlist' AND en.entity_id = p.id) as notes_count
                FROM playlists p
                JOIN devices d ON p.device_id = d.id
                JOIN users u ON d.user_id = u.id";
        
        $params = [];
        $where = [];
        
        if ($deviceId !== null) {
            $where[] = "p.device_id = :device_id";
            $params['device_id'] = $deviceId;
        }
        
        if ($search) {
            $where[] = "(p.name LIKE :search OR d.mac LIKE :search2 OR d.code LIKE :search3 OR u.username LIKE :search4)";
            $params['search'] = "%{$search}%";
            $params['search2'] = "%{$search}%";
            $params['search3'] = "%{$search}%";
            $params['search4'] = "%{$search}%";
        }
        
        if (!empty($where)) {
            $sql .= " WHERE " . implode(' AND ', $where);
        }
        
        $sql .= " ORDER BY p.created_at DESC LIMIT :limit OFFSET :offset";
        $params['limit'] = $limit;
        $params['offset'] = $offset;
        
        return $this->db->fetchAll($sql, $params);
    }

    /**
     * Pobierz playlistę po ID
     */
    public function getById(int $id): ?array
    {
        return $this->db->fetchOne(
            "SELECT p.*, d.mac, d.code, d.name as device_name, d.user_id as owner_id, u.username
             FROM playlists p
             JOIN devices d ON p.device_id = d.id
             JOIN users u ON d.user_id = u.id
             WHERE p.id = :id",
            ['id' => $id]
        );
    }

    /**
     * Utwórz nową playlistę
     */
    public function create(array $data, ?int $userId = null): array
    {
        // Walidacja typu
        $type = $data['type'] ?? '';
        if (!in_array($type, [self::TYPE_XTREAM, self::TYPE_M3U, self::TYPE_STALKER])) {
            return ['success' => false, 'error' => 'Nieprawidłowy typ playlisty.'];
        }

        // Walidacja nazwy
        $name = trim($data['name'] ?? '');
        if (strlen($name) < 1) {
            return ['success' => false, 'error' => 'Nazwa playlisty jest wymagana.'];
        }

        // Sprawdź czy urządzenie istnieje i pobierz dane właściciela
        $device = $this->db->fetchOne(
            "SELECT d.id, d.user_id, u.role, u.playlist_limit, u.max_playlists
             FROM devices d 
             JOIN users u ON d.user_id = u.id
             WHERE d.id = :id",
            ['id' => $data['device_id']]
        );
        
        if (!$device) {
            return ['success' => false, 'error' => 'Urządzenie nie istnieje.'];
        }

        // Admin nie ma limitów
        if ($device['role'] !== 'admin') {
            // Sprawdź limit playlist użytkownika
            $currentPlaylistCount = $this->countByUserId($device['user_id']);
            
            if ($device['role'] === 'reseller') {
                // Dla resellera - dynamiczny limit (max_playlists - przydzielone userom)
                $allocatedToUsers = $this->db->fetchOne(
                    "SELECT COALESCE(SUM(playlist_limit), 0) as total FROM users WHERE parent_id = :id",
                    ['id' => $device['user_id']]
                );
                $availableForReseller = $device['max_playlists'] - (int)$allocatedToUsers['total'];
                
                if ($currentPlaylistCount >= $availableForReseller) {
                    return ['success' => false, 'error' => "Osiągnięto limit playlist. Dostępne: {$availableForReseller} (pula: {$device['max_playlists']}, przydzielone userom: {$allocatedToUsers['total']})."];
                }
            } else {
                // Dla zwykłego usera - standardowy limit
                if ($currentPlaylistCount >= $device['playlist_limit']) {
                    return ['success' => false, 'error' => 'Osiągnięto limit playlist (' . $device['playlist_limit'] . ').'];
                }
            }
        }

        // Walidacja i budowanie konfiguracji
        $configResult = $this->validateAndBuildConfig($type, $data);
        if (!$configResult['success']) {
            return $configResult;
        }

        // Sprawdź czy taka playlista już istnieje na urządzeniu
        $duplicateCheck = $this->checkDuplicatePlaylist($data['device_id'], $type, $configResult['config']);
        if ($duplicateCheck) {
            return ['success' => false, 'error' => "Taka playlista już istnieje na tym urządzeniu: \"{$duplicateCheck['name']}\""];
        }

        // Generuj unikalną nazwę jeśli taka już istnieje
        $originalName = $name;
        $name = $this->generateUniqueName($data['device_id'], $name);
        $nameChanged = ($name !== $originalName);

        $updateDate = $data['update_date'] ?? time();

        $id = $this->db->insert(
            "INSERT INTO playlists (device_id, type, name, config_json, update_date, sort_order, is_active) 
             VALUES (:device_id, :type, :name, :config, :update_date, :sort_order, 1)",
            [
                'device_id' => $data['device_id'],
                'type' => $type,
                'name' => $name,
                'config' => json_encode($configResult['config'], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
                'update_date' => $updateDate,
                'sort_order' => (int)($data['sort_order'] ?? 0)
            ]
        );

        if ($nameChanged) {
            $this->logger->log('playlist_created', "Dodano playlistę: {$name} (ID: {$id}, typ: {$type}, oryginalna nazwa: {$originalName})", $userId);
        } else {
            $this->logger->log('playlist_created', "Dodano playlistę: {$name} (ID: {$id}, typ: {$type})", $userId);
        }

        return ['success' => true, 'id' => $id, 'name' => $name, 'name_changed' => $nameChanged];
    }

    /**
     * Aktualizuj playlistę
     */
    public function update(int $id, array $data, ?int $userId = null): array
    {
        $playlist = $this->getById($id);
        
        if (!$playlist) {
            return ['success' => false, 'error' => 'Playlista nie istnieje.'];
        }

        $updates = [];
        $params = ['id' => $id];
        $nameChanged = false;
        $originalName = null;
        $newName = null;

        if (isset($data['name'])) {
            $name = trim($data['name']);
            if (strlen($name) < 1) {
                return ['success' => false, 'error' => 'Nazwa playlisty jest wymagana.'];
            }
            
            // Sprawdź unikalność nazwy tylko jeśli się zmienia
            if ($name !== $playlist['name']) {
                $originalName = $name;
                $name = $this->generateUniqueName($playlist['device_id'], $name, $id);
                $nameChanged = ($name !== $originalName);
                $newName = $name;
            }
            
            $updates[] = "name = :name";
            $params['name'] = $name;
        }

        if (isset($data['type'])) {
            if (!in_array($data['type'], [self::TYPE_XTREAM, self::TYPE_M3U, self::TYPE_STALKER])) {
                return ['success' => false, 'error' => 'Nieprawidłowy typ playlisty.'];
            }
            $updates[] = "type = :type";
            $params['type'] = $data['type'];
        }

        // Jeśli zmienia się konfiguracja
        $type = $data['type'] ?? $playlist['type'];
        if ($this->hasConfigData($data)) {
            $configResult = $this->validateAndBuildConfig($type, $data);
            if (!$configResult['success']) {
                return $configResult;
            }
            
            // Sprawdź czy taka playlista już istnieje na urządzeniu (pomiń samą siebie)
            $duplicateCheck = $this->checkDuplicatePlaylist($playlist['device_id'], $type, $configResult['config'], $id);
            if ($duplicateCheck) {
                return ['success' => false, 'error' => "Taka playlista już istnieje na tym urządzeniu: \"{$duplicateCheck['name']}\""];
            }
            
            $updates[] = "config_json = :config";
            $params['config'] = json_encode($configResult['config'], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        }

        if (isset($data['update_date'])) {
            $updates[] = "update_date = :update_date";
            $params['update_date'] = (int)$data['update_date'];
        }

        if (isset($data['sort_order'])) {
            $updates[] = "sort_order = :sort_order";
            $params['sort_order'] = (int)$data['sort_order'];
        }

        if (isset($data['is_active'])) {
            $updates[] = "is_active = :is_active";
            $params['is_active'] = $data['is_active'] ? 1 : 0;
        }

        if (empty($updates)) {
            return ['success' => false, 'error' => 'Brak danych do aktualizacji.'];
        }

        $updates[] = "updated_at = datetime('now')";

        $this->db->execute(
            "UPDATE playlists SET " . implode(', ', $updates) . " WHERE id = :id",
            $params
        );

        if ($nameChanged) {
            $this->logger->log('playlist_updated', "Zaktualizowano playlistę ID: {$id} (nazwa zmieniona z '{$originalName}' na '{$newName}')", $userId);
        } else {
            $this->logger->log('playlist_updated', "Zaktualizowano playlistę ID: {$id}", $userId);
        }

        return ['success' => true, 'name' => $newName ?? $playlist['name'], 'name_changed' => $nameChanged];
    }

    /**
     * Aktualizuj update_date (touch)
     */
    public function touch(int $id, ?int $userId = null): bool
    {
        $playlist = $this->getById($id);
        if (!$playlist) {
            return false;
        }
        
        $result = $this->db->execute(
            "UPDATE playlists SET update_date = :time, updated_at = datetime('now') WHERE id = :id",
            ['time' => time(), 'id' => $id]
        );
        
        if ($result && $userId) {
            $this->logger->log('playlist_touched', "Odświeżono update_date playlisty: {$playlist['name']} (ID: {$id})", $userId);
        }
        
        return $result;
    }

    /**
     * Przełącz status aktywności playlisty
     */
    public function toggleActive(int $id, ?int $userId = null): array
    {
        $playlist = $this->getById($id);
        
        if (!$playlist) {
            return ['success' => false, 'error' => 'Playlista nie istnieje.'];
        }

        $newStatus = $playlist['is_active'] ? 0 : 1;
        
        $this->db->execute(
            "UPDATE playlists SET is_active = :status, updated_at = datetime('now') WHERE id = :id",
            ['status' => $newStatus, 'id' => $id]
        );

        $action = $newStatus ? 'aktywowano' : 'dezaktywowano';
        $this->logger->log('playlist_toggle_active', "Playlista {$playlist['name']} {$action} (ID: {$id})", $userId);

        return ['success' => true, 'is_active' => $newStatus];
    }

    /**
     * Usuń playlistę
     */
    public function delete(int $id, ?int $userId = null): array
    {
        $playlist = $this->getById($id);
        
        if (!$playlist) {
            return ['success' => false, 'error' => 'Playlista nie istnieje.'];
        }

        $this->db->execute("DELETE FROM playlists WHERE id = :id", ['id' => $id]);

        $this->logger->log('playlist_deleted', "Usunięto playlistę: {$playlist['name']} (ID: {$id})", $userId);

        return ['success' => true];
    }

    /**
     * Pobierz playlisty dla API (po MAC i CODE)
     */
    public function getForApi(string $mac, string $code): ?array
    {
        // Znajdź urządzenie
        $device = $this->db->fetchOne(
            "SELECT d.*, u.is_active as user_active 
             FROM devices d 
             JOIN users u ON d.user_id = u.id
             WHERE d.mac = :mac AND d.code = :code",
            ['mac' => strtoupper($mac), 'code' => $code]
        );

        if (!$device) {
            return null;
        }

        // Sprawdź czy użytkownik i urządzenie są aktywne
        if (!$device['user_active'] || $device['is_blocked']) {
            return ['blocked' => true, 'playlists' => []];
        }

        // Pobierz playlisty
        $playlists = $this->db->fetchAll(
            "SELECT * FROM playlists WHERE device_id = :device_id AND is_active = 1 ORDER BY sort_order ASC",
            ['device_id' => $device['id']]
        );

        // Formatuj playlisty dla API
        $formatted = [];
        foreach ($playlists as $p) {
            $config = json_decode($p['config_json'], true);
            $config['name'] = $p['name'];
            $config['type'] = $p['type'];
            $config['update_date'] = (int)$p['update_date'];
            $formatted[] = $config;
        }

        return [
            'blocked' => false,
            'device_id' => $device['id'],
            'playlists' => $formatted
        ];
    }

    /**
     * Zlicz playlisty
     */
    public function count(?int $deviceId = null, ?int $userId = null, ?string $search = null): int
    {
        $sql = "SELECT COUNT(*) as count FROM playlists p 
                JOIN devices d ON p.device_id = d.id
                JOIN users u ON d.user_id = u.id";
        $where = [];
        $params = [];
        
        if ($deviceId !== null) {
            $where[] = "p.device_id = :device_id";
            $params['device_id'] = $deviceId;
        }
        
        if ($userId !== null) {
            $where[] = "d.user_id = :user_id";
            $params['user_id'] = $userId;
        }
        
        if ($search) {
            $where[] = "(p.name LIKE :search OR d.mac LIKE :search2 OR d.code LIKE :search3 OR u.username LIKE :search4)";
            $params['search'] = "%{$search}%";
            $params['search2'] = "%{$search}%";
            $params['search3'] = "%{$search}%";
            $params['search4'] = "%{$search}%";
        }
        
        if (!empty($where)) {
            $sql .= " WHERE " . implode(' AND ', $where);
        }
        
        $result = $this->db->fetchOne($sql, $params);
        return $result['count'] ?? 0;
    }

    /**
     * Pobierz statystyki playlist
     */
    public function getStats(): array
    {
        $total = $this->db->fetchOne("SELECT COUNT(*) as count FROM playlists");
        $byType = $this->db->fetchAll(
            "SELECT type, COUNT(*) as count FROM playlists GROUP BY type"
        );
        
        $types = ['xtream' => 0, 'm3u' => 0, 'stalker' => 0];
        foreach ($byType as $row) {
            $types[$row['type']] = $row['count'];
        }
        
        return [
            'total' => $total['count'] ?? 0,
            'by_type' => $types
        ];
    }

    /**
     * Waliduj i zbuduj konfigurację
     */
    private function validateAndBuildConfig(string $type, array $data): array
    {
        $config = [];

        switch ($type) {
            case self::TYPE_XTREAM:
                if (empty($data['host'])) {
                    return ['success' => false, 'error' => 'Host jest wymagany dla Xtream.'];
                }
                if (empty($data['username'])) {
                    return ['success' => false, 'error' => 'Username jest wymagany dla Xtream.'];
                }
                if (empty($data['password'])) {
                    return ['success' => false, 'error' => 'Password jest wymagany dla Xtream.'];
                }
                
                $config = [
                    'host' => rtrim(trim($data['host']), '/'),
                    'username' => trim($data['username']),
                    'password' => trim($data['password'])
                ];
                break;

            case self::TYPE_M3U:
                if (empty($data['url'])) {
                    return ['success' => false, 'error' => 'URL jest wymagany dla M3U.'];
                }
                
                $config = [
                    'url' => trim($data['url'])
                ];
                
                if (!empty($data['epg_link'])) {
                    $config['epg_link'] = trim($data['epg_link']);
                }
                break;

            case self::TYPE_STALKER:
                if (empty($data['server_url'])) {
                    return ['success' => false, 'error' => 'Server URL jest wymagany dla Stalker.'];
                }
                if (empty($data['mac_address'])) {
                    return ['success' => false, 'error' => 'MAC Address jest wymagany dla Stalker.'];
                }
                
                $config = [
                    'server_url' => rtrim(trim($data['server_url']), '/'),
                    'mac_address' => strtoupper(trim($data['mac_address']))
                ];
                
                // Opcjonalne pola
                if (!empty($data['referrer_url'])) {
                    $config['referrer_url'] = rtrim(trim($data['referrer_url']), '/');
                }
                if (!empty($data['serial_number'])) {
                    $config['serial_number'] = trim($data['serial_number']);
                }
                if (!empty($data['stalker_device_id'])) {
                    $config['device_id'] = trim($data['stalker_device_id']);
                } elseif (!empty($data['device_id']) && !is_numeric($data['device_id'])) {
                    // device_id tylko jeśli nie jest numeryczny (bo numeryczny to ID urządzenia w bazie)
                    $config['device_id'] = trim($data['device_id']);
                }
                if (!empty($data['device_id2'])) {
                    $config['device_id2'] = trim($data['device_id2']);
                }
                if (!empty($data['signature'])) {
                    $config['signature'] = trim($data['signature']);
                }
                if (!empty($data['epg_link'])) {
                    $config['epg_link'] = trim($data['epg_link']);
                }
                break;
        }

        return ['success' => true, 'config' => $config];
    }

    /**
     * Sprawdź czy dane zawierają konfigurację
     */
    private function hasConfigData(array $data): bool
    {
        $configFields = [
            'host', 'username', 'password', // xtream
            'url', 'epg_link', // m3u
            'server_url', 'referrer_url', 'mac_address', 'serial_number', 
            'device_id', 'device_id2', 'signature' // stalker
        ];
        
        foreach ($configFields as $field) {
            if (isset($data[$field])) {
                return true;
            }
        }
        
        return false;
    }

    /**
     * Generuj unikalną nazwę playlisty dla urządzenia
     * Jeśli nazwa już istnieje, dodaje numerację (01, 02, 03...)
     */
    private function generateUniqueName(int $deviceId, string $name, ?int $excludeId = null): string
    {
        // Sprawdź czy nazwa jest unikalna
        $params = ['device_id' => $deviceId, 'name' => $name];
        $sql = "SELECT COUNT(*) as count FROM playlists WHERE device_id = :device_id AND name = :name";
        
        if ($excludeId !== null) {
            $sql .= " AND id != :exclude_id";
            $params['exclude_id'] = $excludeId;
        }
        
        $result = $this->db->fetchOne($sql, $params);
        
        if ($result['count'] == 0) {
            return $name;
        }

        // Nazwa istnieje - szukaj wolnego numeru
        // Najpierw usuń istniejącą numerację z nazwy (jeśli jest)
        $baseName = preg_replace('/ \d{2}$/', '', $name);
        
        for ($i = 1; $i <= 99; $i++) {
            $newName = $baseName . ' ' . str_pad($i, 2, '0', STR_PAD_LEFT);
            
            $params = ['device_id' => $deviceId, 'name' => $newName];
            $sql = "SELECT COUNT(*) as count FROM playlists WHERE device_id = :device_id AND name = :name";
            
            if ($excludeId !== null) {
                $sql .= " AND id != :exclude_id";
                $params['exclude_id'] = $excludeId;
            }
            
            $result = $this->db->fetchOne($sql, $params);
            
            if ($result['count'] == 0) {
                return $newName;
            }
        }

        // Fallback - dodaj timestamp
        return $baseName . ' ' . time();
    }

    /**
     * Sprawdź czy playlista z takimi samymi danymi już istnieje na urządzeniu
     * Xtream: host + username + password
     * M3U: url
     * Stalker: server_url + mac_address
     * 
     * @return array|null Zwraca istniejącą playlistę lub null jeśli nie ma duplikatu
     */
    private function checkDuplicatePlaylist(int $deviceId, string $type, array $config, ?int $excludeId = null): ?array
    {
        // Pobierz wszystkie playlisty danego typu na urządzeniu
        $params = ['device_id' => $deviceId, 'type' => $type];
        $sql = "SELECT id, name, config_json FROM playlists WHERE device_id = :device_id AND type = :type";
        
        if ($excludeId !== null) {
            $sql .= " AND id != :exclude_id";
            $params['exclude_id'] = $excludeId;
        }
        
        $playlists = $this->db->fetchAll($sql, $params);
        
        foreach ($playlists as $playlist) {
            $existingConfig = json_decode($playlist['config_json'], true);
            
            $isDuplicate = false;
            
            switch ($type) {
                case self::TYPE_XTREAM:
                    // Porównaj host + username + password
                    $isDuplicate = (
                        $this->normalizeUrl($existingConfig['host'] ?? '') === $this->normalizeUrl($config['host'] ?? '') &&
                        ($existingConfig['username'] ?? '') === ($config['username'] ?? '') &&
                        ($existingConfig['password'] ?? '') === ($config['password'] ?? '')
                    );
                    break;
                    
                case self::TYPE_M3U:
                    // Porównaj url
                    $isDuplicate = (
                        $this->normalizeUrl($existingConfig['url'] ?? '') === $this->normalizeUrl($config['url'] ?? '')
                    );
                    break;
                    
                case self::TYPE_STALKER:
                    // Porównaj server_url + mac_address
                    $isDuplicate = (
                        $this->normalizeUrl($existingConfig['server_url'] ?? '') === $this->normalizeUrl($config['server_url'] ?? '') &&
                        strtoupper($existingConfig['mac_address'] ?? '') === strtoupper($config['mac_address'] ?? '')
                    );
                    break;
            }
            
            if ($isDuplicate) {
                return $playlist;
            }
        }
        
        return null;
    }

    /**
     * Normalizuj URL do porównania (usuń trailing slash, lowercase)
     */
    private function normalizeUrl(string $url): string
    {
        $url = strtolower(trim($url));
        $url = rtrim($url, '/');
        return $url;
    }

    /**
     * Zlicz playlisty użytkownika
     */
    public function countByUserId(int $userId, ?string $search = null): int
    {
        $sql = "SELECT COUNT(*) as count 
             FROM playlists p 
             JOIN devices d ON p.device_id = d.id 
             WHERE d.user_id = :user_id";
        $params = ['user_id' => $userId];
        
        if ($search) {
            $sql .= " AND (p.name LIKE :search OR d.mac LIKE :search2 OR d.code LIKE :search3)";
            $params['search'] = "%{$search}%";
            $params['search2'] = "%{$search}%";
            $params['search3'] = "%{$search}%";
        }
        
        $result = $this->db->fetchOne($sql, $params);
        return $result['count'] ?? 0;
    }

    /**
     * Zlicz playlisty dla listy user_ids (dla resellera)
     */
    public function countByUserIds(array $userIds, ?string $search = null): int
    {
        if (empty($userIds)) {
            return 0;
        }
        $placeholders = implode(',', array_fill(0, count($userIds), '?'));
        $sql = "SELECT COUNT(*) as count 
             FROM playlists p 
             JOIN devices d ON p.device_id = d.id 
             WHERE d.user_id IN ({$placeholders})";
        $params = $userIds;
        
        if ($search) {
            $sql .= " AND (p.name LIKE ? OR d.mac LIKE ? OR d.code LIKE ?)";
            $params[] = "%{$search}%";
            $params[] = "%{$search}%";
            $params[] = "%{$search}%";
        }
        
        $result = $this->db->fetchOne($sql, $params);
        return $result['count'] ?? 0;
    }
    
    /**
     * Pobierz playlisty dla listy user_ids (dla resellera)
     */
    public function getByUserIds(array $userIds, int $limit = ITEMS_PER_PAGE, int $offset = 0, ?string $search = null): array
    {
        if (empty($userIds)) {
            return [];
        }
        
        $placeholders = implode(',', array_fill(0, count($userIds), '?'));
        $sql = "SELECT p.*, d.mac, d.code, d.name as device_name, u.username,
                       (SELECT COUNT(*) FROM entity_notes en WHERE en.entity_type = 'playlist' AND en.entity_id = p.id) as notes_count
                FROM playlists p
                JOIN devices d ON p.device_id = d.id
                JOIN users u ON d.user_id = u.id
                WHERE d.user_id IN ({$placeholders})";
        $params = $userIds;
        
        if ($search) {
            $sql .= " AND (p.name LIKE ? OR d.mac LIKE ? OR d.code LIKE ? OR u.username LIKE ?)";
            $params[] = "%{$search}%";
            $params[] = "%{$search}%";
            $params[] = "%{$search}%";
            $params[] = "%{$search}%";
        }
        
        $sql .= " ORDER BY p.created_at DESC LIMIT ? OFFSET ?";
        $params[] = $limit;
        $params[] = $offset;
        
        return $this->db->fetchAll($sql, $params);
    }
}
