File size: 6,547 Bytes
4bea261
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import { txasupabase } from './txasupabase.js';

const getLocalMovies = () => txasupabase.getLocalMovies();
const getDeletedMovies = () => txasupabase.getDeletedMovies();

export { formatImageUrl } from './image.js';

// Hàm định dạng phim cục bộ thành định dạng danh sách rút gọn
function mapLocalToListItem(localMovie) {
  return {
    _id: localMovie.movie.id || localMovie.movie.slug,
    name: localMovie.movie.name,
    slug: localMovie.movie.slug,
    origin_name: localMovie.movie.origin_name,
    thumb_url: localMovie.movie.thumb_url,
    poster_url: localMovie.movie.poster_url,
    year: localMovie.movie.year,
    quality: localMovie.movie.quality || 'FHD',
    lang: localMovie.movie.lang || 'Vietsub',
    episode_current: localMovie.movie.episode_current || 'Tập 1',
    type: localMovie.movie.type || 'single',
    isLocal: true
  };
}

// Trích tên gốc phim (bỏ Phần/Part/Season/SS/EP suffix)
function getBaseMovieName(name) {
  if (!name) return '';
  let clean = name;
  // 1. Xóa các cụm chứa ngoặc đơn hoặc ngoặc vuông chứa Phần/Part/Season (ví dụ: "(Phần 2)", "[Season 3]")
  clean = clean.replace(/\s*[([\])]*\s*(Phần|Part|Season|SS|EP|Ep|Tập)\s*(\d+|[IVXLCDM]+)\s*[([\])]*/gi, '');
  // 2. Xóa các cụm Phần/Part/Season đứng độc lập ở cuối
  clean = clean.replace(/\s*[-–—|:]?\s*(Phần|Part|Season|SS|EP|Ep|Tập)\s*(\d+|[IVXLCDM]+)\b/gi, '');
  // 3. Xóa các số hoặc ký tự số La Mã cô độc ở cuối
  clean = clean.replace(/\s*[-–—|:]?\s*(\d+|[IVXLCDM]+)$/gi, '');
  // 4. Dọn dẹp sạch các dấu ngoặc đơn/ngoặc vuông rỗng còn sót lại ở cuối chuỗi
  clean = clean.replace(/\s*[([\])]+\s*$/gi, '');
  return clean.trim().toLowerCase();
}

// Nhóm phim theo tên gốc, giữ lại phần mới nhất cho listing
function groupMoviesBySeason(movies) {
  const groups = new Map();
  
  for (const m of movies) {
    const base = getBaseMovieName(m.movie.name);
    if (!base || base.length <= 2) {
      // Tên quá ngắn, không nhóm
      groups.set(m.movie.slug, m);
      continue;
    }
    
    const existing = groups.get(base);
    if (!existing) {
      groups.set(base, m);
    } else {
      // Giữ phần cập nhật mới nhất
      const existingTime = new Date(existing.movie.modified?.time || 0).getTime();
      const currentTime = new Date(m.movie.modified?.time || 0).getTime();
      if (currentTime > existingTime) {
        groups.set(base, m);
      }
    }
  }
  
  return Array.from(groups.values());
}

// Hàm lọc và phân trang phim cục bộ
// grouped = true: gộp phim cùng tên khác phần (mặc định cho trang công khai)
// grouped = false: không gộp (dùng cho trang admin)
async function queryLocalMovies(filterFn, page = 1, limit = 12, { grouped = true } = {}) {
  const localMovies = await getLocalMovies();
  const deletedSlugs = await getDeletedMovies();
  
  let list = Object.values(localMovies)
    .filter(m => !deletedSlugs.includes(m.movie.slug));
  
  if (filterFn) {
    list = list.filter(filterFn);
  }
  
  // Sắp xếp theo ngày cập nhật mới nhất (modified time)
  list.sort((a, b) => {
    const timeA = new Date(a.movie.modified?.time || 0).getTime();
    const timeB = new Date(b.movie.modified?.time || 0).getTime();
    return timeB - timeA;
  });

  // Nhóm phim theo season/part — chỉ hiển thị phần mới nhất trong listing
  if (grouped) {
    list = groupMoviesBySeason(list);
  }
  
  const totalItems = list.length;
  const totalPages = Math.ceil(totalItems / limit) || 1;
  const paginated = list.slice((page - 1) * limit, page * limit)
    .map(mapLocalToListItem);
    
  return {
    items: paginated,
    pagination: {
      totalItems,
      totalItemsPerPage: limit,
      currentPage: page,
      totalPages
    }
  };
}

/**
 * Lấy danh sách phim mới cập nhật
 * @param {number} page
 * @param {{ grouped?: boolean }} options - set grouped: false for admin pages
 */
export async function getNewestMovies(page = 1, options = {}) {
  return await queryLocalMovies(null, page, 12, options);
}

/**
 * Lấy chi tiết phim theo slug
 */
export async function getMovieDetail(slug) {
  const deletedSlugs = await getDeletedMovies();
  if (deletedSlugs.includes(slug)) {
    return null;
  }
  
  const localMovies = await getLocalMovies();
  if (localMovies[slug]) {
    return {
      status: true,
      movie: localMovies[slug].movie,
      episodes: localMovies[slug].episodes || []
    };
  }
  return null;
}

export async function getSeriesMovies(page = 1) {
  return await queryLocalMovies(m => m.movie.type === 'series', page, 12);
}

export async function getSingleMovies(page = 1) {
  return await queryLocalMovies(m => m.movie.type === 'single', page, 12);
}

export async function getCartoonMovies(page = 1) {
  return await queryLocalMovies(m => m.movie.type === 'hoat-hinh', page, 12);
}

export async function getTvShows(page = 1) {
  return await queryLocalMovies(m => m.movie.type === 'tvshows' || m.movie.type === 'tv-shows', page, 12);
}

export async function getMoviesByCategory(category, page = 1) {
  const defaultTypes = {
    'phim-bo': 'series',
    'phim-le': 'single',
    'hoat-hinh': 'hoat-hinh',
    'tv-shows': 'tv-shows'
  };
  
  if (defaultTypes[category]) {
    return await queryLocalMovies(m => m.movie.type === defaultTypes[category], page, 12);
  }
  
  return await queryLocalMovies(m => 
    m.movie.category?.some(c => c.slug === category), page, 12
  );
}

export async function getMoviesByCountry(country, page = 1) {
  return await queryLocalMovies(m => 
    m.movie.country?.some(c => c.slug === country), page, 12
  );
}

export async function searchMovies(keyword, page = 1, options = {}) {
  if (!keyword) {
    return { items: [], pagination: { totalItems: 0, totalItemsPerPage: 12, currentPage: page, totalPages: 1 } };
  }
  const k = keyword.toLowerCase();
  return await queryLocalMovies(m => 
    m.movie.name?.toLowerCase().includes(k) || 
    m.movie.origin_name?.toLowerCase().includes(k) || 
    m.movie.slug?.includes(k), page, 12, options
  );
}

export async function getMoviesByTopic(topicSlug, page = 1) {
  const { TOPICS } = await import('./topics.js');
  const topic = TOPICS.find(t => t.slug === topicSlug);
  if (!topic) {
    return { items: [], pagination: { totalItems: 0, totalItemsPerPage: 12, currentPage: page, totalPages: 1 } };
  }
  return await queryLocalMovies(topic.filter, page, 12);
}