import React, { useEffect, useMemo, useRef, useState } from 'react';
import { createRoot } from 'react-dom/client';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import {
  signInWithEmail, signUpWithEmail, signOut, onSessionChange,
  getMyProfile, updateMyProfile, listProfileMedia, uploadProfileMedia, removeProfileMedia,
  listConversations, createDirectConversation, sendMessage, subscribeToMessages, markConversationRead,
  listDiscoverableProfiles, updatePrivacySettings, requestProfileVerification,
  uploadChatMedia, getChatMediaUrl, REDBOY_PREFERENCES, REDBOY_TAGS, REDBOY_FETISHES,
  watchApproximateLocation, saveApproximateLocation, listNearbyApproximateLocations,
  getSupabaseClient, blockUser, listBlockedUsers, reportUser, REPORT_REASONS, blockReasonLabel,
  getPlan, startAsap, stopAsap, getMyActiveAsap, listActiveAsapProfiles,
  requestAccountDeletion, sendSupportRequest, isAdminUser, listOpenReports, updateReportStatus,
  moderateUser, listDeletionRequests, listSupportRequests, legalSections,
  checkoutRedboyPlus, openBillingPortal, registerRedboyPwa, promptPwaInstall, isStandalonePwa,
} from './lib';
import './styles.css';

registerRedboyPwa();

const ROLES=['Activo','Pasivo','Inter','Inter activo','Inter pasivo'];
const TRIBES=['Oso','Nutria','Twink','Daddy','Jock','Cuero','Discreto','Geek','Musculoso','Cachorro','Sissy','FTM','Trans'];
const CATEGORIES=['Citas','Amistad','Algo serio','Ahora','Chat','Viajes','Encuentro Casual','Cumdump','Exhibicionismo'];
const emptyFilters={roles:[],tribes:[],tags:[],preferences:[],fetishes:[],verifiedOnly:false,maxDistanceKm:null};
const initials=(value='')=>value.trim().split(/\s+/).slice(0,2).map(v=>v[0]).join('').toUpperCase()||'R';
const toggle=(list,value)=>list.includes(value)?list.filter(v=>v!==value):[...list,value];
const ageFromBirthDate=(value)=>{if(!value)return'';const d=new Date(`${value}T00:00:00`);if(Number.isNaN(d.getTime()))return'';const n=new Date();let age=n.getFullYear()-d.getFullYear();const m=n.getMonth()-d.getMonth();if(m<0||(m===0&&n.getDate()<d.getDate()))age--;return age};
const formatRemaining=(endsAt)=>{const ms=new Date(endsAt).getTime()-Date.now();if(ms<=0)return'Expirada';const min=Math.ceil(ms/60000);return min<60?`${min} min`:`${Math.floor(min/60)} h ${min%60} min`};

async function signedProfileUrl(path){if(!path)return'';const{data,error}=await getSupabaseClient().storage.from('profile-media').createSignedUrl(path,3600);return error?'':data?.signedUrl||''}

function App(){
  const[session,setSession]=useState(null);const[booting,setBooting]=useState(true);const[screen,setScreen]=useState('home');
  const[profile,setProfile]=useState(null);const[profiles,setProfiles]=useState([]);const[media,setMedia]=useState([]);const[conversations,setConversations]=useState([]);
  const[activeConversation,setActiveConversation]=useState(null);const[messages,setMessages]=useState([]);const[filters,setFilters]=useState(emptyFilters);const[nearby,setNearby]=useState([]);
  const[plan,setPlan]=useState({plan:'free',status:'active',isPlus:false});const[asap,setAsap]=useState(null);const[asapProfiles,setAsapProfiles]=useState([]);const[admin,setAdmin]=useState(false);
  const[blockedIds,setBlockedIds]=useState([]);const[notice,setNotice]=useState('');const[theme,setTheme]=useState(()=>localStorage.getItem('redboy-theme')||'dark');const[installable,setInstallable]=useState(false);
  const stopLocation=useRef(null);
  useEffect(()=>{document.documentElement.dataset.theme=theme;localStorage.setItem('redboy-theme',theme)},[theme]);
  useEffect(()=>{const fn=()=>setInstallable(true);window.addEventListener('redboy:pwa-installable',fn);return()=>window.removeEventListener('redboy:pwa-installable',fn)},[]);
  useEffect(()=>{let off=()=>{};try{off=onSessionChange(next=>{setSession(next);setBooting(false)})}catch(error){setNotice(error.message||'Backend no disponible');setBooting(false)}return()=>{off();stopLocation.current?.()}},[]);
  useEffect(()=>{if(session?.user?.id)void refresh(session.user.id);else{setProfile(null);setProfiles([]);setConversations([]);setBlockedIds([])}},[session?.user?.id,filters]);
  useEffect(()=>{if(!activeConversation?.conversation_id||!session?.user?.id)return;const id=activeConversation.conversation_id;return subscribeToMessages(id,rows=>{setMessages(rows);void markConversationRead(id,session.user.id)},error=>setNotice(error?.message||'Error de Realtime'))},[activeConversation?.conversation_id,session?.user?.id]);

  async function refresh(userId){
    try{
      const[me,discovery,chats,mediaRows,nearbyRows,planRow,asapRow,asapList,blocked,adminFlag]=await Promise.all([
        getMyProfile(userId),listDiscoverableProfiles(userId,filters),listConversations(userId),listProfileMedia(userId),listNearbyApproximateLocations(),getPlan(userId),getMyActiveAsap(userId),listActiveAsapProfiles(),listBlockedUsers(userId),isAdminUser(),
      ]);
      const blockedSet=new Set((blocked||[]).map(x=>x.blocked_id));
      setProfile(me);setProfiles((discovery||[]).filter(x=>!blockedSet.has(x.user_id)));setConversations(chats||[]);setNearby(nearbyRows||[]);setPlan(planRow);setAsap(asapRow);setAsapProfiles(asapList||[]);setBlockedIds([...blockedSet]);setAdmin(adminFlag);
      setMedia(await Promise.all((mediaRows||[]).map(async row=>({...row,url:await signedProfileUrl(row.storage_path)}))));
    }catch(error){setNotice(error.message||'No fue posible cargar Redboy')}
  }
  async function startConversation(otherUserId){try{const conversationId=await createDirectConversation(otherUserId);const rows=await listConversations(session.user.id);setConversations(rows);setActiveConversation(rows.find(r=>r.conversation_id===conversationId)||{conversation_id:conversationId,peers:[]});setScreen('chat')}catch(error){setNotice(error.message)}}
  function enableLocation(){if(stopLocation.current){stopLocation.current();stopLocation.current=null;setNotice('Ubicación pausada');return}try{stopLocation.current=watchApproximateLocation(async loc=>{try{await saveApproximateLocation(session.user.id,loc);setNearby(await listNearbyApproximateLocations())}catch(error){setNotice(error.message)}},()=>setNotice('No se pudo acceder a tu ubicación'));setNotice('Ubicación aproximada activada')}catch(error){setNotice(error.message)}}
  async function doInstall(){const ok=await promptPwaInstall();setInstallable(false);setNotice(ok?'Redboy quedó instalado':'Puedes instalar Redboy desde el menú de tu navegador')}

  if(booting)return<Loading/>;if(!session)return<Auth notice={notice} onNotice={setNotice}/>;
  return <div className="app-shell"><header className="topbar"><button className="avatar" onClick={()=>setScreen('profile')}>{initials(profile?.display_name)}</button><div className="brand-title">Redboy<small>BETA</small></div><button className="icon-button" onClick={()=>setTheme(theme==='dark'?'light':'dark')}>{theme==='dark'?'☀':'☾'}</button></header>
    <main className="main-stage">
      {screen==='home'&&<Discovery profiles={profiles} filters={filters} setFilters={setFilters} onMessage={startConversation}/>} 
      {screen==='map'&&<MapScreen rows={nearby.filter(x=>x.user_id!==session.user.id&&!blockedIds.includes(x.user_id))} onToggleLocation={enableLocation} active={Boolean(stopLocation.current)}/>} 
      {screen==='messages'&&<Messages rows={conversations} onOpen={row=>{setActiveConversation(row);setScreen('chat')}}/>}
      {screen==='chat'&&activeConversation&&<Chat conversation={activeConversation} messages={messages} userId={session.user.id} onBack={()=>setScreen('messages')} onNotice={setNotice} onBlocked={async()=>{await refresh(session.user.id);setScreen('messages')}}/>}
      {screen==='profile'&&<Profile me={profile} media={media} userId={session.user.id} plan={plan} admin={admin} installable={installable&&!isStandalonePwa()} onInstall={doInstall} onNavigate={setScreen} onRefresh={()=>refresh(session.user.id)} onLogout={signOut} onNotice={setNotice}/>} 
      {screen==='asap'&&<Asap me={asap} rows={asapProfiles.filter(x=>x.user_id!==session.user.id&&!blockedIds.includes(x.user_id))} plan={plan} onRefresh={()=>refresh(session.user.id)} onNotice={setNotice} onMessage={startConversation}/>} 
      {screen==='plus'&&<Plus plan={plan} onNotice={setNotice}/>} 
      {screen==='support'&&<Support userId={session.user.id} onNotice={setNotice}/>} 
      {screen==='legal'&&<Legal/>}
      {screen==='admin'&&admin&&<Admin onNotice={setNotice}/>} 
      {screen==='store'&&<Store/>}
    </main>
    {screen!=='chat'&&<nav className="bottom-nav"><button onClick={()=>setScreen('map')} className={screen==='map'?'active':''}><span>⌖</span><b>Mapa</b></button><button onClick={()=>setScreen('messages')} className={screen==='messages'?'active':''}><span>◌</span><b>Mensajes</b></button><button className="center" onClick={()=>setScreen('home')} aria-label="Inicio"><span>⌂</span></button><button onClick={()=>setScreen('asap')} className={screen==='asap'?'active':''}><span>ϟ</span><b>ASAP</b></button><button onClick={()=>setScreen('store')} className={screen==='store'?'active':''}><span>▢</span><b>Tienda</b></button></nav>}
    {notice&&<button className="toast" onClick={()=>setNotice('')}>{notice}</button>}</div>;
}

function Loading(){return<div className="loading"><div className="loader-mark">R</div><p>Cargando Redboy…</p></div>}

function Auth({notice,onNotice}){
  const[mode,setMode]=useState('login');const[busy,setBusy]=useState(false);const[email,setEmail]=useState('');
  async function submit(e){e.preventDefault();const fd=new FormData(e.currentTarget);const mail=String(fd.get('email')||'');const password=String(fd.get('password')||'');setBusy(true);try{if(mode==='login')await signInWithEmail(mail,password);else{const confirm=String(fd.get('confirm')||'');if(password!==confirm)throw new Error('Las contraseñas no coinciden');const result=await signUpWithEmail(mail,password,String(fd.get('name')||'Usuario'));if(!result.session){onNotice('Cuenta creada. Revisa tu correo para confirmar el acceso.');setMode('login')}}}catch(error){onNotice(error.message)}finally{setBusy(false)}}
  async function reset(){if(!email.trim()){onNotice('Escribe tu correo primero');return}const{error}=await getSupabaseClient().auth.resetPasswordForEmail(email.trim(),{redirectTo:`${location.origin}/`});onNotice(error?error.message:'Te enviamos un enlace para restablecer tu contraseña.')}
  return<div className="auth-screen"><div className="auth-brand-space">Redboy<small>BETA PRIVADA</small></div><h1>{mode==='login'?'Conecta a tu manera.':'Crea tu cuenta.'}</h1><p>Comunidad para mayores de 18 años.</p><form className="auth-card" onSubmit={submit}>{mode==='signup'&&<label>Nombre<input name="name" required/></label>}<label>Correo<input name="email" type="email" value={email} onChange={e=>setEmail(e.target.value)} required/></label><label>Contraseña<input name="password" type="password" minLength="8" required/></label>{mode==='signup'&&<><label>Confirmar<input name="confirm" type="password" minLength="8" required/></label><label className="checkbox"><input type="checkbox" required/> Confirmo que tengo 18 años o más.</label></>}<button className="primary" disabled={busy}>{busy?'Procesando…':mode==='login'?'Iniciar sesión':'Crear cuenta'}</button><button type="button" className="text-button" onClick={()=>setMode(mode==='login'?'signup':'login')}>{mode==='login'?'Crear una cuenta':'Ya tengo una cuenta'}</button>{mode==='login'&&<button type="button" className="text-button" onClick={reset}>Olvidé mi contraseña</button>}</form>{notice&&<button className="auth-notice" onClick={()=>onNotice('')}>{notice}</button>}</div>
}

function Discovery({profiles,filters,setFilters,onMessage}){const[show,setShow]=useState(false);const[query,setQuery]=useState('');const visible=useMemo(()=>profiles.filter(p=>(p.display_name||'').toLowerCase().includes(query.toLowerCase())),[profiles,query]);const choose=(key,value)=>setFilters(prev=>({...prev,[key]:toggle(prev[key],value)}));return<section className="content"><div className="search-row"><input value={query} onChange={e=>setQuery(e.target.value)} placeholder="Explora perfiles"/><button onClick={()=>setShow(v=>!v)}>☰</button></div>{show&&<div className="filter-panel"><div className="panel-head"><strong>Filtros</strong><button className="text-button" onClick={()=>setFilters(emptyFilters)}>Limpiar</button></div><FilterGroup title="Rol" values={ROLES} selected={filters.roles} onToggle={v=>choose('roles',v)}/><FilterGroup title="Tribu" values={TRIBES} selected={filters.tribes} onToggle={v=>choose('tribes',v)}/><FilterGroup title="Tags" values={REDBOY_TAGS} selected={filters.tags} onToggle={v=>choose('tags',v)}/><FilterGroup title="Preferencias" values={REDBOY_PREFERENCES} selected={filters.preferences} onToggle={v=>choose('preferences',v)}/><FilterGroup title="Fetiches" values={REDBOY_FETISHES} selected={filters.fetishes} onToggle={v=>choose('fetishes',v)}/><label className="switch-line"><input type="checkbox" checked={filters.verifiedOnly} onChange={e=>setFilters(p=>({...p,verifiedOnly:e.target.checked}))}/> Solo verificados</label><label className="range-line">Distancia máxima <select value={filters.maxDistanceKm??''} onChange={e=>setFilters(p=>({...p,maxDistanceKm:e.target.value?Number(e.target.value):null}))}><option value="">Cualquiera</option><option value="2">2 km</option><option value="5">5 km</option><option value="10">10 km</option><option value="25">25 km</option></select></label></div>}<div className="section-title"><h2>Cerca de ti</h2><span>{visible.length}</span></div>{visible.length?<div className="profile-grid">{visible.map(p=><button className="profile-card" key={p.user_id} onClick={()=>onMessage(p.user_id)}><div className="profile-fallback">{initials(p.display_name)}</div><div className="profile-card-copy"><strong><i className={p.is_online?'online':''}/>{p.display_name||'Usuario'} {(p.verification_status==='verified'||p.phone_verified)?'◆':''}</strong><small>{ageFromBirthDate(p.birth_date)?`${ageFromBirthDate(p.birth_date)} · `:''}{p.role||'Explorando'}{Number.isFinite(p.distanceKm)?` · ${p.distanceKm.toFixed(1)} km`:''}</small></div></button>)}</div>:<Empty title="Sin coincidencias" text="Prueba quitando uno de los filtros."/>}</section>}
function FilterGroup({title,values,selected,onToggle}){return<div className="filter-group"><b>{title}</b><div className="tag-wrap">{values.map(v=><button type="button" key={v} className={`chip ${selected.includes(v)?'active':''}`} onClick={()=>onToggle(v)}>{v}</button>)}</div></div>}

function MapScreen({rows,onToggleLocation,active}){const ref=useRef(null);const mapRef=useRef(null);useEffect(()=>{if(!ref.current)return;const map=new maplibregl.Map({container:ref.current,style:{version:8,sources:{osm:{type:'raster',tiles:['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],tileSize:256,attribution:'© OpenStreetMap'}},layers:[{id:'osm',type:'raster',source:'osm'}]},center:[-100.99,25.43],zoom:11,attributionControl:true});map.addControl(new maplibregl.NavigationControl({showCompass:false}),'top-right');mapRef.current=map;return()=>map.remove()},[]);useEffect(()=>{const map=mapRef.current;if(!map)return;const markers=[];rows.forEach(row=>{if(!Number.isFinite(row.longitude)||!Number.isFinite(row.latitude))return;const el=document.createElement('div');el.className='map-avatar';el.textContent=initials(row.profiles?.display_name);const marker=new maplibregl.Marker({element:el}).setLngLat([row.longitude,row.latitude]).setPopup(new maplibregl.Popup({offset:18}).setText(`${row.profiles?.display_name||'Usuario'} · ubicación aproximada`)).addTo(map);markers.push(marker)});return()=>markers.forEach(m=>m.remove())},[rows]);return<section className="content map-content"><div className="section-title"><h2>Mapa</h2><span>{rows.length}</span></div><div className="map-privacy"><span>Ubicación aproximada · mínimo 500 m</span><button className="secondary" onClick={onToggleLocation}>{active?'Pausar':'Activar'}</button></div><div className="real-map" ref={ref}/></section>}

function Messages({rows,onOpen}){return<section className="content"><div className="section-title"><h2>Mensajes</h2><span>{rows.length}</span></div>{rows.length?<div className="message-list">{rows.map(row=>{const p=row.peers?.[0]?.profiles||{};const latest=row.latest_message;const preview=latest?.body||(latest?.media_type==='audio'?'🎙 Mensaje de voz':latest?.media_type?'📷 Multimedia':'Sin mensajes todavía');return<button className="message-row" key={row.conversation_id} onClick={()=>onOpen(row)}><div className="circle-avatar">{initials(p.display_name)}</div><div><strong>{p.display_name||'Conversación'} {(p.verification_status==='verified'||p.phone_verified)?'◆':''}</strong><small>{preview}</small></div><time>{row.pinned?'●':''}</time></button>})}</div>:<Empty title="Sin conversaciones" text="Abre un perfil para comenzar un chat."/>}</section>}

function Chat({conversation,messages,userId,onBack,onNotice,onBlocked}){const[text,setText]=useState('');const[menu,setMenu]=useState(false);const photoRef=useRef(null);const tempRef=useRef(null);const recorder=useRef(null);const chunks=useRef([]);const[recording,setRecording]=useState(false);const scroller=useRef(null);const peerRow=conversation.peers?.[0];const peer=peerRow?.profiles||{};const peerId=peerRow?.user_id;useEffect(()=>{scroller.current?.scrollTo({top:scroller.current.scrollHeight})},[messages.length]);async function submit(e){e.preventDefault();if(!text.trim())return;const value=text;setText('');try{await sendMessage(conversation.conversation_id,userId,value)}catch(error){setText(value);onNotice(error.message)}}async function upload(file,temp=false){if(!file)return;try{await uploadChatMedia(conversation.conversation_id,userId,file,{temporaryMinutes:temp?60:null})}catch(error){onNotice(error.message)}}async function toggleRecording(){if(recording){recorder.current?.stop();setRecording(false);return}if(!navigator.mediaDevices||!window.MediaRecorder){onNotice('Este navegador no permite grabar audio');return}try{const stream=await navigator.mediaDevices.getUserMedia({audio:true});chunks.current=[];const rec=new MediaRecorder(stream);recorder.current=rec;rec.ondataavailable=e=>{if(e.data.size)chunks.current.push(e.data)};rec.onstop=async()=>{stream.getTracks().forEach(t=>t.stop());const blob=new Blob(chunks.current,{type:rec.mimeType||'audio/webm'});await upload(new File([blob],`voz-${Date.now()}.webm`,{type:blob.type}),false)};rec.start();setRecording(true)}catch{onNotice('No se pudo usar el micrófono')}}async function doBlock(){if(!peerId)return;await blockUser(userId,peerId);onNotice('Usuario bloqueado');await onBlocked()}async function doReport(reason){if(!peerId)return;await reportUser(userId,peerId,reason);onNotice('Reporte enviado para revisión');setMenu(false)}return<section className="chat-screen"><header className="chat-header"><button onClick={onBack}>‹</button><div className="circle-avatar">{initials(peer.display_name)}</div><div><strong>{peer.display_name||'Chat'}</strong><small>{peer.is_online?'En línea':'Conversación privada'}</small></div><button onClick={()=>setMenu(v=>!v)}>•••</button></header>{menu&&<div className="chat-menu"><button onClick={doBlock}>Bloquear usuario</button><strong>Reportar</strong>{REPORT_REASONS.map(r=><button key={r} onClick={()=>doReport(r)}>{blockReasonLabel(r)}</button>)}</div>}<div className="chat-messages" ref={scroller}>{messages.map(m=><MessageBubble key={m.id} message={m} mine={m.sender_id===userId}/>)}</div><form className="composer" onSubmit={submit}><div className="composer-tools"><button type="button" onClick={()=>photoRef.current?.click()}>📷</button><button type="button" onClick={()=>tempRef.current?.click()}>⏳</button><button type="button" className={recording?'recording':''} onClick={toggleRecording}>{recording?'■':'🎙'}</button></div><input value={text} onChange={e=>setText(e.target.value)} placeholder="Mensaje"/><button className="send">➤</button><input hidden ref={photoRef} type="file" accept="image/*,video/*" onChange={e=>{upload(e.target.files?.[0],false);e.target.value=''}}/><input hidden ref={tempRef} type="file" accept="image/*" onChange={e=>{upload(e.target.files?.[0],true);e.target.value=''}}/></form></section>}
function MessageBubble({message,mine}){const[url,setUrl]=useState('');const expired=message.expires_at&&new Date(message.expires_at).getTime()<=Date.now();useEffect(()=>{let live=true;if(message.media_path&&!expired)getChatMediaUrl(message.media_path).then(v=>{if(live)setUrl(v)}).catch(()=>{});return()=>{live=false}},[message.media_path,message.expires_at,expired]);return<div className={`bubble ${mine?'me':'them'}`}>{message.deleted_at?<em>Mensaje eliminado</em>:<>{message.body&&<div>{message.body}</div>}{expired?<div className="expired-media">⏳ Foto temporal expirada</div>:url&&message.media_type==='audio'?<audio controls src={url}/>:url&&message.media_type==='video'?<video controls src={url}/>:url?<img className="chat-media" src={url} alt="Multimedia"/>:message.media_path?<span>Cargando multimedia…</span>:null}{message.expires_at&&!expired&&<small className="temp-label">Temporal · expira automáticamente</small>}</>}<time>{new Date(message.created_at).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'})}</time></div>}

function Asap({me,rows,plan,onRefresh,onNotice,onMessage}){const[minutes,setMinutes]=useState(30);async function activate(){try{await startAsap(minutes);await onRefresh();onNotice('ASAP activado')}catch(error){onNotice(error.message)}}async function deactivate(){try{await stopAsap();await onRefresh();onNotice('ASAP pausado')}catch(error){onNotice(error.message)}}return<section className="content"><div className="section-title"><h2>ASAP</h2><span>{rows.length} activos</span></div>{me?<div className="asap-live"><b>Estás disponible ahora</b><span>{formatRemaining(me.ends_at)}</span><button className="secondary" onClick={deactivate}>Desactivar</button></div>:<div className="panel"><h3>¿Disponible para un encuentro en corto?</h3><p>Free: 30 minutos una vez al día. Plus: sesiones ilimitadas y duración configurable.</p>{plan.isPlus&&<select className="full select-box" value={minutes} onChange={e=>setMinutes(Number(e.target.value))}><option value="30">30 minutos</option><option value="60">1 hora</option><option value="120">2 horas</option><option value="240">4 horas</option></select>}<button className="primary full" onClick={activate}>Activar ASAP {plan.isPlus?'':'30 min'}</button></div>}<div className="profile-grid">{rows.map(p=><button className="profile-card" key={p.user_id} onClick={()=>onMessage(p.user_id)}><div className="profile-fallback">{initials(p.display_name)}</div><div className="profile-card-copy"><strong>{p.display_name}</strong><small>{p.role||'ASAP'} · {formatRemaining(p.ends_at)}</small></div></button>)}</div></section>}

function Plus({plan,onNotice}){async function buy(kind){try{await checkoutRedboyPlus(kind)}catch(error){onNotice(error.message.includes('STRIPE')?'Pagos Plus aún no están configurados en esta beta.':error.message)}}return<section className="content"><div className="plus-hero"><span>REDBOY PLUS</span><h1>Más control. Más privacidad.</h1><p>ASAP ilimitado, modo discreto sin límite, más álbumes y funciones premium.</p></div><div className="plan-grid"><button className="plan-card" onClick={()=>buy('monthly')}><b>Mensual</b><span>Continuar a pago</span></button><button className="plan-card" onClick={()=>buy('annual')}><b>Anual</b><span>Continuar a pago</span></button></div>{plan.isPlus&&<button className="secondary full" onClick={()=>openBillingPortal().catch(e=>onNotice(e.message))}>Administrar suscripción</button>}<p className="fine-print">Los cobros se habilitarán únicamente cuando Stripe esté configurado con precios de producción.</p></section>}

function Profile({me,media,userId,plan,admin,installable,onInstall,onNavigate,onRefresh,onLogout,onNotice}){const[editing,setEditing]=useState(false);const[busy,setBusy]=useState(false);const mediaRef=useRef(null);const verifyRef=useRef(null);if(!me)return<Loading/>;async function save(e){e.preventDefault();const fd=new FormData(e.currentTarget);setBusy(true);try{await updateMyProfile(userId,{display_name:String(fd.get('display_name')||''),birth_date:String(fd.get('birth_date')||'')||null,bio:String(fd.get('bio')||''),role:String(fd.get('role')||'')||null,tribes:fd.getAll('tribes'),categories:fd.getAll('categories'),tags:fd.getAll('tags'),preferences:fd.getAll('preferences'),fetishes:fd.getAll('fetishes')});await onRefresh();setEditing(false)}catch(error){onNotice(error.message)}finally{setBusy(false)}}async function addMedia(file){if(!file||media.length>=5)return;try{await uploadProfileMedia(userId,file,media.length);await onRefresh()}catch(error){onNotice(error.message)}}async function verify(file){if(!file)return;try{await requestProfileVerification(userId,file);await onRefresh();onNotice('Solicitud de verificación enviada')}catch(error){onNotice(error.message)}}async function privacy(patch){try{await updatePrivacySettings(userId,patch);await onRefresh()}catch(error){onNotice(error.message)}}if(editing)return<section className="content"><div className="section-title"><h2>Editar perfil</h2><button className="text-button" onClick={()=>setEditing(false)}>Cancelar</button></div><form className="edit-form" onSubmit={save}><label>Nombre<input name="display_name" defaultValue={me.display_name||''}/></label><label>Fecha de nacimiento<input type="date" name="birth_date" defaultValue={me.birth_date||''}/></label><label>Sobre mí<textarea name="bio" defaultValue={me.bio||''}/></label><label>Rol<select name="role" defaultValue={me.role||''}><option value="">Sin definir</option>{ROLES.map(v=><option key={v}>{v}</option>)}</select></label><CheckSet name="tribes" title="Tribus" values={TRIBES} selected={me.tribes}/><CheckSet name="categories" title="Busco" values={CATEGORIES} selected={me.categories}/><CheckSet name="tags" title="Tags" values={REDBOY_TAGS} selected={me.tags}/><CheckSet name="preferences" title="Preferencias" values={REDBOY_PREFERENCES} selected={me.preferences}/><CheckSet name="fetishes" title="Fetiches" values={REDBOY_FETISHES} selected={me.fetishes}/><button className="primary" disabled={busy}>{busy?'Guardando…':'Guardar perfil'}</button></form></section>;return<section className="content"><div className="profile-hero"><div className="large-avatar">{initials(me.display_name)}</div><div><h1>{me.display_name||'Tu perfil'} {(me.verification_status==='verified'||me.phone_verified)?'◆':''}</h1><p>{ageFromBirthDate(me.birth_date)||''} {me.role?`· ${me.role}`:''} · {plan.isPlus?'Plus':'Free'}</p></div></div><button className="primary full" onClick={()=>setEditing(true)}>Editar perfil</button><div className="panel"><h3>Privacidad</h3><label className="switch-line"><input type="checkbox" checked={Boolean(me.discrete_mode)} onChange={e=>privacy({discrete_mode:e.target.checked})}/> Modo discreto <small>{plan.isPlus?'Plus: sin límite':'Free: 30 min una vez al día'}</small></label><label className="switch-line"><input type="checkbox" checked={me.show_distance!==false} onChange={e=>privacy({show_distance:e.target.checked})}/> Mostrar distancia aproximada</label>{!plan.isPlus&&<button className="text-button" onClick={()=>onNavigate('plus')}>Conocer Redboy Plus</button>}</div><div className="panel"><h3>Verificación</h3><p>Estado: <strong>{me.verification_status==='verified'?'Verificado':me.verification_status==='pending'?'En revisión':'Sin verificar'}</strong></p>{me.verification_status!=='verified'&&me.verification_status!=='pending'&&<><button className="secondary full" onClick={()=>verifyRef.current?.click()}>Solicitar verificación</button><input hidden ref={verifyRef} type="file" accept="image/*" capture="user" onChange={e=>{verify(e.target.files?.[0]);e.target.value=''}}/></>}</div><div className="panel"><div className="panel-head"><h3>Fotos y videos</h3><b>{media.length}/5</b></div><div className="media-grid">{media.map(item=><div className="media-item" key={item.id}>{item.media_type==='video'?<video src={item.url} muted/>:<img src={item.url} alt="Perfil"/>}<button onClick={async()=>{await removeProfileMedia(item.id);await onRefresh()}}>×</button></div>)}{media.length<5&&<button className="media-add" onClick={()=>mediaRef.current?.click()}>＋</button>}</div><input hidden ref={mediaRef} type="file" accept="image/*,video/*" onChange={e=>{addMedia(e.target.files?.[0]);e.target.value=''}}/></div><div className="settings-list">{installable&&<button onClick={onInstall}>Instalar Redboy</button>}<button onClick={()=>onNavigate('plus')}>Redboy Plus</button><button onClick={()=>onNavigate('support')}>Soporte y eliminación de cuenta</button><button onClick={()=>onNavigate('legal')}>Privacidad, términos y reglas</button>{admin&&<button onClick={()=>onNavigate('admin')}>Administración</button>}</div><button className="danger full" onClick={onLogout}>Cerrar sesión</button></section>}

function Support({userId,onNotice}){const[sub,setSub]=useState('');const[msg,setMsg]=useState('');async function send(e){e.preventDefault();try{await sendSupportRequest(userId,sub,msg);setSub('');setMsg('');onNotice('Solicitud enviada a soporte')}catch(error){onNotice(error.message)}}async function deletion(){if(!confirm('¿Solicitar la eliminación de tu cuenta y datos?'))return;try{await requestAccountDeletion(userId);onNotice('Solicitud de eliminación registrada')}catch(error){onNotice(error.message)}}return<section className="content"><div className="section-title"><h2>Soporte</h2></div><form className="edit-form panel" onSubmit={send}><label>Asunto<input value={sub} onChange={e=>setSub(e.target.value)} required/></label><label>Mensaje<textarea value={msg} onChange={e=>setMsg(e.target.value)} required/></label><button className="primary">Enviar</button></form><div className="panel"><h3>Eliminar mi cuenta</h3><p>Registra una solicitud formal para eliminar la cuenta y sus datos asociados. Durante la beta el equipo procesa estas solicitudes desde administración.</p><button className="danger full" onClick={deletion}>Solicitar eliminación</button></div></section>}
function Legal(){return<section className="content"><div className="section-title"><h2>Información legal</h2></div>{legalSections.map(s=><article className="panel legal-card" key={s.id}><h3>{s.title}</h3><p>{s.text}</p></article>)}<p className="fine-print">Texto operativo de beta sujeto a revisión jurídica antes del lanzamiento comercial.</p></section>}

function Admin({onNotice}){const[reports,setReports]=useState([]);const[deletions,setDeletions]=useState([]);const[support,setSupport]=useState([]);async function load(){try{const[a,b,c]=await Promise.all([listOpenReports(),listDeletionRequests(),listSupportRequests()]);setReports(a);setDeletions(b);setSupport(c)}catch(error){onNotice(error.message)}}useEffect(()=>{void load()},[]);async function action(report,status){try{await updateReportStatus(report.id,status);await load()}catch(error){onNotice(error.message)}}async function suspend(report,status){try{await moderateUser(report.reported_id,status,`Reporte ${report.id}: ${report.reason}`);await updateReportStatus(report.id,'resolved');await load();onNotice(status==='active'?'Cuenta reactivada':`Cuenta ${status==='banned'?'bloqueada':'suspendida'}`)}catch(error){onNotice(error.message)}}return<section className="content"><div className="section-title"><h2>Administración</h2><span>{reports.length}</span></div><div className="panel"><h3>Reportes abiertos</h3>{reports.length?reports.map(r=><div className="admin-row" key={r.id}><div><b>{blockReasonLabel(r.reason)}</b><small>{r.reported_id}</small><p>{r.details||'Sin detalles'}</p></div><div className="admin-actions"><button onClick={()=>action(r,'reviewing')}>Revisar</button><button onClick={()=>suspend(r,'suspended')}>Suspender</button><button onClick={()=>suspend(r,'banned')}>Bloquear</button><button onClick={()=>action(r,'dismissed')}>Descartar</button></div></div>):<p>Sin reportes pendientes.</p>}</div><div className="panel"><h3>Eliminación de cuenta</h3>{deletions.length?deletions.map(x=><div className="admin-row" key={x.user_id}><b>{x.user_id}</b><small>{new Date(x.requested_at).toLocaleString()}</small></div>):<p>Sin solicitudes.</p>}</div><div className="panel"><h3>Soporte</h3>{support.length?support.map(x=><div className="admin-row" key={x.id}><b>{x.subject}</b><small>{x.user_id}</small><p>{x.message}</p></div>):<p>Sin solicitudes.</p>}</div></section>}

function Store(){const[message,setMessage]=useState('Próximamente');useEffect(()=>{getSupabaseClient().from('store_settings').select('public_message,catalog_visible,sales_enabled').limit(1).maybeSingle().then(({data})=>{if(data?.public_message)setMessage(data.public_message)})},[]);return<section className="content store-coming"><div><span>REDBOY STORE</span><h1>{message}</h1><p>El catálogo ya está cargado internamente, pero la tienda y las ventas permanecen desactivadas durante esta fase de la beta.</p></div></section>}
function CheckSet({name,title,values,selected=[]}){return<fieldset><legend>{title}</legend><div className="multi">{values.map(v=><label key={v}><input type="checkbox" name={name} value={v} defaultChecked={(selected||[]).includes(v)}/>{v}</label>)}</div></fieldset>}
function Empty({title,text}){return<div className="empty"><strong>{title}</strong><p>{text}</p></div>}

createRoot(document.getElementById('root')).render(<React.StrictMode><App/></React.StrictMode>);
