'use strict';

// SiteSight V1.99.6 — final integration compatibility layer.
// Load after app.js, mobile-v1995.js, mobile-v1996.js and any field workflow
// compatibility file so these guards remain the outermost wrappers.
(function installV1996Integration(){
  const rootWindow=window;

  function integrationUserHas(user,permission){
    return !!user&&(user.role==='Director'||user.permissions?.[permission]===true)
  }
  function integrationUserCanAccessSite(user,siteId){
    if(!user||!siteId)return false;
    return user.role==='Director'||user.allSitesAccess===true||(user.allowedSites||[]).includes(siteId)
  }
  function integrationFinancialNotification(notification,target,user=currentUser()){
    if(!notification||!user||!['payment','variation'].includes(notification.kind))return false;
    if(notification.targetUserId&&notification.targetUserId!==user.id)return false;
    if(!integrationUserHas(user,'view_financials'))return false;
    let exact=target?.targetType==='payment'?(typeof v1996cEntryByPaymentId==='function'?v1996cEntryByPaymentId(target.targetId):v176ActualPayment(target.targetId)):null,itemId=exact?.item?.id||(target?.targetType==='work'?target.targetId:(notification.itemId||target?.targetParentId||'')),item=exact?.item||(db.items||[]).find(row=>row.id===itemId),siteId=item?.site||notification.siteId||'';
    return integrationUserCanAccessSite(user,siteId)
  }

  // Financial notifications are authorised by financial permission and Site,
  // rather than incorrectly requiring Accounts users to have Work Items.
  if(typeof v1996UserCanOpenNotification==='function'){
    const baseNotificationAccess=v1996UserCanOpenNotification;
    rootWindow.v1996UserCanOpenNotification=function(notification,target,user=currentUser()){
      if(integrationFinancialNotification(notification,target,user))return true;
      return baseNotificationAccess(notification,target,user)
    }
  }

  function integrationMarkNotificationRead(notification,user=currentUser()){
    if(!notification||!user)return;
    notification.readBy=notification.readBy||[];
    if(!notification.readBy.includes(user.id)){notification.readBy.push(user.id);save()}
  }
  function integrationPaymentRowsForItem(itemId){
    let rows=typeof v1996cEntries==='function'?v1996cEntries().filter(row=>row.item?.id===itemId).map(row=>({...row,integrationOpenable:typeof v1996cIsPaid==='function'&&v1996cIsPaid(row.payment)})):typeof v195AllPayments==='function'?v195AllPayments().filter(row=>row.item?.id===itemId).map(row=>({...row,integrationOpenable:true})):[];
    let item=(db.items||[]).find(row=>row.id===itemId);
    if(item&&typeof v1996cEntries!=='function')for(const variation of item.variations||[])for(const payment of variation.payments||[])if(!payment.amended)rows.push({item,target:variation,payment,stage:variation.name||'Variation',integrationOpenable:false});
    return rows.sort((left,right)=>String(right.payment.enteredAt||right.payment.paidAt||right.payment.date||'').localeCompare(String(left.payment.enteredAt||left.payment.paidAt||left.payment.date||'')))
  }
  function integrationOpenFinancialNotification(notification,target){
    let exact=target?.targetType==='payment'?(typeof v1996cEntryByPaymentId==='function'?v1996cEntryByPaymentId(target.targetId):v176ActualPayment(target.targetId)):null,itemId=exact?.item?.id||(target?.targetType==='work'?target.targetId:(notification.itemId||target?.targetParentId||'')),item=exact?.item||(db.items||[]).find(row=>row.id===itemId);
    integrationMarkNotificationRead(notification);
    settingsOpen=false;modal=null;
    if(exact&&typeof v1996OpenPaymentResult==='function')return v1996OpenPaymentResult(exact.payment.id);
    if(notification.kind==='variation'&&item){
      let text=String(notification.text||'').toLowerCase(),variation=(item.variations||[]).find(row=>row.name&&text.includes(String(row.name).toLowerCase()))||(item.variations||[]).slice().sort((a,b)=>String(b.updatedAt||b.createdAt||'').localeCompare(String(a.updatedAt||a.createdAt||'')))[0];
      if(variation)return v1996OpenVariationResult(item.id,variation.id)
    }
    let payment=integrationPaymentRowsForItem(itemId)[0];
    if(payment?.integrationOpenable&&typeof v1996OpenPaymentResult==='function')return v1996OpenPaymentResult(payment.payment.id);
    if(payment&&!payment.integrationOpenable&&payment.variation?.id&&notification.kind==='variation')return v1996OpenVariationResult(itemId,payment.variation.id);
    if(item){site=item.site;paymentSearch=item.ref||'';paymentFrom='';paymentTo=''}
    v192CommercialTab=currentUser()?.role==='Accounts'?'reconcile':v1996IsPhone()&&['Director','Contracts Manager'].includes(currentUser()?.role)?'mobile-approvals':'pay-run';view='commercial';selected=null;selectedGang=null;render()
  }
  if(typeof v199OpenNotification==='function'){
    const baseOpenNotification=v199OpenNotification;
    rootWindow.v199OpenNotification=function(id){
      let notification=(db.notifications||[]).find(row=>row.id===id),target=notification&&v1996NotificationTarget(notification);
      if(notification&&integrationFinancialNotification(notification,target))return integrationOpenFinancialNotification(notification,target);
      return baseOpenNotification(id)
    }
  }

  // Accounts can open the financial portion of a variation without being
  // granted operational Work Item access.
  if(typeof v1996OpenVariationResult==='function'){
    const baseOpenVariationResult=v1996OpenVariationResult;
    rootWindow.v1996OpenVariationResult=function(itemId,variationId){
      let item=(db.items||[]).find(row=>row.id===itemId),variation=item?.variations?.find(row=>row.id===variationId),user=currentUser();
      if(!item||!variation||!integrationUserHas(user,'view_financials')||!integrationUserCanAccessSite(user,item.site))return toast('This variation is unavailable');
      if(canViewItem(item))return baseOpenVariationResult(itemId,variationId);
      modal=`<div class="modal"><div class="modalbox v1996-readonly-sheet" role="dialog" aria-modal="true" aria-label="Variation details"><span class="att-kicker">FINANCIAL VARIATION</span><h2>${v1996Esc(item.ref)} · ${v1996Esc(variation.name||'Variation')}</h2><div class="v1996-review-grid"><span><small>Site</small><b>${v1996Esc(siteName(item.site))}</b></span><span><small>Stage</small><b>${v1996Esc(variation.stage||'Not specified')}</b></span><span><small>Status</small><b>${v1996Esc(variation.approvalStatus||'Pending Approval')}</b></span><span><small>Value</small><b>${attendanceMoney(variation.value||0)}</b></span><span><small>Instruction</small><b>${v1996Esc(variation.instructionReference||'Not entered')}</b></span><span><small>Recorded by</small><b>${v1996Esc(variation.createdBy||'Not recorded')}</b></span></div>${variation.notes?`<div class="field"><label>Notes</label><p>${v1996Esc(variation.notes)}</p></div>`:''}<button type="button" class="btn light" onclick="modal=null;render()">Close</button></div></div>`;
      render()
    }
  }

  // Global callable detail functions must enforce the feature permission as
  // well as Site scope. Their existing V1.99.6 wrappers retain Site checks.
  if(typeof openTeamMember==='function'){
    const baseOpenTeamMember=openTeamMember;
    rootWindow.openTeamMember=function(teamId,memberId){
      if(!integrationUserHas(currentUser(),'gangs'))return toast('People access is not included in your permissions');
      return baseOpenTeamMember(teamId,memberId)
    }
  }
  if(typeof openGang==='function'){
    const baseOpenGang=openGang;
    rootWindow.openGang=function(id){
      if(!integrationUserHas(currentUser(),'gangs'))return toast('People access is not included in your permissions');
      return baseOpenGang(id)
    }
  }
  if(typeof viewIssue==='function'){
    const baseViewIssue=viewIssue;
    rootWindow.viewIssue=function(itemId,issueId){
      if(!integrationUserHas(currentUser(),'view_issues'))return toast('Issue access is not included in your permissions');
      return baseViewIssue(itemId,issueId)
    }
  }

  // One success boundary clears both the legacy dirty flag and the V1.99.6
  // modal draft. This prevents a successful save causing a false leave prompt.
  function integrationCommitDraft(){
    if(typeof v1996CommitDraft==='function')v1996CommitDraft();
    else if(typeof clearDirty==='function')clearDirty()
  }
  function integrationReleaseBusy(button,commit=false){
    if(!button)return;if(commit)integrationCommitDraft();
    button.dataset.v1996IntegrationBusy='';button.removeAttribute('aria-busy');button.classList.remove('v1996-submit-busy');
    if(button.dataset.v1996WasDisabled!=='1')button.disabled=false;
    if(typeof v1996DraftState!=='undefined'&&v1996DraftState.saving)v1996DraftState.saving=false
  }
  if(typeof savedState==='function'){
    const baseSavedState=savedState;
    rootWindow.savedState=function(...args){
      integrationCommitDraft();document.querySelectorAll('[data-v1996-integration-busy="1"]').forEach(button=>integrationReleaseBusy(button));
      return baseSavedState.apply(this,args)
    }
  }
  function integrationExitControl(target,editor){
    if(!editor)return false;
    if(target===target.closest('.modal'))return /modal\s*=\s*null|close/i.test(target.getAttribute('onclick')||'');
    let button=target.closest('button');if(!button)return false;
    let label=`${button.textContent||''} ${button.getAttribute('aria-label')||''}`.trim();
    if(/^back\b/i.test(label)&&/ReturnToPaymentEdit|addVariationPayment|OpenRunReview/i.test(button.getAttribute('onclick')||''))return false;
    return button.matches('[data-dialog-close],.v1991-modal-close')||/^(?:×|✕|cancel|close|done|back)\b/i.test(label)
  }
  function integrationSubmitControl(target,editor){
    let button=target.closest('button');if(!editor||!button)return null;
    let label=String(button.textContent||'').trim();
    if(/cancel|close|back|retry|open|review next/i.test(label))return null;
    return /\b(save|upload|add issue|issue new revision|record|submit|confirm|approve|lock|create)\b/i.test(label)?button:null
  }
  document.addEventListener('click',event=>{
    let modalRoot=event.target.closest?.('.modal'),editor=modalRoot?.querySelector?.('.field-mobile-editor')||event.target.closest?.('.field-mobile-editor');
    if(integrationExitControl(event.target,editor)){
      if(typeof v1996ConfirmDraftExit==='function'&&!v1996ConfirmDraftExit()){event.preventDefault();event.stopImmediatePropagation();return}
      integrationCommitDraft();return
    }
    let button=integrationSubmitControl(event.target,editor);if(!button)return;
    if(button.dataset.v1996IntegrationBusy==='1'){event.preventDefault();event.stopImmediatePropagation();return}
    button.dataset.v1996IntegrationBusy='1';button.dataset.v1996WasDisabled=button.disabled?'1':'0';button.setAttribute('aria-busy','true');button.classList.add('v1996-submit-busy');
    let knownToasts=new Set(document.querySelectorAll('.toast')),started=Date.now();
    queueMicrotask(()=>{if(button.isConnected&&button.dataset.v1996IntegrationBusy==='1'){button.disabled=true;if(typeof v1996DraftState!=='undefined')v1996DraftState.saving=true}});
    let watcher=setInterval(()=>{
      if(!button.isConnected||button.dataset.v1996IntegrationBusy!=='1'){clearInterval(watcher);return}
      let fresh=[...document.querySelectorAll('.toast')].find(row=>!knownToasts.has(row));
      if(fresh){clearInterval(watcher);let success=/saved|uploaded|recorded|approved|paid|created|updated/i.test(fresh.textContent||'');integrationReleaseBusy(button,success);return}
      if(Date.now()-started>20000){clearInterval(watcher);integrationReleaseBusy(button)}
    },100)
  },true);

  // Refresh the phone sync controls whenever the established sync state is
  // updated; no extra render or navigation is required.
  function integrationRefreshSyncControls(){
    if(typeof v1996SyncUi!=='function')return;let state=v1996SyncUi();
    document.querySelectorAll('.v1996-sync-chip').forEach(button=>{button.className=`v1996-sync-chip ${state.state}`;button.setAttribute('aria-label',`${state.label}. ${state.detail}`);button.innerHTML=`<i aria-hidden="true"></i><span><b>${v1996Esc(state.label)}</b><small>${v1996Esc(state.detail)}</small></span>`;button.onclick=()=>state.state==='synced'||state.state==='local'?v1995OpenMobileAccount():v1996RetrySync()});
    document.querySelectorAll('.v1996-account-sync').forEach(button=>{button.className=`v1996-account-sync ${state.state}`;button.innerHTML=`<span><b>${v1996Esc(state.label)}</b><small>${v1996Esc(state.detail)}</small></span>${['offline','error','saving'].includes(state.state)?'<strong>Retry</strong>':''}`;button.onclick=()=>v1996RetrySync()})
  }
  if(typeof updateSyncUI==='function'){
    const baseUpdateSyncUI=updateSyncUI;
    rootWindow.updateSyncUI=function(...args){let result=baseUpdateSyncUI.apply(this,args);integrationRefreshSyncControls();return result}
  }

  // Back restores the recorded list position. Setting both route keys before
  // render prevents the earlier V1.99.5/V1.99.6 scroll-to-top wrappers from
  // overwriting the saved scroll position.
  if(typeof v1994GoBack==='function')rootWindow.v1994GoBack=function(){
    if(typeof v1994History==='undefined'||!v1994History.length)return;
    if(typeof v1996ConfirmDraftExit==='function'&&!v1996ConfirmDraftExit())return;
    let state=v1994History.pop(),scrollTop=Number(state.scrollY||0);v1994Restoring=true;modal=null;settingsOpen=false;
    try{
      v1994RestoreState(state);
      if(typeof v1995LastPageKey!=='undefined')v1995LastPageKey=`${view}|${selected||''}|${selectedGang||''}`;
      if(typeof v1996LastRouteKey!=='undefined')v1996LastRouteKey=JSON.stringify([currentUserId,view,site,selected||'',selectedGang||'',typeof attendanceTab==='undefined'?'':attendanceTab,typeof contractorAttendanceTab==='undefined'?'':contractorAttendanceTab,typeof v192CommercialTab==='undefined'?'':v192CommercialTab,typeof v191DocumentFolder==='undefined'?'':v191DocumentFolder]);
      render()
    }finally{v1994Restoring=false}
    requestAnimationFrame(()=>{window.scrollTo(0,scrollTop);document.querySelector('.main')?.scrollTo?.({top:scrollTop,left:0,behavior:'instant'})})
  };

  // A Pay Run card is shown only when every Site in that run is inside the
  // current permitted commercial scope.
  if(typeof v1996MobileApprovals==='function')rootWindow.v1996MobileApprovals=function(){
    let data=v1996ManagementMoneyData(),runs=(db.payRuns||[]).filter(run=>{let ids=typeof v1993RunSiteIds==='function'?v1993RunSiteIds(run):(run.siteIds||[]);return !run.superseded&&ids.length&&ids.every(id=>data.siteIds.includes(id)&&canAccessSite(id))&&run.status!=='Paid'}).sort((a,b)=>String(b.updatedAt||b.createdAt).localeCompare(String(a.updatedAt||a.createdAt)));
    return `<section class="panel v1996-approval-list"><div class="sectionhead"><div><h2>Approvals</h2><p class="muted">One clear next action for each record.</p></div></div>${data.pendingVariations.map(({item,variation})=>`<article><span><b>${v1996Esc(item.ref)} · ${v1996Esc(variation.name)}</b><small>${v1996Esc(siteName(item.site))} · ${attendanceMoney(variation.value||0)} · Pending Approval</small></span>${hasPerm('approve_variations')?`<button class="btn orange" onclick="selected='${item.id}';approveVariation('${variation.id}')">Review</button>`:''}</article>`).join('')}${runs.slice(0,8).map(run=>{let ids=typeof v1993RunSiteIds==='function'?v1993RunSiteIds(run):(run.siteIds||[]),action=run.status==='Draft'?'Review draft':run.status==='Approved'?'Review and lock':['Locked','Exported'].includes(run.status)?'Review payment':'Open';return `<article><span><b>${v1996Esc(v174RangeLabel(run.from,run.to))}</b><small>${v1996Esc(ids.map(siteName).join(' · '))} · ${v1996Esc(run.status)} · ${attendanceMoney(run.snapshot?.totals?.netDue||0)}</small></span><button class="btn ${run.status==='Draft'?'orange':'green'}" onclick="v1993OpenRun('${run.id}');${run.status==='Draft'?'v190ApproveRun()':run.status==='Approved'?'v190LockRun()':['Locked','Exported'].includes(run.status)?'v190MarkPaid()':''}">${action}</button></article>`}).join('')||smartEmpty('No approvals waiting.','This Site scope is clear.','','','✓')}</section>`
  };

  // The launch target was captured by mobile-v1996.js. Remove it from the
  // visible URL so refresh does not open the same notification repeatedly.
  try{
    let launchUrl=new URL(location.href),changed=false;
    if(launchUrl.searchParams.has('notification')){launchUrl.searchParams.delete('notification');changed=true}
    if(launchUrl.searchParams.get('view')==='attendance'){launchUrl.searchParams.delete('view');changed=true}
    if(changed)window.history.replaceState(window.history.state,'',`${launchUrl.pathname}${launchUrl.search}${launchUrl.hash}`)
  }catch(error){}

  // V1.99.8: one Operator role, Director-controlled Accounts time access,
  // and a deliberately small set of fictional demonstration actions.
  const V1998_DEMO_REFINEMENT='v1998-demo-refinement-1';
  const V1998_GUIDE_VERSION='v1998-permission-guide-1';
  const V1998_ACCOUNTS_TIME_KEYS=['view_attendance','manage_attendance','approve_timesheets','manage_working_hours','approve_overtime'];
  const V1998_PLOT_ROLES=['Director','Contracts Manager','Site Manager','Supervisor'];
  const V1998_PLOT_PERMISSIONS=['work_items','update_progress','manage_workitems','sign_off','edit_lifts','view_issues','raise_issues','manage_issues','access_all_site_work'];
  function v1998RoleCanAccessPlots(roleName){return V1998_PLOT_ROLES.includes(roleName)}
  function v1998ApplyPlotAccessPolicy(roleName,permissions={}){
    let next={...(permissions||{})};
    if(!v1998RoleCanAccessPlots(roleName))for(const key of V1998_PLOT_PERMISSIONS)next[key]=false;
    return next
  }
  if(typeof V175_TIME_MANAGER_ROLES!=='undefined'&&!V175_TIME_MANAGER_ROLES.includes('Accounts'))V175_TIME_MANAGER_ROLES.push('Accounts');
  if(typeof V173_ROLE_PROFILES!=='undefined'){
    delete V173_ROLE_PROFILES.Subcontractor;
    if(V173_ROLE_PROFILES.Accounts&&Array.isArray(V173_ROLE_PROFILES.Accounts.permissions))V173_ROLE_PROFILES.Accounts.permissions=V173_ROLE_PROFILES.Accounts.permissions.filter(key=>!V1998_ACCOUNTS_TIME_KEYS.includes(key))
    for(const [roleName,profile] of Object.entries(V173_ROLE_PROFILES)){
      if(v1998RoleCanAccessPlots(roleName)||!Array.isArray(profile.permissions))continue;
      profile.permissions=profile.permissions.filter(key=>!V1998_PLOT_PERMISSIONS.includes(key))
    }
  }
  if(typeof V1992_ROLE_NAV!=='undefined'){
    delete V1992_ROLE_NAV.Subcontractor;
    if(Array.isArray(V1992_ROLE_NAV.Accounts))V1992_ROLE_NAV.Accounts=V1992_ROLE_NAV.Accounts.filter(id=>id!=='attendance')
    for(const [roleName,ids] of Object.entries(V1992_ROLE_NAV))if(!v1998RoleCanAccessPlots(roleName)&&Array.isArray(ids))V1992_ROLE_NAV[roleName]=ids.filter(id=>!['work-items','my-work','issues'].includes(id))
  }
  if(typeof V1991_TEST_ROUTES!=='undefined')delete V1991_TEST_ROUTES.Subcontractor;

  function v1998NormaliseUsers(){
    let changed=false;
    for(const user of db.users||[]){
      if(user.role==='Subcontractor'){user.role='Operator';user.isSubcontractor=true;changed=true}
      let flag=user.role==='Operator'&&user.isSubcontractor===true;if(user.isSubcontractor!==flag){user.isSubcontractor=flag;changed=true}
      if(user.id==='u-subcontractor'){
        if(user.username!=='operator.lewis'){user.username='operator.lewis';changed=true}
        if(user.email!=='operator.lewis@demo.sitesight.local'){user.email='operator.lewis@demo.sitesight.local';changed=true}
      }
      if(user.role==='Accounts'){
        user.permissions=user.permissions||{};let delegated=user.timeApprovalDelegatedByDirector===true;
        if(!delegated)for(const key of V1998_ACCOUNTS_TIME_KEYS)if(user.permissions[key]){user.permissions[key]=false;changed=true}
      }else if(user.timeApprovalDelegatedByDirector){user.timeApprovalDelegatedByDirector=false;changed=true}
      let policyPermissions=v1998ApplyPlotAccessPolicy(user.role,user.permissions||{});
      for(const key of V1998_PLOT_PERMISSIONS)if(user.permissions?.[key]!==policyPermissions[key]){user.permissions=user.permissions||{};user.permissions[key]=policyPermissions[key];changed=true}
      if(!v1998RoleCanAccessPlots(user.role)&&Array.isArray(user.recentWorkItems)&&user.recentWorkItems.length){user.recentWorkItems=[];changed=true}
      if(db.demoProfile&&user.onboardingGuideVersion!==V1998_GUIDE_VERSION){
        user.onboardingSeen=false;user.onboardingCompletedAt='';user.onboardingGuideVersion=V1998_GUIDE_VERSION;changed=true
      }
    }
    return changed
  }
  function v1998RefineDemoQueues(){
    let changed=v1998NormaliseUsers();
    if(!db.demoProfile||db.demoRefinementVersion===V1998_DEMO_REFINEMENT)return changed;
    let keepOvertime=new Set(['shift-demo-site-25-m-r1-2026-08-18','shift-demo-site-96-m-r4-2026-08-18']),reapprove=new Set(['approval-demo-site-25-m-r1-2026-08-17','approval-demo-site-96-m-r4-2026-08-17']);
    for(const shift of db.attendanceShifts||[]){let request=shift.overtimeApproval;if(!request?.manualRequest||request.status!=='Pending'||keepOvertime.has(shift.id))continue;Object.assign(request,{status:'Rejected',reason:'Historic demonstration request already reviewed',approvedBy:'SiteSight Demo',approvedById:'u-director',approvedAt:'2026-08-24T17:05:00.000Z',decidedBy:'SiteSight Demo',decidedAt:'2026-08-24T17:05:00.000Z'});changed=true}
    for(const approval of db.timesheetApprovals||[]){let wanted=approval.superseded!==true&&reapprove.has(approval.id);if(approval.needsReapproval!==wanted){approval.needsReapproval=wanted;changed=true}}
    db.demoRefinementVersion=V1998_DEMO_REFINEMENT;return true
  }
  function v1998LimitActionRows(rows){let counts={},limited=[];for(const row of rows||[]){let kind=row?.kind||'other';if((counts[kind]||0)>=2)continue;counts[kind]=(counts[kind]||0)+1;limited.push(row);if(limited.length===8)break}return limited}

  const v1998BaseDefaultPerms=defaultPerms;
  rootWindow.defaultPerms=function(roleName){let permissions=v1998ApplyPlotAccessPolicy(roleName,v1998BaseDefaultPerms(roleName));if(roleName==='Accounts')for(const key of V1998_ACCOUNTS_TIME_KEYS)permissions[key]=false;return permissions};
  if(typeof v175TimeManagerRole==='function'){
    const v1998BaseTimeManagerRole=v175TimeManagerRole;
    rootWindow.v175TimeManagerRole=function(user=currentUser()){return user?.role==='Accounts'?user.timeApprovalDelegatedByDirector===true:v1998BaseTimeManagerRole(user)}
  }
  function v1998AccountsMayApprove(user=currentUser()){return user?.role!=='Accounts'||user.timeApprovalDelegatedByDirector===true}
  if(typeof attendanceApproveWorkerWeek==='function'){
    const v1998BaseApproveWorkerWeek=attendanceApproveWorkerWeek;
    rootWindow.attendanceApproveWorkerWeek=function(...args){if(!v1998AccountsMayApprove())return toast('A Director must grant this Accounts user shift approval permission');return v1998BaseApproveWorkerWeek.apply(this,args)}
  }
  if(typeof attendanceApproveAllWeek==='function'){
    const v1998BaseApproveAllWeek=attendanceApproveAllWeek;
    rootWindow.attendanceApproveAllWeek=function(...args){if(!v1998AccountsMayApprove())return toast('A Director must grant this Accounts user shift approval permission');return v1998BaseApproveAllWeek.apply(this,args)}
  }
  const v1998BaseMigrate=migrate;
  rootWindow.migrate=function(persist=true){let baseChanged=!!v1998BaseMigrate(false),refined=v1998RefineDemoQueues();if((baseChanged||refined)&&persist)save();return baseChanged||refined};
  if(typeof v199DashboardExceptionRows==='function'){
    const v1998BaseDashboardExceptionRows=v199DashboardExceptionRows;
    rootWindow.v199DashboardExceptionRows=function(){return v1998LimitActionRows(v1998BaseDashboardExceptionRows())};
    rootWindow.v175ExceptionRows=rootWindow.v199DashboardExceptionRows
  }
  rootWindow.v1998RefineDemoQueues=v1998RefineDemoQueues;
  rootWindow.v1998LimitActionRows=v1998LimitActionRows;
  rootWindow.V1998_PLOT_ROLES=V1998_PLOT_ROLES;
  rootWindow.V1998_PLOT_PERMISSIONS=V1998_PLOT_PERMISSIONS;
  rootWindow.v1998RoleCanAccessPlots=v1998RoleCanAccessPlots;
  rootWindow.v1998ApplyPlotAccessPolicy=v1998ApplyPlotAccessPolicy;
  if((db.users||[]).length)v1998RefineDemoQueues();

  // V1.99.8: the old Demo testing checklist is retired. One permission-led
  // New User Guide now describes every screen that the signed-in user can open.
  if(typeof V173_MORE_TOOLS==='object'){
    delete V173_MORE_TOOLS['test-route'];
    V173_MORE_TOOLS.guide=['?','New User Guide','A guided tour generated from this user’s current permissions']
  }
  function v1998Unique(values){return [...new Set((values||[]).filter(Boolean))]}
  const v1998BaseMoreToolIds=typeof v1992MoreToolIds==='function'?v1992MoreToolIds:v173MoreToolIds;
  rootWindow.v1992MoreToolIds=function(user=currentUser()){
    let ids=v1998BaseMoreToolIds(user).map(id=>id==='test-route'?'guide':id);
    if(user&&!ids.includes('guide'))ids.push('guide');
    return v1998Unique(ids).filter(id=>id!=='test-route'&&v173CanOpenView(id,user))
  };
  rootWindow.v173MoreToolIds=rootWindow.v1992MoreToolIds;

  function v1998GuideScreenIds(user=currentUser()){
    if(!user)return[];
    let primary=v173RoleNavItems(user).map(row=>row.id),secondary=v1992MoreToolIds(user).filter(id=>!['guide','test-route'].includes(id)&&!primary.includes(id));
    let ids=[...primary,...(secondary.length?['more']:[]),...secondary];
    return v1998Unique(ids).filter(id=>id==='more'||v173CanOpenView(id,user))
  }
  function v1998GuideMeta(id,user=currentUser()){
    let primary=v173RoleNavItems(user).find(row=>row.id===id),nav=typeof V1992_NAV_META==='object'?V1992_NAV_META[id]:null,more=typeof V173_MORE_TOOLS==='object'?V173_MORE_TOOLS[id]:null;
    let raw=primary||nav||null,label=raw?(typeof raw.label==='function'?raw.label(user):raw.label):more?.[1],icon=raw?.icon||more?.[0]||'□';
    const fallback={more:['•••','More'],admin:['⚙','Admin'],analytics:['↗','Analytics'],audit:['≡','Audit Trail'],'data-quality':['✓','Missing Information'],functionality:['⚙','Site Settings'],'weekly-report':['▤','Site Report'],dayworks:['＋','Daywork jobs'],gangs:['◎','People'],about:['©','About SiteSight']};
    return {icon:icon||fallback[id]?.[0]||'□',label:label||fallback[id]?.[1]||String(id).replace(/-/g,' ')}
  }
  function v1998GuideSummary(id,user=currentUser()){
    const summaries={
      more:'More keeps occasional reports, records, administration and help tools together without crowding your daily navigation.',
      admin:'Admin controls Sites, people, users, individual permissions and operational setup where your access allows it.',
      analytics:'Analytics turns recorded SiteSight activity into management summaries without changing the underlying records.',
      audit:'Audit Trail shows who changed a record, what changed and when it happened.',
      'data-quality':'Missing Information points directly to incomplete setup or records that need a decision.',
      functionality:'Site Settings contains working hours, clocking, lift templates and the rules used by new records.',
      'weekly-report':'Site Report brings progress, people, issues, programme and Daywork evidence into one review.',
      dayworks:'Daywork jobs records instructed extra work with the instruction, labour and evidence kept together.',
      gangs:'People contains individuals, teams, linked accounts and the records your permissions allow you to see.',
      about:'About SiteSight explains the product, ownership and important usage information.',
      'my-work':'My Work shows permitted plot cards with current stage, issues and the next action in one place.'
    };
    if(summaries[id])return summaries[id];
    if(typeof v1992ScreenExplanationFor==='function')return v1992ScreenExplanationFor(id);
    return `This screen contains the ${v1998GuideMeta(id,user).label} tools available to your account.`
  }
  rootWindow.v1998GuideScreenIds=v1998GuideScreenIds;
  rootWindow.guideSteps=function(){
    let user=currentUser();
    return v1998GuideScreenIds(user).map(id=>{let meta=v1998GuideMeta(id,user),summary=v1998GuideSummary(id,user);return {id,icon:meta.icon,title:meta.label,target:id,summary,bullets:[summary,id==='more'?'Choose a clearly named group, then open only the tool you need.':`Open ${meta.label} from the main navigation or More.`,id==='audit'||id==='analytics'?'This screen is for checking information; it does not change operational records.':'Any change still follows your individual permissions and shows a saved confirmation.']}})
  };

  const v1998BaseMarkGuideSeen=markGuideSeen;
  rootWindow.markGuideSeen=function(){
    let user=currentUser();if(!user)return;
    user.onboardingSeen=true;user.onboardingCompletedAt=user.onboardingCompletedAt||new Date().toISOString();user.onboardingGuideVersion=V1998_GUIDE_VERSION;save()
  };
  const v1998BaseCloseUserGuide=closeUserGuide;
  rootWindow.closeUserGuide=function(){if(currentUser()?.onboardingSeen!==true)markGuideSeen();return v1998BaseCloseUserGuide()};
  rootWindow.newUserGuidePage=function(){
    let user=currentUser(),steps=guideSteps(),first=guideFirstRun&&user?.onboardingSeen!==true,siteCount=user?.role==='Director'||user?.allSitesAccess?(db.sites||[]).filter(row=>!row.archived).length:(user?.allowedSites||[]).length;
    return `<div class="topbar v1998-guide-topbar"><div><span class="att-kicker">${first?'WELCOME TO SITESIGHT':'HELP & ONBOARDING'}</span><h1>New User Guide</h1><div class="sub">Generated from your current role, individual permissions and permitted Sites.</div></div><button class="btn light" onclick="closeUserGuide()">${first?'Skip for now':'Close'}</button></div><section class="panel v1998-guide-overview"><div><span class="v1998-guide-role">${assistEsc(user?.role||'SiteSight user')}</span><h2>${first?`Welcome, ${assistEsc(String(user?.name||'').split(' ')[0]||'there')}`:'Your SiteSight tour'}</h2><p>This walkthrough opens every screen available to your account, explains what it does and shows the safest way to use it. Screens you cannot access are never included.</p><div class="v1998-guide-facts"><span><b>${steps.length}</b> accessible screens</span><span><b>${siteCount||0}</b> permitted Site${siteCount===1?'':'s'}</span><span><b>Manual confirmation</b> for Taskbot changes</span></div></div><button class="btn orange" onclick="startGuidedTour()">Start New User Guide →</button></section><section class="panel v1998-guide-list"><div class="sectionhead"><div><h2>Your accessible screens</h2><p class="muted">This list updates automatically whenever a Director changes your permissions.</p></div></div><div class="v1998-guide-screen-grid">${steps.map((step,index)=>`<button onclick="guideStep=${index};guideActive=true;guideFirstRun=false;view='${step.target}';render()"><span aria-hidden="true">${step.icon}</span><span><b>${assistEsc(step.title)}</b><small>${assistEsc(step.summary)}</small></span><strong>Open ›</strong></button>`).join('')}</div></section>`
  };
  function v1998ShouldAutoOpenGuide(user,state={}){return !!user&&user.accountActive!==false&&user.onboardingSeen!==true&&state.view!=='guide'&&state.guideActive!==true}
  rootWindow.v1998ShouldAutoOpenGuide=v1998ShouldAutoOpenGuide;

  const v1998BaseGoNav=goNav;
  rootWindow.goNav=function(target){return v1998BaseGoNav(target==='test-route'?'guide':target)};
  rootWindow.morePage=function(){
    let ids=v1992MoreToolIds(),groups=[['Reports',['weekly-report','analytics','data-quality']],['Documents and records',['site-drawings','documents','calendar','diary','dayworks','audit']],['Settings and administration',['gangs','admin','functionality']],['Help',['guide','about']]];
    return `<div class="topbar"><div><h1>More</h1><div class="sub">Occasional tools, grouped by purpose</div></div></div><div class="v1992-more-groups">${groups.map(([title,tools])=>{let visible=tools.filter(id=>ids.includes(id));if(!visible.length)return'';return `<section class="panel"><h2>${title}</h2><div>${visible.map(v1992MoreCard).join('')}</div></section>`}).join('')}<section class="panel v1992-more-help"><h2>Need help?</h2><p>Open the permission-based guide or ask Taskbot by typing or speaking.</p><div class="actions"><button class="btn orange" onclick="openUserGuide(false)">New User Guide</button><button class="btn light" onclick="assistOpenPanel()">Open Taskbot</button></div></section></div>`
  };

  // V1.99.8 Taskbot: typed and spoken requests use the same permission check.
  // A record-changing plan is previewed, revalidated and consumed once only
  // after the user presses the confirmation button.
  let v1998TaskbotReview=null,v1998TaskbotExecuting=false,v1998TaskbotUserId='';
  function v1998ClearTaskbotSession(clearMessages=true){
    if(typeof v176AssistRecognition!=='undefined'&&v176AssistRecognition){try{v176AssistRecognition.abort()}catch(error){}v176AssistRecognition=null}
    if(typeof v176AssistListening!=='undefined')v176AssistListening=false;
    v1998TaskbotReview=null;assistPending=null;if(typeof v184AssistPlan!=='undefined')v184AssistPlan=null;assistBusy=false;assistOpen=false;
    if(clearMessages)assistMessages=[];
    document.getElementById('sitesight-assist-panel')?.remove();document.getElementById('sitesight-assist-scrim')?.remove()
  }
  function v1998TaskbotNonce(){let values=new Uint32Array(3);try{crypto.getRandomValues(values)}catch(error){values=[Date.now(),Math.random()*1e9,Math.random()*1e9]};return Array.from(values,value=>Math.floor(value).toString(36)).join('-')}
  function v1998MemberVisible(member){return typeof v1996CanViewMember!=='function'||v1996CanViewMember(member)}
  rootWindow.assistFindItem=function(text){
    let value=String(text||''),match=value.match(/(?:plot|work\s*item)\s*#?0*(\d{1,4})/i),rows=(db.items||[]).filter(item=>canViewItem(item));if(!match)return null;
    let number=Number(match[1]);rows=rows.filter(item=>Number(String(item.ref||'').match(/\d+/)?.[0])===number);
    let namedSite=(db.sites||[]).find(row=>value.toLowerCase().includes(String(row.name||'').toLowerCase()));if(namedSite)rows=rows.filter(item=>item.site===namedSite.id);else if(site!=='all')rows=rows.filter(item=>item.site===site);
    return rows.length===1?rows[0]:null
  };
  rootWindow.assistFindMember=function(text){
    let value=String(text||'').toLowerCase(),rows=(typeof attendanceAllMembers==='function'?attendanceAllMembers():[]).filter(row=>v1998MemberVisible(row.member));
    let exact=rows.filter(row=>value.includes(String(row.member?.name||'').toLowerCase()));if(exact.length===1)return exact[0];
    let words=value.split(/\s+/).filter(word=>word.length>=3),fuzzy=rows.filter(row=>words.some(word=>String(row.member?.name||'').toLowerCase().split(/\s+/).includes(word)));return fuzzy.length===1?fuzzy[0]:null
  };
  function v1998ValidateTaskbotPlan(input,user=currentUser()){
    let plan=JSON.parse(JSON.stringify(input||{}));if(!user||user.accountActive===false)return {ok:false,message:'Sign in with an active SiteSight account before using Taskbot.'};
    let validDate=value=>/^\d{4}-\d{2}-\d{2}$/.test(String(value||'')),siteAllowed=id=>!!id&&canAccessSite(id),item,memberRow;
    if(plan.type==='issue'){
      item=(db.items||[]).find(row=>row.id===plan.itemId);if(!hasPerm('raise_issues'))return {ok:false,message:'Your permissions do not allow you to raise Issues.'};if(!item||!canViewItem(item))return {ok:false,message:'That plot is not available to your account. Include the Site name if the same plot number exists on more than one Site.'};
      plan.title=String(plan.title||'').trim().slice(0,180);plan.priority=['Normal','High','Urgent'].includes(plan.priority)?plan.priority:'Normal';if(!plan.title)return {ok:false,message:'Tell Taskbot what the Issue is before continuing.'};if(plan.targetDate&&!validDate(plan.targetDate))return {ok:false,message:'The Issue target date is not valid.'};return {ok:true,plan,siteId:item.site,target:item.ref}
    }
    if(plan.type==='progress'){
      item=(db.items||[]).find(row=>row.id===plan.itemId);if(!item||!canViewItem(item)||!(typeof canUpdateItem==='function'?canUpdateItem(item):(hasPerm('update_progress')||hasPerm('manage_workitems'))))return {ok:false,message:'Your permissions do not allow that plot progress update.'};
      if(!['Not started','Started','Complete'].includes(plan.status))return {ok:false,message:'Choose Not started, Started or Complete.'};if(item.type==='House Plot'&&(!Number.isInteger(Number(plan.stageIndex))||!item.lifts?.[Number(plan.stageIndex)]))return {ok:false,message:'Choose the exact lift or stage to update.'};plan.stageIndex=Number(plan.stageIndex);return {ok:true,plan,siteId:item.site,target:item.ref}
    }
    if(plan.type==='calendar'){
      if(!hasPerm('manage_calendar'))return {ok:false,message:'Your permissions do not allow Calendar changes.'};if(!siteAllowed(plan.siteId))return {ok:false,message:'That Site is outside your permitted access.'};plan.title=String(plan.title||'').trim().slice(0,180);if(!plan.title||!validDate(plan.date))return {ok:false,message:'Include a Calendar title and a valid date.'};return {ok:true,plan,siteId:plan.siteId,target:siteName(plan.siteId)}
    }
    if(plan.type==='absence'||plan.type==='leave'){
      memberRow=typeof contractorMember==='function'?contractorMember(plan.workerId):null;if(!memberRow||!v1998MemberVisible(memberRow.member))return {ok:false,message:'That person is not available to your account.'};if(!siteAllowed(plan.siteId))return {ok:false,message:'That Site is outside your permitted access.'};
      if(plan.type==='absence'&&!hasPerm('manage_attendance')&&!canDirector())return {ok:false,message:'Attendance management permission is required.'};
      if(plan.type==='leave'&&!(typeof contractorCanViewLeave==='function'&&contractorCanViewLeave()))return {ok:false,message:'Leave requests are not included in your permissions.'};
      if(plan.type==='absence'&&!validDate(plan.date))return {ok:false,message:'Include a valid absence date.'};if(plan.type==='leave'&&(!validDate(plan.from)||!validDate(plan.to)))return {ok:false,message:'Include a valid leave date or range.'};return {ok:true,plan,siteId:plan.siteId,target:memberRow.member.name}
    }
    return {ok:false,message:'Taskbot can explain this request and take you to the correct screen, but it cannot safely apply that change.'}
  }
  rootWindow.v1998ValidateTaskbotPlan=v1998ValidateTaskbotPlan;
  const v1998BaseAssistIntent=assistIntent;
  rootWindow.assistIntent=function(text){
    let result=v1998BaseAssistIntent(text);if(!result)return result;
    let pending=(result.actions||[]).find(action=>action.kind==='pending');if(pending){let checked=v1998ValidateTaskbotPlan(pending.plan);if(!checked.ok)return {intent:'permission_denied',reply:checked.message,actions:[]};pending.plan=checked.plan}
    result.actions=(result.actions||[]).filter(action=>{if(action.kind==='navigate-item'){let item=(db.items||[]).find(row=>row.id===action.target);return !!item&&canViewItem(item)}if(['navigate','highlight'].includes(action.kind))return v173CanOpenView(action.target);return true});return result
  };
  rootWindow.assistSubmit=async function(){
    let input=document.getElementById('assist-input'),textValue=(input?.value||'').trim();if(!textValue||assistBusy)return;if(input)input.value='';assistUser(textValue);assistBusy=true;assistRenderPanel();let result=assistIntent(textValue);
    if(result){assistLastIntent=result.intent;return assistSay(result.reply,result.actions||[])}
    let help=roleCanAccessPlots()?'I can help with plots, Issues, progress, leave, attendance, Daywork, Drawings, Calendar, Commercial, people and Admin. You can also say “Take me to …”.':'I can help with the screens and actions available to your role. Plot cards and progress require Supervisor access or above. You can also say “Take me to …”.';
    return assistSay(help,[{label:'Open New User Guide',kind:'navigate',target:'guide',primary:true}])
  };
  rootWindow.assistRenderPanel=function(){
    if(!assistOpen)return;document.getElementById('sitesight-assist-panel')?.remove();let scrim=document.getElementById('sitesight-assist-scrim');if(!scrim){scrim=document.createElement('div');scrim.id='sitesight-assist-scrim';scrim.className='assist-scrim';scrim.onclick=assistClose;document.body.appendChild(scrim)}
    let listening=typeof v176AssistListening!=='undefined'&&v176AssistListening,panel=document.createElement('section');panel.id='sitesight-assist-panel';panel.className='assist-panel';panel.setAttribute('aria-label','SiteSight Taskbot');panel.innerHTML=`<div class="assist-head"><div><span>SITESIGHT TASKBOT</span><b>Type or talk</b><small>${assistEsc(assistPageLabel())} · ${assistEsc(currentUser()?.role||'')}</small></div><button onclick="assistClose()" aria-label="Close Taskbot">×</button></div><div class="assist-quick"><button onclick="assistAskPreset('How do I use this page?')">Explain this screen</button><button onclick="assistAskPreset('What needs attention here?')">What needs attention?</button><button onclick="openUserGuide(false);assistClose()">New User Guide</button></div><div class="assist-chat" aria-live="polite">${assistMessages.map((message,index)=>`<div class="assist-msg ${message.who}"><div>${assistEsc(message.text).replace(/\n/g,'<br>')}</div>${(message.actions||[]).length?`<div class="assist-actions">${message.actions.map((action,actionIndex)=>`<button class="${action.primary?'primary':''}" onclick="assistRunMessageAction(${index},${actionIndex})">${assistEsc(action.label)}</button>`).join('')}</div>`:''}</div>`).join('')}${assistBusy?'<div class="assist-msg bot"><div>Working that out…</div></div>':''}</div>${listening?'<div class="assist-listening-status" role="status">● Listening… speak now, then pause</div>':''}<div class="assist-input-row"><button class="assist-mic ${listening?'listening':''}" onclick="assistVoice()" aria-pressed="${listening}" aria-label="${listening?'Stop voice input':'Start voice input'}">${listening?'■':'🎤'}</button><textarea id="assist-input" rows="2" aria-label="Message Taskbot" placeholder="Type a request, or use the microphone…" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();assistSubmit()}"></textarea><button class="assist-send" onclick="assistSubmit()">Send</button></div><div class="assist-foot">Taskbot follows your role, Site access and permissions. Every record change requires your manual confirmation.</div>`;document.body.appendChild(panel);let chat=panel.querySelector('.assist-chat');if(chat)chat.scrollTop=chat.scrollHeight
  };
  rootWindow.assistOpenPanel=function(prefill=''){
    let user=currentUser();if(!user)return;if(v1998TaskbotUserId&&v1998TaskbotUserId!==user.id)v1998ClearTaskbotSession(true);v1998TaskbotUserId=user.id;assistOpen=true;
    if(!assistMessages.length)assistMessages=[{who:'bot',text:`Hi ${String(user.name||'').split(' ')[0]||'there'} — tell me what you want to do. I can explain a screen, take you there, or prepare a supported action for your confirmation.`}];assistMount();assistRenderPanel();if(prefill)setTimeout(()=>{let field=document.getElementById('assist-input');if(field){field.value=prefill;field.focus()}},20)
  };
  rootWindow.assistClose=function(){v1998ClearTaskbotSession(false)};
  rootWindow.assistMount=function(){
    if(!currentUser()){document.getElementById('sitesight-assist-fab')?.remove();return}let button=document.getElementById('sitesight-assist-fab');if(!button){button=document.createElement('button');button.id='sitesight-assist-fab';button.className='assist-fab';document.body.appendChild(button)}button.innerHTML='<span aria-hidden="true">✦</span><b>Taskbot</b>';button.setAttribute('aria-label','Open SiteSight Taskbot');button.onclick=()=>assistOpenPanel();if(assistOpen)assistRenderPanel()
  };
  rootWindow.assistVoice=function(){
    if(typeof v176AssistRecognition!=='undefined'&&v176AssistRecognition){try{v176AssistRecognition.stop()}catch(error){}return}let Speech=rootWindow.SpeechRecognition||rootWindow.webkitSpeechRecognition;if(!Speech)return toast('Voice input needs Chrome, Edge or another supported browser. You can still type your request.');let recognition=new Speech(),finalText='';v176AssistRecognition=recognition;recognition.lang='en-GB';recognition.interimResults=true;recognition.continuous=false;recognition.maxAlternatives=1;
    recognition.onstart=()=>{v176AssistListening=true;assistRenderPanel()};recognition.onresult=event=>{let heard=Array.from(event.results||[]).map(result=>result?.[0]?.transcript||'').join(' ').trim(),field=document.getElementById('assist-input');if(field)field.value=heard;let last=event.results?.[event.results.length-1];if(last?.isFinal&&heard){finalText=heard;v176AssistListening=false;assistRenderPanel();setTimeout(()=>{let next=document.getElementById('assist-input');if(next){next.value=finalText;assistSubmit()}},80)}};
    recognition.onerror=event=>{v176AssistListening=false;v176AssistRecognition=null;assistRenderPanel();let copy={'not-allowed':'Microphone permission was not allowed. Enable it for SiteSight, then try again.','audio-capture':'No working microphone was found.','no-speech':'I did not hear a request. Press the microphone and try again.','network':'Voice recognition could not reach the browser speech service.'};toast(copy[event.error]||'Voice input could not start')};recognition.onend=()=>{v176AssistRecognition=null;if(v176AssistListening){v176AssistListening=false;assistRenderPanel()}};try{recognition.start()}catch(error){v176AssistRecognition=null;v176AssistListening=false;assistRenderPanel();toast('Voice input could not start')}
  };
  function v1998TaskbotReviewRows(result){
    let plan=result.plan,rows=[];if(result.siteId)rows.push(['Site',siteName(result.siteId)]);if(result.target)rows.push(['Target',result.target]);
    if(plan.type==='issue')rows.push(['Priority',plan.priority],['Issue',plan.title],...(plan.targetDate?[['Target date',plan.targetDate]]:[]));
    if(plan.type==='progress'){let item=(db.items||[]).find(row=>row.id===plan.itemId);rows.push(['Stage',plan.stageIndex>=0?item?.lifts?.[plan.stageIndex]?.name||'Selected stage':'Overall record'],['New status',plan.status])}
    if(plan.type==='calendar')rows.push(['Date',plan.date],['Calendar note',plan.title]);if(plan.type==='absence')rows.push(['Date',plan.date],['Status',plan.absenceType]);if(plan.type==='leave')rows.push(['From',plan.from],['To',plan.to],['Request',plan.leaveType||'Holiday']);
    return rows.map(([label,value])=>`<div><dt>${assistEsc(label)}</dt><dd>${assistEsc(value)}</dd></div>`).join('')
  }
  rootWindow.assistConfirmPlan=function(plan){
    let checked=v1998ValidateTaskbotPlan(plan);if(!checked.ok){assistSay(checked.message);return}assistClose();let nonce=v1998TaskbotNonce();v1998TaskbotReview={nonce,userId:currentUser()?.id||'',createdAt:Date.now(),plan:checked.plan};assistPending=checked.plan;
    modal=`<div class="modal"><div class="modalbox assist-confirm v1998-taskbot-confirm"><span class="att-kicker">TASKBOT · REVIEW & CONFIRM</span><h2>Nothing changes until you confirm</h2><p>${assistEsc(assistPlanSummary(checked.plan))}</p><dl class="v1998-taskbot-review-grid">${v1998TaskbotReviewRows(checked)}</dl><div class="assist-confirm-rule">Taskbot will recheck your current permission and Site access, apply this once, then record the completed change in normal SiteSight history.</div><div class="actions"><button class="btn orange" onclick="v1998ConfirmTaskbot('${nonce}',this)">Confirm & apply change</button><button class="btn light" onclick="v1998CancelTaskbotPlan()">Cancel</button></div></div></div>`;render()
  };
  rootWindow.v1998CancelTaskbotPlan=function(){v1998TaskbotReview=null;assistPending=null;if(typeof v184AssistPlan!=='undefined')v184AssistPlan=null;modal=null;render();setTimeout(()=>assistOpenPanel(),20)};
  rootWindow.assistExecutePending=function(){return toast('Open Taskbot and use Review & confirm before applying a change.')};
  const v1998TaskbotDo={leave:assistDoLeave,absence:assistDoAbsence,issue:assistDoIssue,progress:assistDoProgress,calendar:assistDoCalendar};
  for(const [type,baseAction] of Object.entries(v1998TaskbotDo))rootWindow[`assistDo${type[0].toUpperCase()+type.slice(1)}`]=function(plan){if(!v1998TaskbotExecuting)return toast('This action must be confirmed in Taskbot first.');return baseAction(plan)};
  rootWindow.v1998ConfirmTaskbot=function(nonce,button){
    let review=v1998TaskbotReview;if(!review||review.nonce!==nonce||review.userId!==currentUser()?.id||Date.now()-review.createdAt>10*60*1000)return toast('This Taskbot confirmation has expired. Review the request again.');if(button){button.disabled=true;button.textContent='Applying…'}let checked=v1998ValidateTaskbotPlan(review.plan);if(!checked.ok){v1998TaskbotReview=null;assistPending=null;modal=null;render();return toast(checked.message)}
    let action=rootWindow[`assistDo${checked.plan.type[0].toUpperCase()+checked.plan.type.slice(1)}`];v1998TaskbotReview=null;assistPending=null;v1998TaskbotExecuting=true;try{return action(checked.plan)}finally{v1998TaskbotExecuting=false}
  };
  if(typeof v1991CloseModal==='function'){
    const v1998BaseCloseModal=v1991CloseModal;
    rootWindow.v1991CloseModal=function(){if(document.querySelector('.assist-confirm'))return v1998CancelTaskbotPlan();return v1998BaseCloseModal()}
  }
  rootWindow.v1992OpenHelp=function(){modal=`<div class="modal"><div class="modalbox v1992-help-modal"><span class="att-kicker">HELP</span><h2>${assistEsc(assistPageLabel())}</h2><p>${assistEsc(v1992ScreenExplanation())}</p><div class="v1992-help-actions"><button onclick="modal=null;render();setTimeout(()=>assistOpenPanel(),20)"><span>✦</span><b>Ask Taskbot</b><small>Type or speak a request. Changes always need confirmation.</small></button><button onclick="modal=null;openUserGuide(false)"><span>▶</span><b>New User Guide</b><small>Tour every screen available to this account.</small></button><button onclick="v1992ShowWhere()"><span>→</span><b>Show me where to click</b><small>Highlight the next useful control on this screen.</small></button><button onclick="toggleTrainingMode(!trainingMode)"><span>?</span><b>Turn hints ${trainingMode?'off':'on'}</b><small>Hints are currently ${trainingMode?'shown':'hidden'}.</small></button></div><button class="btn light" onclick="modal=null;render()">Close</button></div></div>`;render()};

  // Final mutation boundary: every plot-related global rechecks the signed-in
  // role, exact record, Site scope and action permission at the point of use.
  function v1998MutationPermission(perms){return !perms?.length||perms.some(permission=>hasPerm(permission))}
  function v1998GuardItemMutation(name,resolver,perms=[],mode='view'){
    const base=rootWindow[name];if(typeof base!=='function')return;
    rootWindow[name]=function(...args){
      if(!roleCanAccessPlots())return toast('Plot records require Supervisor access or above');
      if(!v1998MutationPermission(perms))return toast('Your permissions do not allow this plot change');
      const item=resolver(...args),allowed=mode==='update'?canUpdateItem(item):canViewItem(item);
      if(!allowed)return toast(mode==='update'?'You cannot update this work record':'This work record is outside your access');
      return base.apply(this,args)
    }
  }
  function v1998GuardNewWorkMutation(name,siteResolver){
    const base=rootWindow[name];if(typeof base!=='function')return;
    rootWindow[name]=function(...args){
      if(!roleCanAccessPlots()||!hasPerm('manage_workitems'))return toast('Work-item creation requires Supervisor access or above');
      const siteId=siteResolver(...args);if(!siteId||!userCanAccessSite(currentUser(),siteId))return toast('Choose a Site within your permitted access');
      return base.apply(this,args)
    }
  }
  const v1998SelectedItem=()=> (db.items||[]).find(row=>row.id===selected);
  const v1998ItemFromFirstArg=id=> (db.items||[]).find(row=>row.id===id);
  v1998GuardItemMutation('saveIssueRecord',v1998ItemFromFirstArg,['manage_issues']);
  v1998GuardItemMutation('addIssuePhotos',v1998ItemFromFirstArg,['raise_issues','manage_issues']);
  v1998GuardItemMutation('saveIssuePhotos',v1998ItemFromFirstArg,['raise_issues','manage_issues']);
  v1998GuardItemMutation('resolveIssue',v1998ItemFromFirstArg,['manage_issues']);
  v1998GuardItemMutation('addIssue',v1998SelectedItem,['raise_issues']);
  v1998GuardItemMutation('saveIssue',v1998SelectedItem,['raise_issues']);
  v1998GuardItemMutation('editLifts',v1998SelectedItem,['edit_lifts']);
  v1998GuardItemMutation('saveLifts',v1998SelectedItem,['edit_lifts']);
  v1998GuardItemMutation('editNotes',v1998SelectedItem,['update_progress'],'update');
  v1998GuardItemMutation('saveRichNotes',v1998SelectedItem,['update_progress'],'update');
  v1998GuardItemMutation('signOff',v1998SelectedItem,['sign_off'],'update');
  v1998GuardItemMutation('confirmSignOff',v1998SelectedItem,['sign_off'],'update');
  v1998GuardItemMutation('saveWorkItemTeam',v1998SelectedItem,['manage_workitems','manage_gangs']);
  v1998GuardItemMutation('saveLiftCard',v1998SelectedItem,['update_progress'],'update');
  v1998GuardItemMutation('saveGardenCard',v1998SelectedItem,['update_progress'],'update');
  v1998GuardItemMutation('quickUpdate',v1998ItemFromFirstArg,['update_progress'],'update');
  v1998GuardItemMutation('quickProgress',v1998ItemFromFirstArg,['update_progress'],'update');
  v1998GuardItemMutation('saveQuickProgress',v1998ItemFromFirstArg,['update_progress'],'update');
  v1998GuardItemMutation('addWorkPhotos',v1998SelectedItem,['update_progress'],'update');
  v1998GuardItemMutation('saveWorkPhotos',v1998SelectedItem,['update_progress'],'update');
  v1998GuardItemMutation('removeWorkPhoto',v1998SelectedItem,['update_progress'],'update');
  v1998GuardItemMutation('uploadDrawing',v1998SelectedItem,['manage_drawings']);
  v1998GuardItemMutation('saveDrawing',v1998SelectedItem,['manage_drawings']);
  v1998GuardItemMutation('openMatchingSiteDrawings',v1998SelectedItem,['manage_drawings']);
  v1998GuardItemMutation('transferSiteDrawing',(drawingId,targetId='')=>(db.items||[]).find(row=>row.id===(targetId||document.getElementById('sdw-target')?.value||'')),['manage_drawings']);
  v1998GuardNewWorkMutation('createItem',()=>document.getElementById('ns')?.value||'');
  v1998GuardNewWorkMutation('contractorCreateItem',()=>document.getElementById('ns')?.value||'');
  v1998GuardNewWorkMutation('confirmBulkImport',()=>document.getElementById('bulk-site')?.value||bulkImportSite||'');

  let v1998AutoGuideUserId='';
  const v1998BaseRender=render;
  rootWindow.render=function(){
    if(view==='test-route')view='guide';let result=v1998BaseRender(),user=currentUser();
    if(v1998ShouldAutoOpenGuide(user,{view,guideActive})&&v1998AutoGuideUserId!==user.id){v1998AutoGuideUserId=user.id;setTimeout(()=>{if(v1998ShouldAutoOpenGuide(currentUser(),{view,guideActive}))openUserGuide(true)},0)}
    setTimeout(assistMount,0);return result
  };
  const v1998BaseLogout=logout;
  rootWindow.logout=function(...args){v1998AutoGuideUserId='';v1998TaskbotUserId='';v1998ClearTaskbotSession(true);return v1998BaseLogout.apply(this,args)};
  const v1998BaseLogin=login;
  rootWindow.login=async function(...args){v1998TaskbotUserId='';v1998ClearTaskbotSession(true);return v1998BaseLogin.apply(this,args)};
  rootWindow.v1998ClearTaskbotSession=v1998ClearTaskbotSession;

  integrationRefreshSyncControls()
})();
