<?php

function discoveryText($value): string {
    if (function_exists('ensureUtf8String')) {
        return trim(ensureUtf8String($value));
    }
    if (is_array($value)) {
        $value = implode(' ', array_filter(array_map('strval', $value)));
    }
    return trim((string)$value);
}

function discoveryNormalizeKeyPart($value): string {
    $value = discoveryText($value);
    if ($value === '') return '';
    $value = function_exists('mb_strtolower') ? mb_strtolower($value, 'UTF-8') : strtolower($value);
    $value = preg_replace('/\([^\)]*\)|\[[^\]]*\]/u', '', $value);
    $value = preg_replace('/\b(feat|ft|with|remaster|remastered|live|radio edit)\b\.?/iu', '', $value);
    $value = preg_replace('/[^\p{L}\p{N}]+/u', '', $value);
    return $value ?: '';
}

function discoverySongKey($title, $artist): string {
    return discoveryNormalizeKeyPart($artist) . '|' . discoveryNormalizeKeyPart($title);
}

function discoveryExistingSongKeys(PDO $pdo): array {
    $keys = [];
    try {
        $stmt = $pdo->query("
            SELECT title, artist
            FROM songs
            WHERE COALESCE(is_missing, 0) = 0
              AND COALESCE(is_hidden, 0) = 0
              AND TRIM(COALESCE(title, '')) <> ''
        ");
        foreach ($stmt->fetchAll() as $row) {
            $key = discoverySongKey($row['title'] ?? '', $row['artist'] ?? '');
            if ($key !== '|') $keys[$key] = true;
        }
    } catch (Throwable $e) {
        if (function_exists('writeAppLog')) {
            writeAppLog('warning', 'discovery', 'Existing song key query failed', ['error' => $e->getMessage()]);
        }
    }
    return $keys;
}

function discoveryLibraryProfile(PDO $pdo): array {
    $profile = [
        'artists' => [],
        'genres' => [],
        'albums' => 0,
        'songs' => 0,
    ];

    try {
        $profile['songs'] = (int)$pdo->query("SELECT COUNT(*) FROM songs WHERE COALESCE(is_missing, 0) = 0 AND COALESCE(is_hidden, 0) = 0")->fetchColumn();
        $profile['albums'] = (int)$pdo->query("
            SELECT COUNT(DISTINCT CONCAT(COALESCE(NULLIF(TRIM(album), ''), '__unknown__'), '||', COALESCE(NULLIF(TRIM(artist), ''), '__unknown__')))
            FROM songs
            WHERE COALESCE(is_missing, 0) = 0 AND COALESCE(is_hidden, 0) = 0
        ")->fetchColumn();

        $artistStmt = $pdo->query("
            SELECT artist, COUNT(*) AS song_count, COALESCE(SUM(play_count), 0) AS total_plays
            FROM songs
            WHERE COALESCE(is_missing, 0) = 0
              AND COALESCE(is_hidden, 0) = 0
              AND TRIM(COALESCE(artist, '')) <> ''
            GROUP BY artist
            ORDER BY total_plays DESC, song_count DESC, artist ASC
            LIMIT 8
        ");
        $profile['artists'] = $artistStmt->fetchAll();

        $genreStmt = $pdo->query("
            SELECT genre, COUNT(*) AS song_count, COALESCE(SUM(play_count), 0) AS total_plays
            FROM songs
            WHERE COALESCE(is_missing, 0) = 0
              AND COALESCE(is_hidden, 0) = 0
              AND TRIM(COALESCE(genre, '')) <> ''
            GROUP BY genre
            ORDER BY song_count DESC, total_plays DESC, genre ASC
            LIMIT 8
        ");
        $profile['genres'] = $genreStmt->fetchAll();
    } catch (Throwable $e) {
        if (function_exists('writeAppLog')) {
            writeAppLog('warning', 'discovery', 'Library profile query failed', ['error' => $e->getMessage()]);
        }
    }

    return $profile;
}

function discoveryHttpJson(string $url, int $timeoutSeconds = 3): ?array {
    if (stripos($url, 'https://') !== 0) return null;
    $userAgent = 'MelonLiteMusicDiscovery/1.0 (local-admin-keyless-suggestions)';

    try {
        if (function_exists('curl_init')) {
            $ch = curl_init($url);
            curl_setopt_array($ch, [
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_FOLLOWLOCATION => true,
                CURLOPT_CONNECTTIMEOUT => $timeoutSeconds,
                CURLOPT_TIMEOUT => $timeoutSeconds,
                CURLOPT_USERAGENT => $userAgent,
                CURLOPT_HTTPHEADER => ['Accept: application/json'],
            ]);
            $body = curl_exec($ch);
            $status = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
            curl_close($ch);
            if (!is_string($body) || $body === '' || $status < 200 || $status >= 300) return null;
        } else {
            $context = stream_context_create([
                'http' => [
                    'method' => 'GET',
                    'timeout' => $timeoutSeconds,
                    'header' => "Accept: application/json\r\nUser-Agent: {$userAgent}\r\n",
                ],
            ]);
            $body = @file_get_contents($url, false, $context);
            if (!is_string($body) || $body === '') return null;
        }
        $json = json_decode($body, true);
        return is_array($json) ? $json : null;
    } catch (Throwable $e) {
        if (function_exists('writeAppLog')) {
            writeAppLog('warning', 'discovery_remote', 'Public metadata request failed', ['url' => $url, 'error' => $e->getMessage()]);
        }
        return null;
    }
}

function discoverySearchLinks(array $candidate): array {
    $query = trim(discoveryText($candidate['artist'] ?? '') . ' ' . discoveryText($candidate['title'] ?? ''));
    $encoded = rawurlencode($query);
    return [
        ['label' => 'YouTube Music', 'url' => 'https://music.youtube.com/search?q=' . $encoded],
        ['label' => 'Spotify 검색', 'url' => 'https://open.spotify.com/search/' . $encoded],
        ['label' => 'Apple Music', 'url' => 'https://music.apple.com/search?term=' . $encoded],
        ['label' => 'Melon 검색', 'url' => 'https://www.melon.com/search/total/index.htm?q=' . $encoded],
    ];
}

function discoveryCandidate($title, $artist, $album = '', $year = '', $source = '내장 추천', $reason = '', $score = 50, $sourceUrl = ''): array {
    $candidate = [
        'title' => discoveryText($title),
        'artist' => discoveryText($artist),
        'album' => discoveryText($album),
        'year' => discoveryText($year),
        'source' => discoveryText($source),
        'reason' => discoveryText($reason),
        'score' => (int)$score,
        'source_url' => discoveryText($sourceUrl),
    ];
    $candidate['search_links'] = discoverySearchLinks($candidate);
    $candidate['key'] = discoverySongKey($candidate['title'], $candidate['artist']);
    return $candidate;
}

function discoveryKpopArtistNames(): array {
    return [
        'BTS', '방탄소년단', 'BLACKPINK', '블랙핑크', 'NewJeans', '뉴진스', 'IVE', '아이브',
        'aespa', '에스파', 'LE SSERAFIM', '르세라핌', 'TWICE', '트와이스', 'SEVENTEEN', '세븐틴',
        'Stray Kids', '스트레이 키즈', 'NCT', 'NCT 127', 'NCT DREAM', 'EXO', '엑소',
        'Red Velvet', '레드벨벳', 'ITZY', '있지', '(G)I-DLE', '여자아이들', 'RIIZE', '라이즈',
        'ENHYPEN', '엔하이픈', 'TXT', 'TOMORROW X TOGETHER', '투모로우바이투게더',
        'ZEROBASEONE', '제로베이스원', 'ATEEZ', '에이티즈', 'TREASURE', '트레저',
        'SHINee', '샤이니', 'Girls\' Generation', '소녀시대', 'BIGBANG', '빅뱅',
        'IU', '아이유', 'TAEYEON', '태연', 'AKMU', '악뮤', 'ZICO', '지코',
        'BIBI', '비비', 'Crush', '크러쉬', 'DAY6', '데이식스', 'Wonder Girls', '원더걸스',
        '2NE1', '투애니원', 'KARA', '카라', 'SISTAR', '씨스타', 'MAMAMOO', '마마무',
        'GFRIEND', '여자친구', 'T-ARA', '티아라', 'FIFTY FIFTY', '피프티 피프티',
        'ILLIT', '아일릿', 'BABYMONSTER', '베이비몬스터', 'KISS OF LIFE', '키스오브라이프',
    ];
}

function discoveryIsKpopArtist($artist): bool {
    $artistNorm = discoveryNormalizeKeyPart($artist);
    if ($artistNorm === '') return false;
    foreach (discoveryKpopArtistNames() as $name) {
        $nameNorm = discoveryNormalizeKeyPart($name);
        if ($nameNorm === '') continue;
        if ($artistNorm === $nameNorm || strpos($artistNorm, $nameNorm) !== false || strpos($nameNorm, $artistNorm) !== false) {
            return true;
        }
    }
    return false;
}

function discoveryIsKpopCandidate(array $candidate): bool {
    if (discoveryIsKpopArtist($candidate['artist'] ?? '')) return true;
    $source = discoveryText($candidate['source'] ?? '');
    return stripos($source, 'K-pop') !== false;
}

function discoveryAddCandidate(array &$candidates, array &$seen, array $existingKeys, array $candidate): void {
    if (($candidate['title'] ?? '') === '' || ($candidate['artist'] ?? '') === '') return;
    if (!discoveryIsKpopCandidate($candidate)) return;
    $key = $candidate['key'] ?? discoverySongKey($candidate['title'], $candidate['artist']);
    if ($key === '|' || isset($existingKeys[$key]) || isset($seen[$key])) return;
    $seen[$key] = true;
    $candidates[] = $candidate;
}

function discoveryFetchListenBrainz(array $existingKeys, int $limit): array {
    $json = discoveryHttpJson('https://api.listenbrainz.org/1/stats/sitewide/recordings?range=week&count=30', 3);
    if (!$json) return [];

    $rows = [];
    if (isset($json['payload']['recordings']) && is_array($json['payload']['recordings'])) {
        $rows = $json['payload']['recordings'];
    } elseif (isset($json['payload']) && is_array($json['payload'])) {
        $rows = $json['payload'];
    }

    $picked = [];
    $seen = [];
    foreach ($rows as $row) {
        if (count($picked) >= $limit) break;
        $title = $row['track_name'] ?? $row['recording_name'] ?? $row['name'] ?? $row['title'] ?? '';
        $artist = $row['artist_name'] ?? $row['artist_credit_name'] ?? $row['artist'] ?? '';
        $album = $row['release_name'] ?? $row['release'] ?? '';
        $candidate = discoveryCandidate($title, $artist, $album, '', 'ListenBrainz 공개 K-pop 후보', '최근 공개 청취 통계에서 포착된 K-pop 후보입니다.', 85, 'https://listenbrainz.org');
        discoveryAddCandidate($picked, $seen, $existingKeys, $candidate);
    }
    return $picked;
}

function discoveryFetchMusicBrainzByProfile(array $profile, array $existingKeys, int $limit): array {
    $picked = [];
    $seen = [];
    $artists = array_slice($profile['artists'] ?? [], 0, 2);
    foreach ($artists as $artistRow) {
        if (count($picked) >= $limit) break;
        $artist = discoveryText($artistRow['artist'] ?? '');
        if ($artist === '') continue;
        if (!discoveryIsKpopArtist($artist)) continue;
        $query = rawurlencode('artist:"' . $artist . '"');
        $url = 'https://musicbrainz.org/ws/2/recording?fmt=json&limit=10&query=' . $query;
        $json = discoveryHttpJson($url, 3);
        if (!$json || empty($json['recordings']) || !is_array($json['recordings'])) continue;

        foreach ($json['recordings'] as $row) {
            if (count($picked) >= $limit) break 2;
            $title = $row['title'] ?? '';
            $artistName = $artist;
            if (!empty($row['artist-credit'][0]['name'])) {
                $artistName = $row['artist-credit'][0]['name'];
            }
            $album = '';
            $year = '';
            if (!empty($row['releases'][0]) && is_array($row['releases'][0])) {
                $album = $row['releases'][0]['title'] ?? '';
                $date = (string)($row['releases'][0]['date'] ?? '');
                $year = preg_match('/^\d{4}/', $date, $m) ? $m[0] : '';
            }
            $reason = '내 보관함에서 자주 보이는 K-pop 아티스트 "' . $artist . '" 기준 공개 메타데이터 후보입니다.';
            $candidate = discoveryCandidate($title, $artistName, $album, $year, 'MusicBrainz 공개 K-pop 메타데이터', $reason, 74, 'https://musicbrainz.org');
            discoveryAddCandidate($picked, $seen, $existingKeys, $candidate);
        }
    }
    return $picked;
}

function discoveryCacheDir(): string {
    return dirname(__DIR__) . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'cache';
}

function discoveryProfileHash(array $profile, bool $allowRemote): string {
    $artists = [];
    foreach (array_slice($profile['artists'] ?? [], 0, 4) as $row) {
        $artists[] = discoveryText($row['artist'] ?? '');
    }
    $genres = [];
    foreach (array_slice($profile['genres'] ?? [], 0, 4) as $row) {
        $genres[] = discoveryText($row['genre'] ?? '');
    }
    return sha1(json_encode([
        'scope' => 'kpop-v1',
        'remote' => $allowRemote,
        'artists' => $artists,
        'genres' => $genres,
    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
}

function discoveryCachePath(array $profile, bool $allowRemote): string {
    return discoveryCacheDir() . DIRECTORY_SEPARATOR . 'discovery_' . discoveryProfileHash($profile, $allowRemote) . '.json';
}

function discoveryReadCache(string $path, int $ttlSeconds = 21600): ?array {
    if (!is_file($path)) return null;
    $mtime = @filemtime($path);
    if (!$mtime || time() - $mtime > $ttlSeconds) return null;
    $raw = @file_get_contents($path);
    if (!is_string($raw) || $raw === '') return null;
    $json = json_decode($raw, true);
    return is_array($json) ? $json : null;
}

function discoveryWriteCache(string $path, array $payload): void {
    try {
        $dir = dirname($path);
        if (!is_dir($dir)) {
            @mkdir($dir, 0775, true);
        }
        if (!is_dir($dir) || !is_writable($dir)) return;
        $payload['cached_at'] = date('c');
        $json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE);
        if (is_string($json)) {
            @file_put_contents($path, $json, LOCK_EX);
        }
    } catch (Throwable $e) {
        if (function_exists('writeAppLog')) {
            writeAppLog('warning', 'discovery_cache', 'Discovery cache write failed', ['error' => $e->getMessage()]);
        }
    }
}

function discoveryHydrateCachedCandidates(array $items): array {
    $candidates = [];
    foreach ($items as $item) {
        if (!is_array($item)) continue;
        $candidates[] = discoveryCandidate(
            $item['title'] ?? '',
            $item['artist'] ?? '',
            $item['album'] ?? '',
            $item['year'] ?? '',
            $item['source'] ?? '공개 후보 캐시',
            $item['reason'] ?? '',
            (int)($item['score'] ?? 60),
            $item['source_url'] ?? ''
        );
    }
    return $candidates;
}

function discoveryCuratedCandidates(): array {
    return [
        ['NewJeans', 'Ditto', 'OMG', '2022', '최근 K-pop 감상 흐름에서 꾸준히 언급되는 후보입니다.'],
        ['NewJeans', 'Super Shy', 'Get Up', '2023', '밝은 K-pop 플레이리스트 보강 후보입니다.'],
        ['IVE', 'I AM', 'I’ve IVE', '2023', '보컬 중심 K-pop 대표 후보입니다.'],
        ['aespa', 'Drama', 'Drama', '2023', '강한 사운드의 K-pop 후보입니다.'],
        ['LE SSERAFIM', 'EASY', 'EASY', '2024', '최근 걸그룹 K-pop 후보입니다.'],
        ['BLACKPINK', 'DDU-DU DDU-DU', 'SQUARE UP', '2018', '글로벌 K-pop 대표곡 후보입니다.'],
        ['TWICE', 'Feel Special', 'Feel Special', '2019', '밝고 선명한 K-pop 후보입니다.'],
        ['BTS', 'Dynamite', 'BE', '2020', '전 세계적으로 많이 알려진 K-pop 후보입니다.'],
        ['SEVENTEEN', 'Super', 'FML', '2023', '퍼포먼스 중심 K-pop 후보입니다.'],
        ['Stray Kids', 'S-Class', '5-Star', '2023', '강한 비트의 보이그룹 K-pop 후보입니다.'],
        ['(G)I-DLE', 'Queencard', 'I feel', '2023', '대중성이 높은 K-pop 후보입니다.'],
        ['RIIZE', 'Get A Guitar', 'Get A Guitar', '2023', '신규 보이그룹 K-pop 후보입니다.'],
        ['IU', 'Love wins all', 'The Winning', '2024', '솔로 K-pop/국내 대중음악 후보입니다.'],
        ['TAEYEON', 'INVU', 'INVU', '2022', '솔로 보컬 K-pop 후보입니다.'],
        ['AKMU', 'Love Lee', 'Love Lee', '2023', '국내 대중음악/K-pop 경계의 가벼운 추천 후보입니다.'],
        ['DAY6', 'Welcome to the Show', 'Fourever', '2024', '밴드 기반 K-pop 후보입니다.'],
    ];
}

function discoveryBuildCuratedFallback(array $existingKeys, int $limit): array {
    $picked = [];
    $seen = [];
    foreach (discoveryCuratedCandidates() as $row) {
        if (count($picked) >= $limit) break;
        [$artist, $title, $album, $year, $reason] = $row;
        $candidate = discoveryCandidate($title, $artist, $album, $year, 'K-pop 내장 큐레이션', $reason, 58);
        discoveryAddCandidate($picked, $seen, $existingKeys, $candidate);
    }
    return $picked;
}

function discoveryMergeCandidates(array $groups, array $existingKeys, int $limit): array {
    $merged = [];
    $seen = [];
    foreach ($groups as $group) {
        foreach ($group as $candidate) {
            discoveryAddCandidate($merged, $seen, $existingKeys, $candidate);
        }
    }
    usort($merged, function ($a, $b) {
        return ($b['score'] ?? 0) <=> ($a['score'] ?? 0);
    });
    return array_slice($merged, 0, $limit);
}

function discoveryBuildAdminSuggestions(PDO $pdo, int $limit = 9, bool $allowRemote = true, bool $forceRefresh = false): array {
    $limit = max(3, min(24, $limit));
    $existingKeys = discoveryExistingSongKeys($pdo);
    $profile = discoveryLibraryProfile($pdo);
    $sources = [];
    $groups = [];
    $cacheHit = false;
    $cacheUpdatedAt = null;

    if ($allowRemote && !$forceRefresh) {
        $cache = discoveryReadCache(discoveryCachePath($profile, $allowRemote));
        if ($cache && !empty($cache['remote_candidates']) && is_array($cache['remote_candidates'])) {
            $remoteCandidates = discoveryHydrateCachedCandidates($cache['remote_candidates']);
            $curated = discoveryBuildCuratedFallback($existingKeys, $limit);
            $candidates = discoveryMergeCandidates([$remoteCandidates, $curated], $existingKeys, $limit);
            $sources = array_values(array_unique(array_merge((array)($cache['sources'] ?? []), $curated ? ['K-pop 내장 큐레이션'] : [])));
            $cacheHit = true;
            $cacheUpdatedAt = (string)($cache['cached_at'] ?? '');
            return [
                'candidates' => $candidates,
                'profile' => $profile,
                'sources' => $sources,
                'remote_enabled' => $allowRemote,
                'existing_count' => count($existingKeys),
                'cache_hit' => $cacheHit,
                'cache_updated_at' => $cacheUpdatedAt,
            ];
        }
    }

    if ($allowRemote) {
        $listenBrainz = discoveryFetchListenBrainz($existingKeys, $limit);
        if ($listenBrainz) {
            $groups[] = $listenBrainz;
            $sources[] = 'ListenBrainz 공개 K-pop 후보';
        }

        $musicBrainz = discoveryFetchMusicBrainzByProfile($profile, $existingKeys, $limit);
        if ($musicBrainz) {
            $groups[] = $musicBrainz;
            $sources[] = 'MusicBrainz 공개 K-pop 메타데이터';
        }
    }

    $remoteCandidates = discoveryMergeCandidates($groups, $existingKeys, max($limit, 24));
    if ($allowRemote && !empty($remoteCandidates)) {
        discoveryWriteCache(discoveryCachePath($profile, $allowRemote), [
            'remote_candidates' => array_map(function ($candidate) {
                return [
                    'title' => $candidate['title'] ?? '',
                    'artist' => $candidate['artist'] ?? '',
                    'album' => $candidate['album'] ?? '',
                    'year' => $candidate['year'] ?? '',
                    'source' => $candidate['source'] ?? '',
                    'reason' => $candidate['reason'] ?? '',
                    'score' => (int)($candidate['score'] ?? 0),
                    'source_url' => $candidate['source_url'] ?? '',
                ];
            }, $remoteCandidates),
            'sources' => $sources,
        ]);
    }

    $curated = discoveryBuildCuratedFallback($existingKeys, $limit);
    if ($curated) {
        $groups = [$remoteCandidates, $curated];
        $sources[] = 'K-pop 내장 큐레이션';
    }

    $candidates = discoveryMergeCandidates($groups, $existingKeys, $limit);
    return [
        'candidates' => $candidates,
        'profile' => $profile,
        'sources' => array_values(array_unique($sources)),
        'remote_enabled' => $allowRemote,
        'existing_count' => count($existingKeys),
        'cache_hit' => $cacheHit,
        'cache_updated_at' => $cacheUpdatedAt,
    ];
}
