<?php
require_once __DIR__ . '/db.php';
require_once __DIR__ . '/functions.php';
require_once __DIR__ . '/functions_tags.php';

function normalizeSearchQuery($query) {
    $query = trim(preg_replace('/\s+/u', ' ', (string)$query));
    if (function_exists('mb_substr')) return mb_substr($query, 0, 120, 'UTF-8');
    return substr($query, 0, 120);
}

function searchPageSize() {
    return max(12, min(80, (int)getSetting('search_page_size', 30)));
}

function searchSortOptions() {
    return [
        'relevance' => '관련도',
        'title' => '제목순',
        'artist' => '아티스트순',
        'album' => '앨범순',
        'newest' => '최근 추가순',
        'recent_played' => '최근 재생순',
        'popular' => '많이 들은 순',
        'rating' => '별점 높은 순',
        'liked' => '좋아요 우선',
        'duration' => '재생시간순',
        'random' => '랜덤',
    ];
}

function searchOrderSql($sort) {
    switch ($sort) {
        case 'title': return 's.title ASC, s.artist ASC';
        case 'artist': return 's.artist ASC, s.title ASC';
        case 'album': return 's.album ASC, s.track_no ASC, s.title ASC';
        case 'newest': return 's.created_at DESC, s.id DESC';
        case 'recent_played': return 'last_play.last_played_at IS NULL ASC, last_play.last_played_at DESC, s.title ASC';
        case 'popular': return 's.play_count DESC, s.title ASC';
        case 'rating': return 'COALESCE(r.rating, 0) DESC, s.play_count DESC, s.title ASC';
        case 'liked': return 'l.id IS NULL ASC, l.created_at DESC, s.title ASC';
        case 'duration': return 's.duration DESC, s.title ASC';
        case 'random': return 'RAND()';
        default: return 's.play_count DESC, COALESCE(r.rating, 0) DESC, s.title ASC';
    }
}

function searchFilterState($source = null) {
    $source = $source ?? $_GET;
    return [
        'q' => normalizeSearchQuery($source['q'] ?? $source['search'] ?? ''),
        'artist' => normalizeMetaParam($source['artist'] ?? ''),
        'album' => normalizeMetaParam($source['album'] ?? ''),
        'genre' => normalizeMetaParam($source['genre'] ?? ''),
        'tag_id' => max(0, (int)($source['tag_id'] ?? 0)),
        'min_rating' => max(0, min(5, (int)($source['min_rating'] ?? 0))),
        'liked' => (string)($source['liked'] ?? ''),
        'has_note' => (string)($source['has_note'] ?? ''),
        'has_lyrics' => (string)($source['has_lyrics'] ?? ''),
        'recent_played' => (string)($source['recent_played'] ?? ''),
        'popular_only' => !empty($source['popular_only']),
        'hide_missing_meta' => !empty($source['hide_missing_meta']),
        'manual_only' => !empty($source['manual_only']),
        'playlist_id' => max(0, (int)($source['playlist_id'] ?? 0)),
        'sort' => array_key_exists((string)($source['sort'] ?? ''), searchSortOptions()) ? (string)$source['sort'] : 'relevance',
        'page' => max(1, (int)($source['page'] ?? 1)),
    ];
}

function buildAdvancedSongSearch($filters, &$params, $forCount = false) {
    $joins = [];
    $where = ['s.is_missing = 0', 'COALESCE(s.is_hidden, 0) = 0'];
    $query = normalizeSearchQuery($filters['q'] ?? '');
    $tokens = searchTokens($query);
    $needsLyrics = !empty($tokens) || ($filters['has_lyrics'] ?? '') !== '';
    $needsRecent = ($filters['sort'] ?? '') === 'recent_played' || ($filters['recent_played'] ?? '') === '1';
    $needsPlaylist = (int)($filters['playlist_id'] ?? 0) > 0;

    if ($needsLyrics) $joins[] = 'LEFT JOIN lyrics ly ON ly.song_id = s.id';
    if ($needsRecent) {
        $joins[] = "LEFT JOIN (
            SELECT song_id, MAX(played_at) AS last_played_at
            FROM play_history
            WHERE played_at >= DATE_SUB(NOW(), INTERVAL 365 DAY)
            GROUP BY song_id
        ) last_play ON last_play.song_id = s.id";
    }
    if ($needsPlaylist) {
        $joins[] = 'INNER JOIN playlist_songs ps_filter ON ps_filter.song_id = s.id AND ps_filter.playlist_id = :filter_playlist_id';
        $params[':filter_playlist_id'] = (int)$filters['playlist_id'];
    }

    foreach ($tokens as $idx => $token) {
        $like = '%' . $token . '%';
        $parts = [];
        foreach (['title', 'artist', 'album', 'album_artist', 'genre', 'year', 'composer', 'folder_name'] as $column) {
            $name = ':sq_' . $idx . '_' . $column;
            $parts[] = 's.' . $column . ' LIKE ' . $name;
            $params[$name] = $like;
        }
        if ($needsLyrics) {
            $name = ':sq_' . $idx . '_lyrics';
            $parts[] = 'ly.lyrics_text LIKE ' . $name;
            $params[$name] = $like;
        }
        $name = ':sq_' . $idx . '_note';
        $parts[] = 'n.note_text LIKE ' . $name;
        $params[$name] = $like;

        $name = ':sq_' . $idx . '_tag';
        $parts[] = "EXISTS (
            SELECT 1 FROM song_tags st_q
            INNER JOIN tags t_q ON t_q.id = st_q.tag_id
            WHERE st_q.song_id = s.id AND t_q.name LIKE $name
        )";
        $params[$name] = $like;
        $where[] = '(' . implode(' OR ', $parts) . ')';
    }

    foreach (['artist', 'album', 'genre'] as $field) {
        if (($filters[$field] ?? '') !== '') {
            $name = ':filter_' . $field;
            $where[] = 's.' . $field . ' = ' . $name;
            $params[$name] = $filters[$field];
        }
    }
    if ((int)($filters['tag_id'] ?? 0) > 0) {
        $where[] = "EXISTS (SELECT 1 FROM song_tags st_filter WHERE st_filter.song_id = s.id AND st_filter.tag_id = :filter_tag_id)";
        $params[':filter_tag_id'] = (int)$filters['tag_id'];
    }
    if ((int)($filters['min_rating'] ?? 0) > 0) {
        $where[] = 'COALESCE(r.rating, 0) >= :filter_min_rating';
        $params[':filter_min_rating'] = (int)$filters['min_rating'];
    }
    if (($filters['liked'] ?? '') === '1') $where[] = 'l.id IS NOT NULL';
    if (($filters['liked'] ?? '') === '0') $where[] = 'l.id IS NULL';
    if (($filters['has_note'] ?? '') === '1') $where[] = "n.note_text IS NOT NULL AND TRIM(n.note_text) != ''";
    if (($filters['has_note'] ?? '') === '0') $where[] = "(n.note_text IS NULL OR TRIM(n.note_text) = '')";
    if (($filters['has_lyrics'] ?? '') === '1') $where[] = "ly.lyrics_text IS NOT NULL AND TRIM(ly.lyrics_text) != ''";
    if (($filters['has_lyrics'] ?? '') === '0') $where[] = "(ly.lyrics_text IS NULL OR TRIM(ly.lyrics_text) = '')";
    if (($filters['recent_played'] ?? '') === '1') $where[] = 'last_play.last_played_at IS NOT NULL';
    if (!empty($filters['popular_only'])) $where[] = 'COALESCE(s.play_count, 0) > 0';
    if (!empty($filters['hide_missing_meta'])) {
        $where[] = "TRIM(COALESCE(s.title, '')) != ''";
        $where[] = "TRIM(COALESCE(s.artist, '')) != ''";
        $where[] = "TRIM(COALESCE(s.album, '')) != ''";
        $where[] = "TRIM(COALESCE(s.genre, '')) != ''";
    }
    if (!empty($filters['manual_only'])) $where[] = 'COALESCE(s.is_manual_edited, 0) = 1';

    return [
        'joins' => implode("\n", array_unique($joins)),
        'where' => implode(' AND ', $where),
    ];
}

function searchSongsAdvanced($filters) {
    $params = [];
    $parts = buildAdvancedSongSearch($filters, $params, false);
    $limit = searchPageSize();
    $page = max(1, (int)($filters['page'] ?? 1));
    $offset = ($page - 1) * $limit;
    $sort = $filters['sort'] ?? 'relevance';

    $countSql = "
        SELECT COUNT(DISTINCT s.id)
        FROM songs s
        LEFT JOIN liked_songs l ON s.id = l.song_id
        LEFT JOIN track_ratings r ON r.song_id = s.id
        LEFT JOIN track_notes n ON n.song_id = s.id
        {$parts['joins']}
        WHERE {$parts['where']}
    ";
    $stmt = getDBConnection()->prepare($countSql);
    $stmt->execute($params);
    $total = (int)$stmt->fetchColumn();

    $sql = baseSongSelectSql() . "
        {$parts['joins']}
        WHERE {$parts['where']}
        ORDER BY " . searchOrderSql($sort) . "
        LIMIT " . (int)$limit . " OFFSET " . (int)$offset;
    $stmt = getDBConnection()->prepare($sql);
    $stmt->execute($params);

    return [
        'songs' => $stmt->fetchAll(),
        'total' => $total,
        'page' => $page,
        'page_size' => $limit,
        'pages' => max(1, (int)ceil($total / $limit)),
    ];
}

function searchCategoryResults($query) {
    $query = normalizeSearchQuery($query);
    if ($query === '') return ['artists' => [], 'albums' => [], 'playlists' => [], 'tags' => [], 'genres' => []];
    ensureTagTables();
    $pdo = getDBConnection();
    $tokens = searchTokens($query);
    $build = function ($prefix, $fields) use ($tokens) {
        $where = [];
        $params = [];
        foreach ($tokens as $idx => $token) {
            $parts = [];
            foreach ($fields as $fieldIdx => $field) {
                $name = ':' . $prefix . '_' . $idx . '_' . $fieldIdx;
                $parts[] = $field . ' LIKE ' . $name;
                $params[$name] = '%' . $token . '%';
            }
            $where[] = '(' . implode(' OR ', $parts) . ')';
        }
        return [implode(' AND ', $where), $params];
    };

    [$artistWhere, $artistParams] = $build('artist', ["COALESCE(NULLIF(TRIM(artist), ''), '알 수 없는 아티스트')"]);
    $stmt = $pdo->prepare("
        SELECT COALESCE(NULLIF(TRIM(artist), ''), '알 수 없는 아티스트') AS artist_name,
               COUNT(*) AS song_count, COUNT(DISTINCT album) AS album_count, MIN(id) AS cover_song_id, SUM(play_count) AS total_plays
        FROM songs
        WHERE is_missing = 0 AND COALESCE(is_hidden, 0) = 0 AND $artistWhere
        GROUP BY artist_name
        ORDER BY song_count DESC, artist_name ASC
        LIMIT 20
    ");
    $stmt->execute($artistParams);
    $artists = $stmt->fetchAll();

    [$albumWhere, $albumParams] = $build('album', ["COALESCE(NULLIF(TRIM(album), ''), '알 수 없는 앨범')", "COALESCE(NULLIF(TRIM(artist), ''), '알 수 없는 아티스트')"]);
    $stmt = $pdo->prepare("
        SELECT COALESCE(NULLIF(TRIM(album), ''), '알 수 없는 앨범') AS album_name,
               COALESCE(NULLIF(TRIM(artist), ''), '알 수 없는 아티스트') AS artist_name,
               COUNT(*) AS song_count, SUM(duration) AS total_duration, MIN(id) AS cover_song_id
        FROM songs
        WHERE is_missing = 0 AND COALESCE(is_hidden, 0) = 0 AND $albumWhere
        GROUP BY album_name, artist_name
        ORDER BY song_count DESC, album_name ASC
        LIMIT 24
    ");
    $stmt->execute($albumParams);
    $albums = $stmt->fetchAll();

    [$genreWhere, $genreParams] = $build('genre', ["COALESCE(NULLIF(TRIM(genre), ''), '알 수 없는 장르')"]);
    $stmt = $pdo->prepare("
        SELECT COALESCE(NULLIF(TRIM(genre), ''), '알 수 없는 장르') AS genre_name,
               COUNT(*) AS song_count, SUM(play_count) AS total_plays
        FROM songs
        WHERE is_missing = 0 AND COALESCE(is_hidden, 0) = 0 AND $genreWhere
        GROUP BY genre_name
        ORDER BY song_count DESC, genre_name ASC
        LIMIT 20
    ");
    $stmt->execute($genreParams);
    $genres = $stmt->fetchAll();

    [$playlistWhere, $playlistParams] = $build('playlist', ['p.name', 'p.description']);
    $stmt = $pdo->prepare("
        SELECT p.id, p.name, p.description, COUNT(s_count.id) AS song_count
        FROM playlists p
        LEFT JOIN playlist_songs ps ON ps.playlist_id = p.id
        LEFT JOIN songs s_count ON s_count.id = ps.song_id AND s_count.is_missing = 0 AND COALESCE(s_count.is_hidden, 0) = 0
        WHERE $playlistWhere
        GROUP BY p.id
        ORDER BY p.name ASC
        LIMIT 20
    ");
    $stmt->execute($playlistParams);
    $playlists = $stmt->fetchAll();

    [$tagWhere, $tagParams] = $build('tag', ['t.name']);
    $stmt = $pdo->prepare("
        SELECT t.id, t.name, COUNT(s_tag.id) AS song_count, MAX(st.created_at) AS last_used_at
        FROM tags t
        LEFT JOIN song_tags st ON st.tag_id = t.id
        LEFT JOIN songs s_tag ON s_tag.id = st.song_id AND s_tag.is_missing = 0 AND COALESCE(s_tag.is_hidden, 0) = 0
        WHERE $tagWhere
        GROUP BY t.id, t.name
        ORDER BY song_count DESC, t.name ASC
        LIMIT 20
    ");
    $stmt->execute($tagParams);
    $tags = $stmt->fetchAll();

    return compact('artists', 'albums', 'genres', 'playlists', 'tags');
}

function searchLyricsSnippet($lyrics, $query, $radius = 56) {
    $text = preg_replace('/\[(\d{1,2}):(\d{2})(?:[.:](\d{1,3}))?\]/u', '', (string)$lyrics);
    $text = trim(preg_replace('/\s+/u', ' ', $text));
    if ($text === '') return '';

    $needle = normalizeSearchQuery($query);
    $tokens = searchTokens($needle);
    $match = $needle;
    if ($match === '' || (function_exists('mb_stripos') ? mb_stripos($text, $match, 0, 'UTF-8') === false : stripos($text, $match) === false)) {
        $match = $tokens[0] ?? '';
    }

    if ($match !== '') {
        $pos = function_exists('mb_stripos') ? mb_stripos($text, $match, 0, 'UTF-8') : stripos($text, $match);
    } else {
        $pos = false;
    }
    if ($pos === false) $pos = 0;

    $length = function_exists('mb_strlen') ? mb_strlen($text, 'UTF-8') : strlen($text);
    $start = max(0, (int)$pos - (int)$radius);
    $size = min(160, $length - $start);
    $slice = function_exists('mb_substr') ? mb_substr($text, $start, $size, 'UTF-8') : substr($text, $start, $size);
    if ($start > 0) $slice = '...' . ltrim($slice);
    if ($start + $size < $length) $slice = rtrim($slice) . '...';
    return $slice;
}

function searchLyricsResults($query, $limit = 12) {
    $query = normalizeSearchQuery($query);
    $tokens = searchTokens($query);
    if ($query === '' || empty($tokens)) return ['items' => [], 'total' => 0];

    $params = [];
    $where = [
        's.is_missing = 0',
        'COALESCE(s.is_hidden, 0) = 0',
        "ly.lyrics_text IS NOT NULL",
        "TRIM(ly.lyrics_text) != ''",
    ];
    foreach ($tokens as $idx => $token) {
        $name = ':lyric_' . $idx;
        $where[] = 'ly.lyrics_text LIKE ' . $name;
        $params[$name] = '%' . $token . '%';
    }
    $whereSql = implode(' AND ', $where);
    $pdo = getDBConnection();

    $stmt = $pdo->prepare("
        SELECT COUNT(DISTINCT s.id)
        FROM songs s
        INNER JOIN lyrics ly ON ly.song_id = s.id
        WHERE $whereSql
    ");
    $stmt->execute($params);
    $total = (int)$stmt->fetchColumn();

    $selectSql = str_replace(
        'IF(n.note_text IS NOT NULL AND TRIM(n.note_text) != \'\', 1, 0) AS has_note',
        'ly.lyrics_text, IF(n.note_text IS NOT NULL AND TRIM(n.note_text) != \'\', 1, 0) AS has_note',
        baseSongSelectSql()
    );
    $sql = $selectSql . "
        INNER JOIN lyrics ly ON ly.song_id = s.id
        WHERE $whereSql
        ORDER BY s.play_count DESC, s.title ASC
        LIMIT " . (int)max(1, min(30, $limit));
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    $rows = $stmt->fetchAll();

    $items = [];
    foreach ($rows as $row) {
        $items[] = [
            'song' => $row,
            'snippet' => searchLyricsSnippet($row['lyrics_text'] ?? '', $query),
        ];
    }
    return ['items' => $items, 'total' => $total];
}

function searchRecentFallbackSongs($limit = 12) {
    return fetchSongs('s.is_missing = 0 AND COALESCE(s.is_hidden, 0) = 0', [], 's.created_at DESC, s.id DESC', $limit);
}

function searchPopularFallbackSongs($limit = 12) {
    return fetchSongs('s.is_missing = 0 AND COALESCE(s.is_hidden, 0) = 0 AND COALESCE(s.play_count, 0) > 0', [], 's.play_count DESC, s.title ASC', $limit);
}

function searchUrlWith($changes = []) {
    $params = $_GET;
    foreach ($changes as $key => $value) {
        if ($value === null || $value === '') unset($params[$key]);
        else $params[$key] = $value;
    }
    return 'search.php?' . http_build_query($params);
}
