import type { AppSupabaseClient } from '../supabase/client'; export interface WatchSessionState { playing: boolean; positionSeconds: number; updatedAtMs: number; } export interface WatchSession { id: string; conversationId: string; ownerUserId: string; videoId: string; startedAt: string; endedAt: string | null; currentState: WatchSessionState; } export function parseYouTubeUrl(input: string): string | null { const s = input.trim(); if (!s) return null; if (/^[A-Za-z0-9_-]{11}$/.test(s)) return s; const patterns = [ /[?&]v=([A-Za-z0-9_-]{11})/, /youtu\.be\/([A-Za-z0-9_-]{11})/, /youtube\.com\/embed\/([A-Za-z0-9_-]{11})/, /youtube\.com\/shorts\/([A-Za-z0-9_-]{11})/, ]; for (const re of patterns) { const m = re.exec(s); if (m && m[1]) return m[1]; } return null; } export async function createWatchSession( client: AppSupabaseClient, params: { conversationId: string; videoId: string }, ): Promise { const { data: session } = await client.auth.getUser(); if (!session.user) throw new Error('not authenticated'); const { data, error } = await client .from('conversation_watch_sessions') .insert({ conversation_id: params.conversationId, owner_user_id: session.user.id, video_id: params.videoId, }) .select('id, conversation_id, owner_user_id, video_id, started_at, ended_at, current_state') .single(); if (error) throw error; return mapRow(data); } export async function getWatchSession( client: AppSupabaseClient, sessionId: string, ): Promise { const { data, error } = await client .from('conversation_watch_sessions') .select('id, conversation_id, owner_user_id, video_id, started_at, ended_at, current_state') .eq('id', sessionId) .maybeSingle(); if (error) throw error; return data ? mapRow(data) : null; } export async function updateWatchSessionState( client: AppSupabaseClient, sessionId: string, state: WatchSessionState, ): Promise { const { error } = await client .from('conversation_watch_sessions') .update({ current_state: { playing: state.playing, position_seconds: state.positionSeconds, updated_at_ms: state.updatedAtMs, }, }) .eq('id', sessionId); if (error) throw error; } export async function endWatchSession( client: AppSupabaseClient, sessionId: string, ): Promise { const { error } = await client .from('conversation_watch_sessions') .update({ ended_at: new Date().toISOString() }) .eq('id', sessionId); if (error) throw error; } function mapRow(row: { id: string; conversation_id: string; owner_user_id: string; video_id: string; started_at: string; ended_at: string | null; current_state: unknown; }): WatchSession { const raw = (row.current_state ?? {}) as Partial<{ playing: boolean; position_seconds: number; updated_at_ms: number; }>; return { id: row.id, conversationId: row.conversation_id, ownerUserId: row.owner_user_id, videoId: row.video_id, startedAt: row.started_at, endedAt: row.ended_at, currentState: { playing: typeof raw.playing === 'boolean' ? raw.playing : false, positionSeconds: typeof raw.position_seconds === 'number' ? raw.position_seconds : 0, updatedAtMs: typeof raw.updated_at_ms === 'number' ? raw.updated_at_ms : 0, }, }; }