"""Component library repositories — Phase 11.2.""" from __future__ import annotations from typing import Sequence from uuid import UUID from sqlalchemy import func, select from app.models.components import ( ExperienceComponent, ExperienceComponentVersion, PageComponentPlacement, ) from app.repositories.base import TenantBaseRepository from app.specifications.components import ( ComponentListSpec, ComponentVersionListSpec, PlacementListSpec, ) class ExperienceComponentRepository(TenantBaseRepository[ExperienceComponent]): model = ExperienceComponent async def get_by_code( self, tenant_id: UUID, workspace_id: UUID, code: str ) -> ExperienceComponent | None: stmt = select(self.model).where( self.model.tenant_id == tenant_id, self.model.workspace_id == workspace_id, self.model.code == code, self.model.is_deleted.is_(False), ) result = await self.session.execute(stmt) return result.scalar_one_or_none() async def list_filtered( self, tenant_id: UUID, *, offset: int, limit: int, spec: ComponentListSpec | None = None, ) -> tuple[Sequence[ExperienceComponent], int]: base = select(self.model).where( self.model.tenant_id == tenant_id, self.model.is_deleted.is_(False), ) count_base = select(func.count()).select_from(self.model).where( self.model.tenant_id == tenant_id, self.model.is_deleted.is_(False), ) if spec is not None: base = spec.apply(base) for clause in spec.filter_clauses(): count_base = count_base.where(clause) else: base = base.order_by(self.model.created_at.desc()) total = int((await self.session.execute(count_base)).scalar_one()) result = await self.session.execute(base.offset(offset).limit(limit)) return result.scalars().all(), total class ExperienceComponentVersionRepository( TenantBaseRepository[ExperienceComponentVersion] ): model = ExperienceComponentVersion async def get_by_number( self, tenant_id: UUID, component_id: UUID, version_number: int ) -> ExperienceComponentVersion | None: stmt = select(self.model).where( self.model.tenant_id == tenant_id, self.model.component_id == component_id, self.model.version_number == version_number, self.model.is_deleted.is_(False), ) result = await self.session.execute(stmt) return result.scalar_one_or_none() async def max_version_number(self, tenant_id: UUID, component_id: UUID) -> int: stmt = select(func.max(self.model.version_number)).where( self.model.tenant_id == tenant_id, self.model.component_id == component_id, self.model.is_deleted.is_(False), ) result = await self.session.execute(stmt) value = result.scalar_one() return int(value or 0) async def list_filtered( self, tenant_id: UUID, *, offset: int, limit: int, spec: ComponentVersionListSpec | None = None, ) -> tuple[Sequence[ExperienceComponentVersion], int]: base = select(self.model).where( self.model.tenant_id == tenant_id, self.model.is_deleted.is_(False), ) count_base = select(func.count()).select_from(self.model).where( self.model.tenant_id == tenant_id, self.model.is_deleted.is_(False), ) if spec is not None: base = spec.apply(base) for clause in spec.filter_clauses(): count_base = count_base.where(clause) else: base = base.order_by(self.model.version_number.desc()) total = int((await self.session.execute(count_base)).scalar_one()) result = await self.session.execute(base.offset(offset).limit(limit)) return result.scalars().all(), total class PageComponentPlacementRepository(TenantBaseRepository[PageComponentPlacement]): model = PageComponentPlacement async def get_by_slot( self, tenant_id: UUID, page_id: UUID, slot_key: str ) -> PageComponentPlacement | None: stmt = select(self.model).where( self.model.tenant_id == tenant_id, self.model.page_id == page_id, self.model.slot_key == slot_key, self.model.is_deleted.is_(False), ) result = await self.session.execute(stmt) return result.scalar_one_or_none() async def count_by_component(self, tenant_id: UUID, component_id: UUID) -> int: stmt = select(func.count()).select_from(self.model).where( self.model.tenant_id == tenant_id, self.model.component_id == component_id, self.model.is_deleted.is_(False), ) result = await self.session.execute(stmt) return int(result.scalar_one()) async def count_by_version( self, tenant_id: UUID, component_version_id: UUID ) -> int: stmt = select(func.count()).select_from(self.model).where( self.model.tenant_id == tenant_id, self.model.component_version_id == component_version_id, self.model.is_deleted.is_(False), ) result = await self.session.execute(stmt) return int(result.scalar_one()) async def list_filtered( self, tenant_id: UUID, *, offset: int, limit: int, spec: PlacementListSpec | None = None, ) -> tuple[Sequence[PageComponentPlacement], int]: base = select(self.model).where( self.model.tenant_id == tenant_id, self.model.is_deleted.is_(False), ) count_base = select(func.count()).select_from(self.model).where( self.model.tenant_id == tenant_id, self.model.is_deleted.is_(False), ) if spec is not None: base = spec.apply(base) for clause in spec.filter_clauses(): count_base = count_base.where(clause) else: base = base.order_by(self.model.sort_order.asc()) total = int((await self.session.execute(count_base)).scalar_one()) result = await self.session.execute(base.offset(offset).limit(limit)) return result.scalars().all(), total