"""Plex browse: the library list, the unified cross-library browse feed, and its faceted filters.""" from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session from app import sysconfig from app.auth import current_user from app.db import get_db from app.mailtext import lang_for_user from app.models import User from app.plex import library as plex_library from app.plex import queries from app.plex.cards import display_title, episode_card from app.plex.query import LibraryFilters from app.routes.plex._shared import _norm_lang router = APIRouter(prefix="/api/plex", tags=["plex"]) @router.get("/libraries") def list_libraries(user: User = Depends(current_user), db: Session = Depends(get_db)) -> dict: """The mirrored libraries the user can browse (drives the Plex scope selector). HU overlay libraries are excluded — they're not browsable, they only feed the EN twin's Hungarian metadata.""" out = [ {"key": lib.plex_key, "title": lib.title, "kind": lib.kind, "count": count} for lib, count in queries.browsable_libraries(db, lang_for_user(user)) ] return {"enabled": sysconfig.get_bool(db, "plex_enabled"), "libraries": out} @router.get("/library") def unified_library( scope: str = "both", q: str | None = None, field: str = "all", sort: str = "added", sort_dir: str = "desc", show: str = "all", lang: str = "en", offset: int = 0, limit: int = Query(default=40, ge=1, le=100), f: LibraryFilters = Depends(), user: User = Depends(current_user), db: Session = Depends(get_db), ) -> dict: """Unified cross-library browse: movies + shows in ONE mixed, paginated feed, scoped to movie|show|both, with the shared filters/search/watch-state (a show's state is the aggregate of its episodes). On a search that also matches episodes (and shows are in scope), the matching episodes come back in a separate `episodes` list (grouped result — the 'Episodes' section).""" offset = max(0, offset) lang = _norm_lang(lang) total, rows, eps = plex_library.library_page( db, user, scope=scope, q=q, field=field, sort=sort, sort_dir=sort_dir, show=show, lang=lang, offset=offset, limit=limit, f=f, ) items = [] for r in rows: if r.kind == "movie": items.append( { "id": r.rk, "type": "movie", "title": r.title, "year": r.year, "rating": r.rating, "summary": r.summary, "duration_seconds": r.duration_s, "thumb": f"/api/plex/image/{r.rk}", "playable": r.playable, "transcode_hw": r.transcode_hw, "status": r.status, "position_seconds": r.position_seconds, } ) else: items.append( { "id": r.rk, "type": "show", "title": r.title, "year": r.year, "rating": r.rating, "summary": r.summary, "thumb": f"/api/plex/image/{r.rk}", "season_count": r.season_count, "status": r.status, "resumable": r.resumable, } ) episodes = [] for e, s, sh in eps: card = episode_card(e, s, lang=lang) card["show_title"] = display_title(sh, lang) if sh is not None else None episodes.append(card) return { "scope": scope, "total": total, "offset": offset, "limit": limit, "items": items, "episodes": episodes, } @router.get("/facets") def facets( scope: str = "both", show: str = "all", lang: str = "en", f: LibraryFilters = Depends(), user: User = Depends(current_user), db: Session = Depends(get_db), ) -> dict: """Available filter values for the unified library (scope movie|show|both), ADAPTED to the active filters (faceted search): each group's values/bounds are computed with all the OTHER active filters applied but NOT its own — so selecting a filter narrows what the other groups offer, while a multi-select group still lets you add more. Merged across the scope's libraries; duration is movie-only. (Search text is not factored in — facets track the sidebar filters.)""" return plex_library.facets(db, user, scope=scope, show=show, lang=lang, f=f)