/* ============================================================
   BACKEND — Supabase client + auth + media-upload helpers.
   No-build: loaded after the @supabase/supabase-js CDN script,
   exposed on window.* like the rest of the app (Game, Room, audio).
   ============================================================ */

/* Public project values (safe in the browser — the publishable key is
   RLS-gated; never put the `sb_secret_…` key here). */
const SUPABASE_URL  = 'https://sagixvxfvlkofclgfslv.supabase.co';
const SUPABASE_KEY  = 'sb_publishable_nWAcLD6BllHRoqe5I9pmZg_fHIci2lc';
const FUNCTIONS_URL = SUPABASE_URL + '/functions/v1';

/* guard: the CDN <script> must have loaded first */
if(!window.supabase || !window.supabase.createClient){
  console.error('[backend] @supabase/supabase-js not loaded — check the CDN <script> in index.html');
}
const sb = window.supabase.createClient(SUPABASE_URL, SUPABASE_KEY, {
  auth: { persistSession: true, autoRefreshToken: true },
});
window.sb = sb;

/* ---------- auth: guest / login / upgrade (Phase 1) ---------- */
window.Auth = {
  // "Play as guest" — instant user id, no email. Requires Anonymous
  // sign-ins to be enabled in Supabase → Auth → Providers.
  guest:   ()                  => sb.auth.signInAnonymously(),
  signup:  (email, password)   => sb.auth.signUp({ email, password }),
  login:   (email, password)   => sb.auth.signInWithPassword({ email, password }),
  google:  ()                  => sb.auth.signInWithOAuth({ provider: 'google' }),
  // "Save your progress" — link an email/password to the existing
  // anonymous user so their games & uploads survive.
  upgrade: (email, password)   => sb.auth.updateUser({ email, password }),
  signOut: ()                  => sb.auth.signOut(),
  user:    async ()            => (await sb.auth.getUser()).data.user,
  onChange:(cb)                => sb.auth.onAuthStateChange((_e, session)=> cb(session && session.user)),
};

/* ---------- media uploads (Phases 4–5) ----------
   Inert until the Edge Functions are deployed and Cloudflare is configured;
   wired here so the front end is ready the moment they are. */
async function token(){ const { data } = await sb.auth.getSession(); return data.session && data.session.access_token; }

async function callFn(name, body){
  const t = await token();
  const res = await fetch(`${FUNCTIONS_URL}/${name}`, {
    method:'POST',
    headers:{ Authorization:`Bearer ${t}`, 'Content-Type':'application/json' },
    body: JSON.stringify(body||{}),
  });
  if(!res.ok) throw new Error(`${name}: ${res.status} ${await res.text()}`);
  return res.json();
}

// category ∈ item|npc|enemy|boss|map
window.uploadVideo = async (file, { gameId=null, category })=>{
  if(file.size > 200*1024*1024) throw new Error('Video too large (200 MB cap)');
  const { uploadURL, assetId } = await callFn('create-video-upload', { gameId, category });
  const form = new FormData(); form.append('file', file);
  const up = await fetch(uploadURL, { method:'POST', body:form });
  if(!up.ok) throw new Error('Cloudflare upload failed');
  return assetId;   // watch the asset row for status → 'ready'
};

window.uploadImage = async (file, { gameId=null, category })=>{
  if(file.size > 20*1024*1024) throw new Error('Image too large (20 MB cap)');
  const { uploadURL, imageId, assetId, deliveryBase } = await callFn('create-image-upload', { gameId, category });
  const form = new FormData(); form.append('file', file);
  const up = await fetch(uploadURL, { method:'POST', body:form });
  if(!up.ok) throw new Error('Cloudflare upload failed');
  // Images are ready immediately; the owner marks the row ready (RLS allows it).
  if(deliveryBase){
    await sb.from('assets').update({
      status:'ready',
      playback_url:  `${deliveryBase}/${imageId}/public`,
      thumbnail_url: `${deliveryBase}/${imageId}/thumbnail`,
    }).eq('id', assetId);
  }
  return assetId;
};

// live-update a UI when an asset finishes processing
window.watchAsset = (assetId, onReady)=>
  sb.channel(`asset:${assetId}`)
    .on('postgres_changes',
        { event:'UPDATE', schema:'public', table:'assets', filter:`id=eq.${assetId}` },
        ({ new:row })=>{ if(row.status==='ready') onReady(row); })
    .subscribe();

/* ---------- campaigns (games) — owner-scoped CRUD (Phase 5) ----------
   RLS requires owner_id = auth.uid() on insert (the column has no default),
   so create() always stamps the current user. Reads/updates/deletes are
   gated by RLS to the owner, so no extra owner filter is needed. */
async function currentUser(){ const { data } = await sb.auth.getUser(); return data.user; }
window.Games = {
  async list(){
    const { data, error } = await sb.from('games')
      .select('id,title,description,is_published,created_at')
      .order('created_at', { ascending:false });
    if(error) throw error; return data || [];
  },
  async create({ title, description } = {}){
    const u = await currentUser(); if(!u) throw new Error('Sign in to create a campaign.');
    const { data, error } = await sb.from('games')
      .insert({ owner_id:u.id, title:(title||'').trim() || 'Untitled campaign',
                description:(description||'').trim() || null })
      .select('id,title,description,is_published,created_at').single();
    if(error) throw error; return data;
  },
  async update(id, patch){
    const { data, error } = await sb.from('games').update(patch).eq('id', id)
      .select('id,title,description,is_published,created_at').single();
    if(error) throw error; return data;
  },
  async remove(id){ const { error } = await sb.from('games').delete().eq('id', id); if(error) throw error; },
};

/* ---------- assets (uploaded media) — owner-scoped reads (Phase 4) ----------
   Rows are *created* by the upload Edge Functions (service role); the client
   only reads/deletes its own. RLS gates everything to the owner. */
window.Assets = {
  async listForGame(gameId){
    const { data, error } = await sb.from('assets')
      .select('id,kind,category,status,playback_url,thumbnail_url,created_at')
      .eq('game_id', gameId).order('created_at', { ascending:false });
    if(error) throw error; return data || [];
  },
  async remove(id){ const { error } = await sb.from('assets').delete().eq('id', id); if(error) throw error; },
};

// one entry point that routes by file type to the right broker
window.uploadAsset = async (file, opts)=>{
  if(file.type && file.type.indexOf('video/')===0) return window.uploadVideo(file, opts);
  if(file.type && file.type.indexOf('image/')===0) return window.uploadImage(file, opts);
  throw new Error('Only image or video files are supported.');
};

// live-refresh a game's whole asset list (insert / update / delete). Returns the
// channel; clean up with sb.removeChannel(channel).
window.watchGameAssets = (gameId, onChange)=>
  sb.channel(`game-assets:${gameId}`)
    .on('postgres_changes',
        { event:'*', schema:'public', table:'assets', filter:`game_id=eq.${gameId}` },
        onChange)
    .subscribe();
