/* Bernard Health SeniorCare V2.0.4 — núcleo único de datos y tiempo real */
(function(){
  'use strict';

  const VERSION='4.3.75';
  const STAFF_TABLE='seniorcare_staff_records';
  const ASSIGN_TABLE='seniorcare_assistant_assignments';
  const ATTENDANCE_TABLE='attendance_records';
  let channel=null;
  let startedKey='';
  let staffBusy=false;
  let attendanceBusy=false;
  let serverStaffLoaded=false;

  const db=()=>window.seniorcareSupabase||null;
  const user=()=>window.currentUser||null;
  const org=()=>user()?.organizationId||user()?.organization_id||null;
  const role=()=>String(user()?.role||'');
  const isAdmin=()=>['admin','administrator','supervisor'].includes(role());
  const isAssistant=()=>role()==='assistant';
  const today=()=>(window.BHSC_LOCAL_DATE?window.BHSC_LOCAL_DATE():new Intl.DateTimeFormat('en-CA',{timeZone:'America/Costa_Rica'}).format(new Date()));
  const normalize=value=>String(value||'').normalize('NFD').replace(/[\u0300-\u036f]/g,'').toLowerCase().replace(/\s+/g,' ').trim();
  const toast=message=>window.showToast?.(message);
  const safeArray=value=>Array.isArray(value)?value:[];
  const staffList=()=>typeof staffMembers!=='undefined'&&Array.isArray(staffMembers)?staffMembers:(window.staffMembers||[]);
  const residentList=()=>typeof residents!=='undefined'&&Array.isArray(residents)?residents:(window.residents||[]);
  const assignmentsMap=()=>{
    if(typeof residentAssignments!=='undefined'&&residentAssignments)return residentAssignments;
    if(!window.residentAssignments)window.residentAssignments={};
    return window.residentAssignments;
  };
  const attendanceList=()=>{
    if(typeof attendanceLog!=='undefined'&&Array.isArray(attendanceLog))return attendanceLog;
    if(!Array.isArray(window.attendanceLog))window.attendanceLog=[];
    return window.attendanceLog;
  };
  const persistStaff=()=>localStorage.setItem('seniorcare-staff',JSON.stringify(staffList()));
  const persistAssignments=()=>localStorage.setItem('seniorcare-resident-assignments',JSON.stringify(assignmentsMap()));
  const persistAttendance=()=>localStorage.setItem('seniorcare-attendance-log',JSON.stringify(attendanceList()));

  // Resolución única de asignaciones para laptop y celular.
  // Acepta tanto la clave local de la ficha como la clave estable user:<UUID>.
  window.getAssignedResidentsForStaff=function(member){
    const map=assignmentsMap();
    const keys=[];
    const add=value=>safeArray(value).forEach(v=>{const k=String(v);if(!keys.includes(k))keys.push(k);});
    const linkedId=member?.linkedUserId||member?.linked_user_id||(isAssistant()?user()?.id:null);
    if(member?.id!==undefined&&member?.id!==null)add(map[String(member.id)]);
    if(linkedId)add(map[`user:${linkedId}`]);
    return residentList().filter(r=>{
      const stable=String(window.residentKey?.(r)||r?.resident_key||r?.id||'');
      return keys.includes(String(r.id))||keys.includes(stable);
    });
  };

  const memberRole=m=>normalize(m?.role||m?.position||'');
  const isAssistantMember=m=>memberRole(m).includes('asistente')||memberRole(m)==='assistant';

  function preserveViewRefresh(kind){
    const scrollY=window.scrollY;
    requestAnimationFrame(()=>{
      try{
        if(kind==='staff'&&window.activeScreen==='staff'&&typeof window.renderStaff==='function'){
          window.renderStaff(); window.bindActions?.();
        }else if(kind==='assistant'&&isAssistant()&&['home','residents'].includes(window.activeScreen||'')){
          if(typeof window.render==='function')window.render();
          else if(window.activeScreen==='home'&&typeof window.renderHome==='function'){window.renderHome();window.bindActions?.();}
          else if(typeof window.renderResidents==='function'){window.renderResidents();window.bindActions?.();}
        }
      }catch(err){console.warn('Actualización visual no disruptiva:',err);}
      requestAnimationFrame(()=>window.scrollTo(0,scrollY));
    });
  }

  async function loadProfiles(){
    const client=db(); if(!client||!org())return [];
    const {data,error}=await client.from('profiles').select('id,full_name,role,active,organization_id').eq('organization_id',org());
    if(error)throw error;
    return data||[];
  }

  async function reconcileStaffLinks(){
    const client=db(); if(!client||!org())return false;
    const profiles=await loadProfiles();
    let changed=false;
    for(const member of staffList()){
      if(member.linkedUserId||member.linked_user_id)continue;
      const matches=profiles.filter(p=>p.active!==false&&normalize(p.full_name)===normalize(member.name)&&(
        (String(p.role)==='assistant'&&isAssistantMember(member))||
        (String(p.role)==='nurse'&&memberRole(member).includes('enfermer'))
      ));
      if(matches.length===1){member.linkedUserId=matches[0].id;member.linked_user_id=matches[0].id;member.linkedEmail=matches[0].email||member.linkedEmail||'';changed=true;}
    }
    if(changed){persistStaff();await pushStaff();}
    return changed;
  }

  async function linkCurrentOperationalUser(){
    if(!['assistant','nurse','cook','cleaning','maintenance','security','doctor'].includes(role())||!user()?.id||!org())return;
    const client=db();
    const {data,error}=await client.from(STAFF_TABLE).select('id,staff_key,data').eq('organization_id',org()).eq('active',true);
    if(error)throw error;
    const candidates=(data||[]).filter(row=>normalize(row.data?.name)===normalize(user()?.name));
    if(candidates.length!==1)return;
    const row=candidates[0];
    if(String(row.data?.linkedUserId||row.data?.linked_user_id||'')===String(user().id))return;
    const next={...(row.data||{}),linkedUserId:user().id,linked_user_id:user().id,linkedEmail:user().email||row.data?.linkedEmail||'',linkedAt:new Date().toISOString()};
    const {error:updateError}=await client.from(STAFF_TABLE).update({data:next,updated_at:new Date().toISOString(),updated_by:user().id}).eq('id',row.id);
    if(updateError)throw updateError;
  }

  async function pullStaff(){
    const client=db(); if(!client||!org()||!isAdmin())return false;
    const {data,error}=await client.from(STAFF_TABLE).select('staff_key,data,active,updated_at').eq('organization_id',org()).eq('active',true).order('updated_at',{ascending:true});
    if(error)throw error;
    const target=staffList();
    const next=(data||[]).map(row=>({...row.data,id:row.data?.id??row.staff_key}));
    // Supabase es la fuente oficial. La lista local se reemplaza completa,
    // incluso cuando el servidor devuelve cero registros.
    target.splice(0,target.length,...next);
    persistStaff();
    serverStaffLoaded=true;
    return true;
  }

  async function pushStaff(){
    const client=db(); if(!client||!org()||!isAdmin()||staffBusy||!serverStaffLoaded)return;
    staffBusy=true;
    try{
      const current=staffList();
      const keys=new Set(current.map(member=>String(member.id)));
      const {data:remote,error:readError}=await client.from(STAFF_TABLE).select('id,staff_key,active').eq('organization_id',org());
      if(readError)throw readError;

      const payload=current.map(member=>({organization_id:org(),staff_key:String(member.id),active:true,data:member,updated_at:new Date().toISOString(),updated_by:user()?.id||null}));
      if(payload.length){
        const {error}=await client.from(STAFF_TABLE).upsert(payload,{onConflict:'organization_id,staff_key'});
        if(error)throw error;
      }

      // Una eliminación hecha en Administración se refleja como active=false.
      // Así una ficha borrada no puede reaparecer desde localStorage.
      const stale=(remote||[]).filter(row=>row.active!==false&&!keys.has(String(row.staff_key))).map(row=>row.id);
      if(stale.length){
        const {error}=await client.from(STAFF_TABLE).update({active:false,updated_at:new Date().toISOString(),updated_by:user()?.id||null}).in('id',stale);
        if(error)throw error;
      }
    }finally{staffBusy=false;}
  }


  async function deleteStaffMember(memberOrId){
    const client=db();
    if(!client||!org())throw new Error('No hay conexión con la organización.');
    if(!isAdmin())throw new Error('Este usuario no tiene permiso para eliminar personal.');
    const member=typeof memberOrId==='object'&&memberOrId?memberOrId:staffList().find(x=>String(x.id)===String(memberOrId));
    if(!member)throw new Error('No se encontró el colaborador.');
    const key=String(member.id||member.staff_key||'').trim();
    if(!key)throw new Error('No se pudo identificar al colaborador.');
    const linked=String(member.linkedUserId||member.linked_user_id||'').trim();
    const stamp=new Date().toISOString();
    let removedByRpc=false;
    if(typeof window.BHSC_CALL_RPC==='function'){
      const rpc=await window.BHSC_CALL_RPC(['bh_delete_staff_member_v4368','bh_delete_staff_member_v4321','bh_delete_staff_member_v361'],{p_staff_key:key,p_linked_user:linked||null});
      if(!rpc?.error)removedByRpc=true;
      else if(!window.BHSC_RPC_FUNCTION_MISSING?.(rpc.error))console.warn('[BHSC delete staff RPC]',rpc.error);
    }
    if(!removedByRpc){
      const remote=await client.from(STAFF_TABLE).update({active:false,updated_at:stamp,updated_by:user()?.id||null}).eq('organization_id',org()).eq('staff_key',key).select('staff_key').maybeSingle();
      if(remote.error)throw remote.error;
      if(!remote.data)throw new Error('No se pudo confirmar la eliminación del colaborador en la nube.');
      if(linked){
        let accessDisabled=false;
        if(typeof window.BHSC_CALL_RPC==='function'){
          const access=await window.BHSC_CALL_RPC(['bh_delete_profile_access_v4321','bh_delete_profile_access_v3'],{p_target:linked});
          accessDisabled=!access?.error;
          if(access?.error&&!window.BHSC_RPC_FUNCTION_MISSING?.(access.error))console.warn('[BHSC disable staff access RPC]',access.error);
        }
        if(!accessDisabled){
          const profile=await client.from('profiles').update({active:false}).eq('id',linked).eq('organization_id',org());
          if(profile.error)console.warn('[BHSC disable staff profile]',profile.error);
        }
      }
    }
    if(linked){
      try{await client.from(ASSIGN_TABLE).delete().eq('organization_id',org()).eq('assistant_user_id',linked);}catch(error){console.warn('[BHSC clear assignments]',error);}
    }
    const target=staffList();
    target.splice(0,target.length,...target.filter(x=>String(x.id)!==key));
    persistStaff();
    try{
      const map=assignmentsMap();
      delete map[key];
      if(linked)delete map[`user:${linked}`];
      persistAssignments();
    }catch(_){ }
    return true;
  }

  window.deleteStaffMemberFromCloud=deleteStaffMember;

  function setAssignmentCache(assistantId,keys){
    const map=assignmentsMap();
    const ids=[...new Set(safeArray(keys).map(String))];
    map[`user:${assistantId}`]=ids.map(v=>/^\d+$/.test(v)?Number(v):v);
    const member=staffList().find(m=>String(m.linkedUserId||m.linked_user_id||'')===String(assistantId));
    if(member)map[String(member.id)]=map[`user:${assistantId}`].slice();
    persistAssignments();
  }

  async function pullMyAssignments(){
    const client=db(); if(!client||!isAssistant())return [];
    const {data,error}=window.BHSC_CALL_RPC?await window.BHSC_CALL_RPC(['bh_get_my_assigned_residents_v4321','bh_get_my_assigned_residents'],{}):await client.rpc('bh_get_my_assigned_residents');
    if(error)throw error;
    const keys=(data||[]).map(row=>String(row.resident_key));
    setAssignmentCache(user().id,keys);
    return keys;
  }

  async function pullAllAssignments(){
    const client=db(); if(!client||!isAdmin())return [];
    const {data,error}=window.BHSC_CALL_RPC?await window.BHSC_CALL_RPC(['bh_list_org_assistant_assignments_v4321','bh_list_org_assistant_assignments'],{}):await client.rpc('bh_list_org_assistant_assignments');
    if(error)throw error;
    const grouped={};
    for(const row of data||[]){(grouped[row.assistant_user_id]??=[]).push(String(row.resident_key));}
    for(const member of staffList().filter(isAssistantMember)){
      {const linked=member.linkedUserId||member.linked_user_id;if(linked)setAssignmentCache(linked,grouped[linked]||[]);}
    }
    return data||[];
  }

  async function saveAssignments(button){
    const member=staffList().find(m=>String(m.id)===String(button.dataset.saveResidentAssignment));
    if(!member)return;
    const linkedAssistantId=String(member.linkedUserId||member.linked_user_id||'').trim();
    if(!linkedAssistantId){toast('Este asistente todavía no ha creado su usuario. Genere o reenvíe el código y pídale registrarse; al registrarse quedará activo automáticamente.');return;}
    const box=document.querySelector(`[data-assignment-box="${CSS.escape(String(member.id))}"]`);
    if(!box)return;
    const keys=[...box.querySelectorAll('input[type="checkbox"]:checked')].map(input=>String(input.value));
    const old=button.textContent; button.disabled=true; button.textContent='Guardando...';
    try{
      const {data,error}=window.BHSC_CALL_RPC?await window.BHSC_CALL_RPC(['bh_save_assistant_assignments_v4321','bh_save_assistant_assignments'],{p_assistant_user_id:linkedAssistantId,p_resident_keys:keys}):await db().rpc('bh_save_assistant_assignments',{p_assistant_user_id:linkedAssistantId,p_resident_keys:keys});
      if(error)throw error;
      const confirmed=(data||[]).map(row=>String(row.resident_key)).sort();
      const expected=[...new Set(keys)].sort();
      if(JSON.stringify(confirmed)!==JSON.stringify(expected))throw new Error('Supabase no confirmó todas las asignaciones.');
      setAssignmentCache(linkedAssistantId,confirmed);
      button.textContent='Guardado'; toast('Asignación guardada y enviada al celular.');
      setTimeout(()=>{if(button.isConnected){button.disabled=false;button.textContent=old;}},900);
    }catch(err){button.disabled=false;button.textContent=old;toast(`No se guardó la asignación: ${err.message||err}`);}
  }

  function localAttendanceForToday(){
    const key=`user:${user()?.id}`;
    return [...attendanceList()].reverse().find(log=>log.date===today()&&(String(log.staffId)===key||String(log.userId||'')===String(user()?.id)))||null;
  }

  function renderAssistantClock(log,busy=''){
    if(!isAssistant())return;
    const card=document.querySelector('.assistant-clock-v141,.assistant-attendance-v141,.self-clock-card.final-clock');
    if(!card)return;
    const small=card.querySelector('small'); const actions=card.querySelector('.self-clock-actions');
    const fmt=v=>v?new Date(v).toLocaleTimeString('es-CR',{hour:'2-digit',minute:'2-digit'}):'No registrada';
    if(small)small.textContent=log?.clockIn?`Entrada ${fmt(log.clockIn)}${log.clockOut?` · Salida ${fmt(log.clockOut)}`:''}`:'Entrada no registrada';
    if(!actions)return;
    if(busy)actions.innerHTML=`<button type="button" class="clock-btn" disabled>${busy}</button>`;
    else if(!log?.clockIn)actions.innerHTML='<button type="button" class="clock-btn clock-in" id="assistantClockIn">Marcar entrada</button>';
    else if(!log.clockOut)actions.innerHTML='<button type="button" class="clock-btn clock-out" id="assistantClockOut">Marcar salida</button>';
    else actions.innerHTML='<span class="status ok">Jornada cerrada</span>';
  }

  async function pullAttendance(){
    const client=db(); if(!client||!org())return [];
    let query=client.from(ATTENDANCE_TABLE).select('*').eq('organization_id',org()).order('work_date',{ascending:false}).limit(1000);
    if(!isAdmin())query=query.eq('user_id',user().id);
    const {data,error}=await query; if(error)throw error;
    const list=attendanceList();
    for(const row of data||[]){
      const incoming={id:row.client_id||Date.parse(row.updated_at)||Date.now(),remoteId:row.id,staffId:row.staff_key,userId:row.user_id,name:row.staff_name,role:row.role_name,date:row.work_date,clockIn:row.clock_in,clockOut:row.clock_out};
      const idx=list.findIndex(x=>String(x.staffId)===String(incoming.staffId)&&x.date===incoming.date);
      if(idx<0)list.push(incoming); else list[idx]={...list[idx],...incoming};
    }
    persistAttendance();
    if(isAssistant())renderAssistantClock(localAttendanceForToday());
    return data||[];
  }

  async function getServerAttendanceToday(){
    const client=db();
    if(!client||!org()||!user()?.id)return [];
    const {data,error}=await client.from(ATTENDANCE_TABLE)
      .select('*')
      .eq('organization_id',org())
      .eq('user_id',user().id)
      .eq('work_date',today())
      .order('updated_at',{ascending:false});
    if(error)throw error;
    return data||[];
  }

  function replaceLocalUserDayFromRows(rows){
    const list=attendanceList();
    for(let i=list.length-1;i>=0;i--){
      const item=list[i];
      if(item?.date===today()&&String(item?.userId||'')===String(user()?.id))list.splice(i,1);
    }
    const best=[...rows].sort((a,b)=>{
      const ao=a.clock_out?1:0, bo=b.clock_out?1:0;
      if(ao!==bo)return bo-ao;
      return String(b.updated_at||'').localeCompare(String(a.updated_at||''));
    })[0];
    if(best){
      list.push({id:best.client_id||Date.parse(best.updated_at)||Date.now(),remoteId:best.id,staffId:best.staff_key,userId:best.user_id,name:best.staff_name,role:best.role_name,date:best.work_date,clockIn:best.clock_in,clockOut:best.clock_out});
    }
    persistAttendance();
    return localAttendanceForToday();
  }

  async function markAttendance(action){
    if(!isAssistant()||attendanceBusy)return;
    attendanceBusy=true;
    const prior=localAttendanceForToday()?{...localAttendanceForToday()}:null;
    try{
      const rows=await getServerAttendanceToday();
      const open=rows.find(r=>r.clock_in&&!r.clock_out)||null;
      const closed=rows.find(r=>r.clock_in&&r.clock_out)||null;
      if(action==='in'){
        if(open||closed){
          const current=replaceLocalUserDayFromRows(rows);
          renderAssistantClock(current);
          toast(closed?'La jornada de hoy ya está cerrada.':'La entrada de hoy ya está registrada.');
          return;
        }
        const now=new Date().toISOString();
        renderAssistantClock({clockIn:now,clockOut:null},'Guardando entrada...');
        const payload={organization_id:org(),user_id:user().id,staff_key:`user:${user().id}`,staff_name:user().name||'Asistente',role_name:'Asistente',work_date:today(),clock_in:now,clock_out:null,client_id:Date.now(),updated_at:now};
        const {error}=await db().from(ATTENDANCE_TABLE).insert(payload);if(error)throw error;
      }else{
        if(closed&&!open){
          const current=replaceLocalUserDayFromRows(rows);
          renderAssistantClock(current);
          toast('La jornada de hoy ya está cerrada.');
          return;
        }
        if(!open)throw new Error('No existe una entrada abierta para registrar la salida.');
        const now=new Date().toISOString();
        renderAssistantClock({clockIn:open.clock_in,clockOut:now},'Guardando salida...');
        const {error}=await db().from(ATTENDANCE_TABLE).update({clock_out:now,updated_at:now}).eq('id',open.id).eq('user_id',user().id);if(error)throw error;
      }
      const confirmedRows=await getServerAttendanceToday();
      const confirmed=replaceLocalUserDayFromRows(confirmedRows);
      if(action==='out'&&!confirmed?.clockOut)throw new Error('Supabase no confirmó la salida.');
      if(action==='in'&&!confirmed?.clockIn)throw new Error('Supabase no confirmó la entrada.');
      renderAssistantClock(confirmed);
      toast(action==='in'?'Entrada registrada.':'Salida registrada.');
    }catch(err){
      const list=attendanceList();
      for(let i=list.length-1;i>=0;i--){if(list[i]?.date===today()&&String(list[i]?.userId||'')===String(user()?.id))list.splice(i,1);}
      if(prior)list.push(prior);
      persistAttendance();renderAssistantClock(prior);toast(`No se guardó la marca: ${err.message||err}`);
    }finally{attendanceBusy=false;}
  }

  async function subscribe(){
    const client=db(); if(!client||!org()||!user()?.id)return;
    if(channel){try{await client.removeChannel(channel);}catch(_e){}channel=null;}
    channel=client.channel(`seniorcare-v200-${org()}-${user().id}`);
    channel.on('postgres_changes',{event:'*',schema:'public',table:STAFF_TABLE,filter:`organization_id=eq.${org()}`},async()=>{if(isAdmin()){await pullStaff();await reconcileStaffLinks();preserveViewRefresh('staff');}});
    channel.on('postgres_changes',{event:'*',schema:'public',table:ASSIGN_TABLE,filter:`organization_id=eq.${org()}`},async()=>{if(isAssistant()){await pullMyAssignments();preserveViewRefresh('assistant');}else if(isAdmin()){await pullAllAssignments();preserveViewRefresh('staff');}});
    channel.on('postgres_changes',{event:'*',schema:'public',table:ATTENDANCE_TABLE,filter:`organization_id=eq.${org()}`},async()=>{await pullAttendance();if(isAdmin())preserveViewRefresh('staff');});
    channel.subscribe(status=>{window.SENIORCARE_REALTIME_STATUS=status;});
  }

  async function start(){
    if(!db()||!user()?.id||!org())return;
    const key=`${org()}:${user().id}`; if(startedKey===key)return; startedKey=key;
    try{
      await linkCurrentOperationalUser();
      if(isAdmin()){await pullStaff();await reconcileStaffLinks();await pullAllAssignments();}
      if(isAssistant()){
        await pullMyAssignments();
        preserveViewRefresh('assistant');
      }
      try{
        await pullAttendance();
      }catch(attendanceError){
        const message=String(attendanceError?.message||attendanceError||'');
        const schemaPending=/attendance_records/i.test(message)&&/schema cache|could not find the table/i.test(message);
        if(!schemaPending)throw attendanceError;
        await new Promise(resolve=>setTimeout(resolve,1500));
        await pullAttendance();
      }
      await subscribe();
      window.SENIORCARE_DATA_CORE_VERSION=VERSION;
    }catch(err){console.error('Inicio núcleo SeniorCare V2.0.4:',err);toast(`Sincronización pendiente: ${err.message||err}`);}
  }

  document.addEventListener('click',event=>{
    const save=event.target.closest?.('[data-save-resident-assignment]');
    if(save&&isAdmin()){event.preventDefault();event.stopImmediatePropagation();saveAssignments(save);return;}
    const clock=event.target.closest?.('#assistantClockIn,#assistantClockOut,#selfClockIn,#selfClockOut');
    if(clock&&isAssistant()){event.preventDefault();event.stopImmediatePropagation();markAttendance(/Out$/.test(clock.id)?'out':'in');}
  },true);

  const originalSaveStaff=window.saveStaff||(typeof saveStaff==='function'?saveStaff:null);
  window.saveStaff=function(){
    if(typeof originalSaveStaff==='function')originalSaveStaff.apply(this,arguments); else persistStaff();
    pushStaff().catch(err=>{console.error('Guardado de personal:',err);toast(`No se sincronizó personal: ${err.message||err}`);});
  };
  try{saveStaff=window.saveStaff;}catch(_e){}

  window.addEventListener('seniorcare:user-ready',()=>start());
  window.addEventListener('seniorcare:manual-sync',async()=>{startedKey='';await start();});
  window.addEventListener('focus',()=>{if(user()?.id){if(isAssistant())pullMyAssignments().then(()=>preserveViewRefresh('assistant')).catch(()=>{});pullAttendance().catch(()=>{});}});

  // Nunca se redibuja o navega durante TOKEN_REFRESHED. Solo SIGNED_OUT lo maneja app.js.
  if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',()=>setTimeout(start,300),{once:true});
  else setTimeout(start,300);
})();
