---
title: "Private Tours"
url: "https://socotratravels.com/socotra-private-tours/"
type: "page"
published: "2022-11-08T19:06:31+03:00"
modified: "2026-01-29T15:17:21+03:00"
author: "Admin"
---

# Design Your Private Socotra Trip — Built Live, Your Way

A smarter way to plan a fully personalized journey to Socotra & South Yemen

At **Socotra Travel Center**, we don’t just collect trip requests — we help you **build your journey step by step**, with real availability, transparent pricing, and guidance from **local experts based in Socotra and Yemen**.

Our new **Trip Planner** lets you design a private itinerary in real time:
choose your destination, dates, flights, accommodation style, and extras — all while instantly seeing costs and options update as you go.

Whether you’re traveling solo, as a couple, with family, or friends, this system gives you **clarity, control, and confidence** from the very first step.

### **How the Trip Planner Works**

1️⃣ **Build your trip**
Select your destination, duration, travel style, and preferred package.

2️⃣ **Match flights & dates**
Choose available flights based on your travel dates — we calculate your return automatically.

3️⃣ **Personalize your stay**
Upgrade hotels, camping, or add special services if you wish.

4️⃣ **Review & confirm**
See a full summary, choose how you’d like to pay, and submit securely.

💡 *Nothing is finalized without confirmation — our team reviews and confirms everything with you.*

### **Why Plan Your Trip This Way?**

✔ Real-time pricing — no surprises
✔ Designed for private trips, not mass tourism
✔ Flights & services reviewed by real humans
✔ Local team on the ground in Socotra & Yemen
✔ Secure payment options — deposit or full payment

You’re not booking through an overseas reseller or automated agency.
You’re working directly with **local specialists who know Socotra inside out**.

### **Start Designing Your Trip**

Use the Trip Planner below to build your journey step by step.
You can adjust choices at any time and see updates instantly.

🕒 Average time to complete: **5–8 minutes**

Fields marked with * are required.

## Step 2 of 4 — Select Flights

Pick your preferred departure date and let us handle the rest. Based on your trip duration, we automatically calculate your return date and display available flights with full details and pricing.

⚠️ Important: Booking your round-trip flights through us is **required** for this trip. These flights are **not included** in the initial package and must be added on, based on availability!!! See below for all round-trip flight details:

## When do you want to travel?

Select your departure date. Your return date will be calculated automatically based on your chosen trip duration.

    **Departure Date**  2026-11-082026-11-082026-11-102026-11-122026-11-122026-11-122026-11-152026-11-152026-11-172026-11-192026-11-192026-11-192026-11-222026-11-222026-11-242026-11-262026-11-262026-11-262026-11-292026-11-292026-12-012026-12-032026-12-032026-12-032026-12-062026-12-062026-12-082026-12-102026-12-102026-12-102026-12-132026-12-132026-12-152026-12-172026-12-172026-12-172026-12-202026-12-202026-12-222026-12-242026-12-242026-12-242026-12-272026-12-27     jet-form-builder--hidden          **Trip Duration**  0.00       0.00 Nights                **Return Date (calculated)**  0.00     jet-form-builder--hidden

## Available Flights – Outbound ✈️ ➡️

Compare airlines, routes, timings, and fares. Seat availability and prices are updated in real time. And select one to view and update details!

    **Available Outbound Flights: ()** *        This field has no options

## Available Flights – Return ✈️ ⬅️

Choose your return flight that best fits your schedule and budget. And select one to view and update details!

    **Available Return Flights: ()** *        This field has no options     Please select a ${this.type==='departure'?'start':'return'} date to see available flights.Select ${this.type==='departure'?'Departure':'Return'} Date →Retry Now`;this.container.appendChild(div);this.dateEmptyOverlay=div;this.isEmptyState=true;this.isLoaded=false;this.offlinePendingDate=null;this._dateEmpty=true;if(this.type==='departure')_departureFlightsLoaded=false;else _returnFlightsLoaded=false;log(`📍 [GLOBAL] Date empty for ${this.type}, resetting its loaded flag`,'INFO',this.type);if(window.RequestQueue){window.RequestQueue.clearListRequestsForLoader(this.type);window.RequestQueue.abortAllForLoader(this.type);window.RequestQueue.clear(true);}setTimeout(()=>{this._preventContentRestoration=false;},CONFIG.timing.preventRestorationDuration);log(`📭 Date empty overlay shown for ${this.type} (empty block activated)`,'INFO',this.type);} hideDateEmpty(){if(this.dateEmptyOverlay){this.dateEmptyOverlay.remove();this.dateEmptyOverlay=null;this.isEmptyState=false;this._dateEmpty=false;log(`🗑️ Date empty overlay hidden for ${this.type} (empty block cleared)`,'DEBUG',this.type);}} showLoading(){if(this.loadingOverlay){this.loadingOverlay.remove();this.loadingOverlay=null;}const div=document.createElement('div');div.className='flight-loading-final';const icon=this.type==='departure'?'✈️ ➡️':'✈️ ⬅️';div.innerHTML=`${icon}0%Loading ${this.type==='departure'?'departure':'return'} flights... `;if(getComputedStyle(this.container).position==='static')this.container.style.position='relative';this.container.appendChild(div);requestAnimationFrame(()=>div.classList.add('active'));this.loadingOverlay=div;this._loadingOverlayShown=true;log(`🌀 Loading overlay shown for ${this.type}`,'INFO',this.type);} hideLoading(){if(this.loadingOverlay){this.loadingOverlay.remove();this.loadingOverlay=null;this._loadingOverlayShown=false;log(`🔵 Loading overlay hidden for ${this.type}`,'INFO',this.type);}} showNoFlights(){if(this._successLockUntil>Date.now()){log(`🔒 [NOFLIGHTS] Skipped due to success lock (${Math.ceil((this._successLockUntil-Date.now())/1000)}s remaining) for ${this.type}`,'DEBUG',this.type);return;}if(this._recentServerError&&(Date.now()-this._recentServerErrorTime)this._clearFlightContent());this.hideAllOverlays();const date=this.getDateValue();const formattedDate=date?new Date(date).toLocaleDateString():'selected date';const div=document.createElement('div');div.className='flight-no-flights-final';div.innerHTML=`✈️ No ${this.type==='departure'?'Departure':'Return'} FlightsNo flights available for ${formattedDate}.
Try a different date.Change Date →Retry Now`;this.container.appendChild(div);this.noFlightsOverlay=div;this.noFlightsConfirmed=true;this._noFlightsDate=date;this.isLoaded=false;this.isEmptyState=true;this._terminalStateLocked=true;this._terminalStateSetAt=Date.now();log(`🔒 [TERMINAL] No Flights terminal state locked for ${this.type} date ${date} at ${this._terminalStateSetAt}`,'INFO',this.type);if(window.RequestQueue){window.RequestQueue.clearListRequestsForLoader(this.type);window.RequestQueue.abortAllForLoader(this.type);}setTimeout(()=>{this._preventContentRestoration=false;},CONFIG.timing.preventRestorationDuration);log(`📭 No flights overlay shown for ${this.type} (date: ${formattedDate})`,'INFO',this.type);this._startRetryLoop();setTimeout(()=>{this._crossTriggerOtherLoader();},CONFIG.timing.crossTriggerRecheckDelay);} _startRetryLoop(){let maxTotal=CONFIG.timing.retryLoopTotalMs;let delays=[2000,4000,6000,8000,10000,12000,14000,16000];if(this._serverConfirmedEmpty){maxTotal=CONFIG.timing.retryLoopShortTotalMs;delays=[2000,4000,4000];log(`⚡ [RETRY-LOOP] Server confirmed empty – using short loop (${maxTotal/1000}s) for ${this.type}`,'INFO',this.type);}else log(`⏳ [RETRY-LOOP] Using standard loop (${maxTotal/1000}s) for ${this.type}`,'INFO',this.type);let total=0,index=0;const nextRetry=()=>{if(this.noFlightsOverlay===null||this.isLoaded)return;if(this.detectFlights(true)){log(`🎯 [RETRY-LOOP] Flights found during check – immediately finalizing for ${this.type}`,'INFO',this.type);this.hideNoFlights();this._finalizeSuccess();return;}if(index>=delays.length){log(`⏳ [RETRY-LOOP] Entering extended check (every ${CONFIG.timing.retryLoopExtendedIntervalMs/1000}s for ${CONFIG.timing.retryLoopExtendedAttempts*CONFIG.timing.retryLoopExtendedIntervalMs/1000}s) for ${this.type}`,'INFO',this.type);let extendedAttempts=0;const extendedCheck=()=>{if(this.noFlightsOverlay===null||this.isLoaded)return;if(extendedAttempts>=CONFIG.timing.retryLoopExtendedAttempts){log(`📭 [RETRY-LOOP] Extended check timed out after ${CONFIG.timing.retryLoopExtendedAttempts*CONFIG.timing.retryLoopExtendedIntervalMs/1000}s – no flights for ${this.type}`,'INFO',this.type);return;}setTimeout(()=>{if(this.noFlightsOverlay===null||this.isLoaded)return;if(this.detectFlights(true)){log(`🎯 [RETRY-LOOP] Flights found during extended check – replacing overlay for ${this.type}`,'INFO',this.type);this.hideNoFlights();this._finalizeSuccess();}else{extendedAttempts++;extendedCheck();}},CONFIG.timing.retryLoopExtendedIntervalMs);};extendedCheck();return;}const delay=delays[index];total+=delay;if(total>maxTotal){const remaining=maxTotal-(total-delay);setTimeout(()=>{if(this.noFlightsOverlay!==null&&!this.isLoaded&&!this.detectFlights(true)){log(`⏰ [RETRY-LOOP] Primary timeout after ${maxTotal/1000}s – entering extended check for ${this.type}`,'INFO',this.type);let extendedAttempts=0;const extendedCheck=()=>{if(this.noFlightsOverlay===null||this.isLoaded)return;if(extendedAttempts>=CONFIG.timing.retryLoopExtendedAttempts){log(`📭 [RETRY-LOOP] Extended check timed out after ${CONFIG.timing.retryLoopExtendedAttempts*CONFIG.timing.retryLoopExtendedIntervalMs/1000}s – no flights for ${this.type}`,'INFO',this.type);return;}setTimeout(()=>{if(this.noFlightsOverlay===null||this.isLoaded)return;if(this.detectFlights(true)){log(`🎯 [RETRY-LOOP] Flights found during extended check – replacing overlay for ${this.type}`,'INFO',this.type);this.hideNoFlights();this._finalizeSuccess();}else{extendedAttempts++;extendedCheck();}},CONFIG.timing.retryLoopExtendedIntervalMs);};extendedCheck();}},remaining);return;}log(`⏳ [RETRY-LOOP] Waiting ${delay/1000}s before re-check for ${this.type} (attempt ${index+1}/${delays.length})`,'DEBUG',this.type);this._tertiaryNoFlightsTimer=setTimeout(()=>{if(this.noFlightsOverlay===null||this.isLoaded)return;const found=this.detectFlights(true);if(found){log(`🎯 [RETRY-LOOP] Flights found after ${delay/1000}s – replacing overlay for ${this.type}`,'INFO',this.type);this.hideNoFlights();this._finalizeSuccess();}else{index++;nextRetry();}},delay);};nextRetry();} hideNoFlights(){this._clearNoFlightsState();} showError(msg,cooldownSeconds=0,permanent=false){if(this.noFlightsOverlay||this.noFlightsConfirmed){log(`⏸️ [ERROR] Suppressed error overlay because "No Flights" is already shown for ${this.type}`,'DEBUG',this.type);return;}if(this._finalizing){log(`⏸️ [ERROR] Suppressed error overlay because finalization in progress for ${this.type}`,'DEBUG',this.type);return;}if(this.errorOverlay)return;this.hideAllOverlays();this.permanentError=permanent;if(permanent){log(`🚨 [PERM-ERROR] permanentError set to true for ${this.type} (reason: "${msg}")`,'WARN',this.type);this.stopLoading();}const div=document.createElement('div');div.className='flight-error-final';const countdownHtml=cooldownSeconds>0?`${cooldownSeconds}`:'';div.innerHTML=`⚠️Loading Failed${msg}Retry Now ${countdownHtml}`;this.container.appendChild(div);this.errorOverlay=div;if(cooldownSeconds>0){let remaining=cooldownSeconds;if(this.localErrorCooldown.timer)clearInterval(this.localErrorCooldown.timer);this.localErrorCooldown.timer=setInterval(()=>{remaining--;this.updateErrorCountdown(remaining);if(remaining<=0){clearInterval(this.localErrorCooldown.timer);this.localErrorCooldown.timer=null;if(!this.hasValidDate()){this.hideError();this.showDateEmpty();return;}this.hideError();this._resetAllForcedRefreshFlags();this.active=false;this.permanentError=false;this.errorCooldownTriggered=false;log(`🔄 Local cooldown expired for ${this.type}, restarting...`,'INFO',this.type);this._releaseLock();this._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true});}},1000);}} hideError(){ if(this.errorOverlay){ if(this.localErrorCooldown.timer)clearInterval(this.localErrorCooldown.timer); this.errorOverlay.remove(); this.errorOverlay=null; const wasPermanent=this.permanentError; this.permanentError=false; // V379: reset _consecutive5xx when clearing permanent to avoid 6/5 7/5 8/5 if(wasPermanent&&this._consecutive5xx>0){ log(`🔄 [RECOVERY] Resetting _consecutive5xx from ${this._consecutive5xx} → 0 for ${this.type} (permanentError cleared)`,'INFO',this.type); this._consecutive5xx=0; } log(`🧹 [PERM-ERROR] permanentError cleared for ${this.type} (error overlay hidden)`,'INFO',this.type); } } updateErrorCountdown(sec){if(this.errorOverlay){const span=this.errorOverlay.querySelector('.flight-retry-countdown-final');if(span)span.textContent=sec;}} updateNetworkQueuedDate(date){if(this.networkOverlay&&date){let el=this.networkOverlay.querySelector('.flight-network-queued');if(!el){el=document.createElement('div');el.className='flight-network-queued';this.networkOverlay.appendChild(el);}el.textContent=`📅 Queued: ${date}`;}} showNetworkOffline(){if(this.systemCooldownOverlay||this.noFlightsConfirmed||this.isLoaded||this._finalizing){log(`🌐 Network offline but suppressed (cooldown=${!!this.systemCooldownOverlay}, noFlights=${this.noFlightsConfirmed}, loaded=${this.isLoaded}, finalizing=${this._finalizing}) for ${this.type}`,'DEBUG',this.type);return;}if(GlobalCooldownManager.getRemainingTime()>0||_globalRefreshInProgress){log(`🌐 Network offline but global cooldown or refresh active – skipping overlay for ${this.type}`,'DEBUG',this.type);return;}if(this._isVerificationStage()){log(`🌐 Network offline but verification stage in progress – not showing overlay for ${this.type}`,'INFO',this.type);return;}if(!this.hasValidDate()){this.showDateEmpty();return;}if(this.isLoaded){log(`🌐 Network offline but flights already loaded – skipping overlay for ${this.type}`,'INFO',this.type);return;}if(this.networkOverlay){this.updateNetworkQueuedDate(this.offlinePendingDate);return;}this.hideAllOverlays();const div=document.createElement('div');div.className='flight-network-overlay';const queuedDate=this.offlinePendingDate;const queuedHtml=queuedDate?`📅 Queued: ${queuedDate}`:'No date queued';div.innerHTML=`Network OfflineCannot load flights while offline. Waiting for connection...${queuedHtml}`;this.container.appendChild(div);this.networkOverlay=div;log(`🌐 Network offline overlay shown for ${this.type} (queued: ${queuedDate||'none'})`,'INFO',this.type);} hideNetworkOffline(){if(this.networkOverlay){this.networkOverlay.remove();this.networkOverlay=null;}} showNetworkRestored(){if(GlobalCooldownManager.getRemainingTime()>0){log(`🌐 Network restored but global cooldown active – skipping overlay for ${this.type}`,'DEBUG',this.type);return;}if(this.systemCooldownOverlay||this.noFlightsConfirmed||this.isLoaded||this._finalizing){log(`🌐 Network restored but suppressed (cooldown=${!!this.systemCooldownOverlay}, noFlights=${this.noFlightsConfirmed}, loaded=${this.isLoaded}, finalizing=${this._finalizing}) for ${this.type}`,'DEBUG',this.type);return;}if(_globalRefreshInProgress){log(`🌐 Network restored but global refresh active – skipping overlay for ${this.type}`,'DEBUG',this.type);return;}if(this._isVerificationStage()){log(`🌐 Network restored but verification stage in progress – skipping overlay for ${this.type}`,'INFO',this.type);return;}if(!this.hasValidDate()){this.showDateEmpty();return;}if(this.noFlightsConfirmed&&this._noFlightsDate===this.getDateValue()){log(`🌐 Network restored but "No Flights" already confirmed for this date – preserving overlay and doing nothing for ${this.type}`,'INFO',this.type);return;}if(this.restoredOverlay)return;this._pluginRefreshInProgress=false;this._suppressEmptyDuringRefresh=true;this._suppressDateChanges=true;this._suppressIntervalChecks=true;this._networkRestoreInProgress=true;this._suppressNewListRequests=true;this._refreshLock=false;this._lastPluginRefreshTime=0;this._lastForcedRefreshCall=0;if(this.dateField&&this.dateField._originalChangeHandler){this.dateField.removeEventListener('change',this.dateField._originalChangeHandler);this.dateField.removeEventListener('input',this.dateField._originalInputHandler);}this.hideAllOverlays();const div=document.createElement('div');div.className='flight-restored-overlay';div.innerHTML=`Network RestoredConnection re-established. Refreshing flights...${CONFIG.network.restoreCountdown}Refresh Now`;this.container.appendChild(div);this.restoredOverlay=div;log(`🌐 Network restored overlay shown for ${this.type} (suppressing date changes, empty state & interval checks)`,'INFO',this.type);} updateRestoredCountdown(sec){if(this.restoredOverlay){const el=this.restoredOverlay.querySelector('.flight-restored-countdown');if(el)el.textContent=sec;}} hideNetworkRestored(){if(this.restoredOverlay){if(this.restoredCountdownInterval)clearInterval(this.restoredCountdownInterval);this.restoredOverlay.remove();this.restoredOverlay=null;}this._suppressDateChanges=false;this._suppressIntervalChecks=false;this._networkRestoreInProgress=false;_dateResetInProgress=false;this._dateResetInProgress=false;this._ignoreNextChange=false;this._processingDate=null;setTimeout(()=>{if(this.dateField&&!this.dateField._originalChangeHandler)log(`🔗 Re-attaching date field listeners for ${this.type}`,'DEBUG',this.type);},500);} showSystemCooldown(sec,cycle){if(this._finalizing){log(`⏸️ [COOLDOWN] Suppressed system cooldown because finalizing for ${this.type}`,'DEBUG',this.type);return;}if(this._isVerificationStage()){log(`⏸️ [COOLDOWN] Suppressed system cooldown because verification stage in progress for ${this.type}`,'INFO',this.type);return;}if(this.systemCooldownOverlay){this.updateSystemCooldownTimer(sec,cycle);return;}this.hideAllOverlays();if(getComputedStyle(this.container).position==='static')this.container.style.position='relative';const div=document.createElement('div');div.className='flight-cooldown-overlay';div.innerHTML=`System Cooling DownRate limit detected. Retrying automatically in ${sec}s...${sec}Cooldown cycle: ${cycle}Retry Now`;this.container.appendChild(div);this.systemCooldownOverlay=div;let remaining=sec;if(this.cooldownInterval)clearInterval(this.cooldownInterval);this.cooldownInterval=setInterval(()=>{remaining--;const s=this.systemCooldownOverlay?.querySelector(`#cooldown-timer-${this.type}`);const l=this.systemCooldownOverlay?.querySelector(`#cooldown-timer-large-${this.type}`);if(s)s.textContent=remaining;if(l)l.textContent=remaining;if(remaining<=0){clearInterval(this.cooldownInterval);this.cooldownInterval=null;this.hideSystemCooldown();}},1000);log(`❄️ System cooldown overlay shown for ${this.type} (${sec}s, cycle ${cycle})`,'INFO',this.type);} updateSystemCooldownTimer(sec,cycle){if(this.systemCooldownOverlay){const s=this.systemCooldownOverlay.querySelector(`#cooldown-timer-${this.type}`);const l=this.systemCooldownOverlay.querySelector(`#cooldown-timer-large-${this.type}`);const c=this.systemCooldownOverlay.querySelector('.flight-cooldown-cycle');if(s)s.textContent=sec;if(l)l.textContent=sec;if(c)c.textContent=`Cooldown cycle: ${cycle}`;}} hideSystemCooldown(){if(this.cooldownInterval){clearInterval(this.cooldownInterval);this.cooldownInterval=null;}if(this.systemCooldownOverlay){this.systemCooldownOverlay.remove();this.systemCooldownOverlay=null;}} onGlobalCooldown(duration,cycle){ log(`❄️ Global cooldown for ${this.type}: ${Math.round(duration/1000)}s`,'INFO',this.type); _clearGlobalLoadingFlag('onGlobalCooldown:'+this.type); this._releaseLock();this._resetAllForcedRefreshFlags();this.ajaxResponseReceived=false;this.firstDetectionDone=false;this.gracePeriodActive=false;this.emptyResponseReceived=false;this.noFlightsConfirmed=false;this.errorCooldownTriggered=false;this._blockForcedRefreshUntil=Date.now()+duration;this._clearAllTimers('onGlobalCooldown');if(this.progressUpdateInterval)clearInterval(this.progressUpdateInterval);if(!this.isLoaded&&!this._isVerificationStage())this.showSystemCooldown(Math.ceil(duration/1000),cycle);else{this.cooldownBlocked=true;this.showCooldownToast(Math.ceil(duration/1000));setTimeout(()=>{this.hideCooldownToast();this.cooldownBlocked=false;},duration);}} showCooldownToast(seconds){if(this.cooldownToast)return;this.cooldownToast=document.createElement('div');this.cooldownToast.className='flight-cooldown-toast';this.cooldownToast.innerHTML=`⏱️ Rate limit active (${seconds}s) - new searches paused`;if(this.container)this.container.appendChild(this.cooldownToast);let remaining=seconds;const interval=setInterval(()=>{remaining--;if(this.cooldownToast)this.cooldownToast.innerHTML=`⏱️ Rate limit active (${remaining}s) - new searches paused`;if(remaining<=0)clearInterval(interval);},1000);this._cooldownToastInterval=interval;} hideCooldownToast(){if(this._cooldownToastInterval)clearInterval(this._cooldownToastInterval);if(this.cooldownToast){this.cooldownToast.remove();this.cooldownToast=null;}} hideAllOverlays(){this.hideLoading();this.hideDateEmpty();this.hideNoFlights();this.hideError();this.hideSystemCooldown();this.hideNetworkOffline();this.hideNetworkRestored();this.hideCooldownToast();} _forcePluginRefresh(){if(!this.dateField)return;log(`🔄 [FORCE-AJAX] Dispatching change event on ${this.type} date field to trigger LIST request`,'INFO',this.type);this._forcingAjax=true;requestAnimationFrame(()=>{this.dateField.dispatchEvent(new Event('change',{bubbles:true}));this._forcingAjax=false;log(`✅ [FORCE-AJAX] Change event dispatched for ${this.type}`,'INFO',this.type);});} async _triggerPluginRefresh(options={bypassDateCheck:false,bypassThrottle:false,force:false}){ const now=Date.now(); _globalRefreshInProgress=true; if(this._refreshLockSafetyTimer){clearTimeout(this._refreshLockSafetyTimer);this._refreshLockSafetyTimer=null;} const currentDate=this.getDateValue(); if(!options.force&&this._isTerminalForDate(currentDate)){log(`⏸️ [FR] Skipping refresh because loader is terminal for date ${currentDate} (${this.isLoaded?'loaded':'noFlights'})`,'INFO',this.type);_globalRefreshInProgress=false;return false;} if(!options.force&&this.isLoaded&&this._lastLoadedDate===currentDate){log(`⏸️ [FR] Skipping refresh because already loaded for date ${currentDate}`,'INFO',this.type);_globalRefreshInProgress=false;return false;} if(this._refreshLock){log(`⏸️ [FR] Refresh already in progress, skipping`,'WARN',this.type);_globalRefreshInProgress=false;return false;} if(this._refreshScheduled){log(`⏸️ [FR] Refresh already scheduled, skipping`,'WARN',this.type);_globalRefreshInProgress=false;return false;} if(!options.force){if(!options.bypassThrottle&&(now-this._lastPluginRefreshTime{if(this._refreshLock){log(`⏰ [FR] Safety: auto-clearing _refreshLock for ${this.type} after 30s (stuck detection)`,'WARN',this.type);this._refreshLock=false;this._refreshScheduled=false;this._refreshStuck=true;}this._refreshLockSafetyTimer=null;},CONFIG.timing.refreshLockSafetyMs); if(this._refreshDebounceTimer){clearTimeout(this._refreshDebounceTimer);this._refreshDebounceTimer=null;log(`🔄 [FR] Cleared pending debounce, executing refresh now`,'INFO',this.type);} const dateKey=currentDate||'unknown'; let attemptsForDate=this._forcedRefreshRetriesForDate.get(dateKey)||0; log(`🔄 [FR] Plugin refresh for ${this.type}: toggling date field (clear → restore) (attempt ${attemptsForDate+1}/∞)`,'INFO',this.type); const originalDate=this.getDateValue(); const safeDate=originalDate||this.lastDateValue; if(!safeDate||!isValidDateString(safeDate)){log(`⚠️ [FR] Cannot trigger refresh: no valid date for ${this.type}`,'WARN',this.type);this._refreshLock=false;this._refreshScheduled=false;_globalRefreshInProgress=false;return false;} if(!options.force&&now{if(this._pluginRefreshInProgress){log(`⚠️ [FR] Plugin refresh stuck – retrying toggle for ${this.type}`,'WARN',this.type);this.dateField.disabled=true;this.dateField.value='';this.dateField.dispatchEvent(new Event('change',{bubbles:true}));this.dateField.dispatchEvent(new Event('input',{bubbles:true}));setTimeout(()=>{if(this.dateField){this.dateField.disabled=wasDisabled;this.dateField.value=safeDate;this.dateField.dispatchEvent(new Event('change',{bubbles:true}));this.dateField.dispatchEvent(new Event('input',{bubbles:true}));}if(this.type==='return'&&this.selectField){const optionToSelect=Array.from(this.selectField.options).find(opt=>opt.value===safeDate||opt.textContent.includes(safeDate));if(optionToSelect){this.selectField.value=optionToSelect.value;this.selectField.dispatchEvent(new Event('change',{bubbles:true}));}}this._pluginRefreshInProgress=false;this._suppressEmptyDuringRefresh=false;this._suppressDateChanges=false;this._suppressIntervalChecks=false;this._ignoreNextChange=false;this._suppressNewListRequests=false;this._listRequestPending=false;if(currentDate)clearListRequestPending(this,currentDate);this._refreshLock=false;this._refreshScheduled=false;this._refreshStuck=false;this._pluginRefreshSuppressUntil=0;_globalRefreshInProgress=false;_clearGlobalLoadingFlag('pluginRefreshRetry:'+this.type);log(`✅ [FR] Plugin refresh completed (retry) for ${this.type}`,'INFO',this.type);},500);}},CONFIG.timing.pluginRefreshSafetyMs); setTimeout(()=>{ if(this.dateField){this.dateField.disabled=wasDisabled;this.dateField.value=safeDate;this.dateField.dispatchEvent(new Event('change',{bubbles:true}));this.dateField.dispatchEvent(new Event('input',{bubbles:true}));log(`📡 [FR] Restored ${this.type} date field to "${safeDate}" and triggered change/input`,'DEBUG',this.type);} if(this.type==='return'&&this.selectField){const optionToSelect=Array.from(this.selectField.options).find(opt=>opt.value===safeDate||opt.textContent.includes(safeDate));if(optionToSelect){this.selectField.value=optionToSelect.value;this.selectField.dispatchEvent(new Event('change',{bubbles:true}));log(`📡 [FR] Synced return select field to "${safeDate}"`,'DEBUG',this.type);}} if(dateChangeDebounceTimers&&dateChangeDebounceTimers[this.type]){clearTimeout(dateChangeDebounceTimers[this.type]);dateChangeDebounceTimers[this.type]=null;log(`⏸️ [FR] Cleared pending date change debounce for ${this.type}`,'DEBUG',this.type);} setTimeout(()=>{ this._pluginRefreshInProgress=false;this._suppressEmptyDuringRefresh=false;this._suppressDateChanges=false;this._suppressIntervalChecks=false;this._ignoreNextChange=false;this._suppressNewListRequests=false;this._listRequestPending=false; if(currentDate)clearListRequestPending(this,currentDate); this._pluginRefreshSuppressUntil=0;_globalRefreshInProgress=false;_clearGlobalLoadingFlag('pluginRefreshDone:'+this.type); log(`✅ [FR] Plugin refresh completed for ${this.type}`,'INFO',this.type); this._refreshLock=false;this._refreshScheduled=false;this._refreshStuck=false;forceListingRefresh(this); if(this.hasValidDate()&&!this.isLoaded&&!this.noFlightsConfirmed&&!this.permanentError&&!this._isTerminalForDate(this.getDateValue())){log(`🚀 [FR] Auto-starting load after plugin refresh for ${this.type}`,'INFO',this.type);this.startLoading(false);} else{log(`⏸️ [FR] Not auto-starting load because terminal or already loaded`,'DEBUG',this.type);if(!this.isLoaded&&!this.noFlightsConfirmed&&this.hasValidDate()&&!this.permanentError&&!this._isTerminalForDate(this.getDateValue())){log(`🔄 [FR] Auto-start skipped but conditions allow – forcing start for ${this.type}`,'INFO',this.type);this.startLoading(true);}} },500); },200); return true; } _clearPluginRefreshTimers(){if(this._pluginRefreshRestoreTimer)clearTimeout(this._pluginRefreshRestoreTimer);if(this._pluginRefreshClearTimer)clearTimeout(this._pluginRefreshClearTimer);if(this._pluginRefreshSafetyTimer)clearTimeout(this._pluginRefreshSafetyTimer);} _areDetailsPopulated(){const detailSelectors=['[name="dep_meta_id"]','[name="dep_post_id"]','[name="dep_carrier"]','[name="dep_route"]','[name="dep_date"]','[name="dep_fare"]','[name="ret_meta_id"]','[name="ret_post_id"]','[name="ret_carrier"]','[name="ret_route"]','[name="ret_date"]','[name="ret_fare"]'];for(const sel of detailSelectors){const el=document.querySelector(sel);if(el){const val=el.value||el.textContent.trim();if(val&&val!==''){log(`🔍 [DETAIL] Found populated field: ${el.name||el.id||'unknown'} = "${val}"`,'DEBUG',this.type);return true;}}}const cards=this.container?.querySelectorAll(CONFIG.selectors.flightCard)||[];for(const card of cards){if(card.textContent&&card.textContent.trim()!==''&&!card.closest('.flight-loading-final')){const price=card.querySelector('[name="dep_fare"]')||card.querySelector('[name="ret_fare"]');if(price&&price.textContent&&price.textContent.trim()!==''&&/\d/.test(price.textContent)){log(`🔍 [DETAIL] Found flight card with fare: "${price.textContent}"`,'DEBUG',this.type);return true;}}}return false;} async _resetDateToClearDetails(newDate){ if(!this.dateField)return; if(!newDate||!isValidDateString(newDate))return; if(this._lastResetDateForDetail===newDate){log(`⏭️ [DATE-RESET] Skipping date reset for ${this.type} because date "${newDate}" already reset recently`,'DEBUG',this.type);return;} const wasResetInProgress=this._dateResetInProgress; log(`🧹 [DATE-RESET] Details populated, clearing date temporarily to reset state for ${this.type} (date changed to ${newDate})`,'INFO',this.type); const wasSuppressEmpty=this._suppressEmptyDuringRefresh; this._suppressEmptyDuringRefresh=true; if(!wasResetInProgress){_dateResetInProgress=true;this._suppressNewListRequests=true;this._dateResetInProgress=true;this._suppressDateChanges=true;this._suppressIntervalChecks=true;if(window.RequestQueue){window.RequestQueue.clearListRequestsForLoader(this.type);window.RequestQueue.abortAllForLoader(this.type);window.RequestQueue.clear(true);log(`🧹 [RESET] Cleared request queues for ${this.type} only`,'INFO',this.type);}log(`🔒 [DATE-RESET] Dedupe maps preserved during temporary date clear`,'DEBUG',this.type);}else log(`⏭️ [DATE-RESET] Reset already in progress (flags already set) – skipping flag setup but still toggling date`,'DEBUG',this.type); const oldValue=this.dateField.value; const wasProgrammatic=window.isProgrammaticUpdate; window.isProgrammaticUpdate=true; this.dateField.value='';this._ignoreNextChange=true;this.dateField.dispatchEvent(new Event('change',{bubbles:true}));this.dateField.dispatchEvent(new Event('input',{bubbles:true})); await new Promise(r=>setTimeout(r,CONFIG.timing.dateResetClearDelay)); this.dateField.value=newDate;this.dateField.dispatchEvent(new Event('change',{bubbles:true}));this.dateField.dispatchEvent(new Event('input',{bubbles:true})); window.isProgrammaticUpdate=wasProgrammatic; await new Promise(r=>setTimeout(r,CONFIG.timing.dateResetRestoreDelay)); this._lastResetDateForDetail=newDate;this._dateEmpty=false;this._lastKnownDateValue=newDate;this._suppressEmptyDuringRefresh=wasSuppressEmpty; _dateResetInProgress=false;this._dateResetInProgress=false;this._suppressNewListRequests=false;this._suppressDateChanges=false;this._suppressIntervalChecks=false;this._ignoreNextChange=false; log(`🛡️ [DATE-RESET] Date toggled to "${newDate}" for ${this.type}, _dateEmpty reset to false, suppression flags cleared`,'INFO',this.type); } _startLoading(forceRefresh=false,bypassDuplicateCheck=false){ const hasPendingInQueue=window.RequestQueue?.queue.some(item=>item.loader===this&&item.requestType==='list'&&item.requestDate===this.getDateValue()); if(this._listRequestPending&&!this._listRequestInFlight&&!hasPendingInQueue){log(`🔄 [START] Resetting stale _listRequestPending for ${this.type} (no matching request in queue)`,'DEBUG',this.type);this._listRequestPending=false;this._pendingListRequestDate=null;clearListRequestPending(this,this.getDateValue());} if(this._listRequestProcessing&&!this._listRequestInFlight&&!hasPendingInQueue){log(`🔄 [START] Resetting stale _listRequestProcessing for ${this.type} (no matching request in queue)`,'DEBUG',this.type);this._listRequestProcessing=false;RequestQueue._listRequestProcessing=false;} if(this._listRequestInFlight&&!hasPendingInQueue&&!this.active){log(`🔄 [START] Resetting stale _listRequestInFlight for ${this.type} (no matching request in queue)`,'DEBUG',this.type);this._listRequestInFlight=false;clearListRequestPending(this,this.getDateValue());} if(!forceRefresh&&(this._listRequestPending||this._listRequestInFlight||isListRequestPending(this,this.getDateValue()))){log(`⏸️ [START] Loading already pending (list request in progress), skipping for ${this.type}`,'DEBUG',this.type);_clearGlobalLoadingFlag('start:alreadyPending');return;} const currentDate=this.getDateValue(); if(this.isLoaded&&this._lastLoadedDate===currentDate&&this.hasAnyNonOverlayContent()){log(`✅ [START] Already loaded for ${this.type} with date ${currentDate}, skipping`,'DEBUG',this.type);_clearGlobalLoadingFlag('start:alreadyLoaded');return;} if(this.active){log(`⏸️ [START] Loader is active, skipping for ${this.type}`,'DEBUG',this.type);_clearGlobalLoadingFlag('start:active');return;} if(this.noFlightsConfirmed&&this._noFlightsDate===currentDate){log(`📭 [START] Resetting "No Flights" state for date ${currentDate} and forcing reload for ${this.type}`,'INFO',this.type);this._clearNoFlightsState();this.isLoaded=false;this._lastLoadedDate=null;forceRefresh=true;} this._listRequestPending=true; if(currentDate&&isListRequestPending(this,currentDate)){log(`⏸️ [START] Skipping because list request already pending for ${this.type} date ${currentDate} (global dedupe)`,'DEBUG',this.type);this._listRequestPending=false;_clearGlobalLoadingFlag('start:pendingDedupe');return;} markListRequestPending(this,currentDate); if(this._dateChangePending){log(`⏸️ [START] Skipping because date change is pending for ${this.type}`,'DEBUG',this.type);this._listRequestPending=false;clearListRequestPending(this,currentDate);_clearGlobalLoadingFlag('start:dateChangePending');return;} if(this._listRequestProcessing||RequestQueue._listRequestProcessing){log(`⏸️ [START] Skipping because a list request is already processing for ${this.type}`,'DEBUG',this.type);this._listRequestPending=false;clearListRequestPending(this,currentDate);_clearGlobalLoadingFlag('start:processing');return;} if(this._blockForcedRefreshUntil{this._retryAfterSkipTimer=null;if(!this.active&&!this.isLoaded&&this.hasValidDate()&&!_globalLoadingActive&&!this.permanentError&&!this._isTerminalForDate(this.getDateValue())){log(`🔄 [RETRY-AFTER-SKIP] Attempt ${attemptsForDate} date=${dateKey} for ${this.type}`,'INFO',this.type);this.startLoading(true);}else if(attemptsForDate{this._retryAfterSkipTimer=null;if(!this.active&&!this.isLoaded&&this.hasValidDate()&&!_globalLoadingActive&&!this.permanentError&&!this._isTerminalForDate(this.getDateValue())){log(`🔄 [RETRY-AFTER-SKIP] Attempt ${attemptsForDate+1} date=${dateKey} for ${this.type}`,'INFO',this.type);this.startLoading(true);}else{this._retryAfterSkipAttemptsByDate.set(dateKey,attemptsForDate+1);if(attemptsForDate+1{this._retryAfterSkipTimer=null;this.startLoading(true);},CONFIG.timing.retryAfterSkipDelay);}else log(`⏸️ [RETRY-AFTER-SKIP] Max retry attempts (${CONFIG.timing.maxRetryAfterSkipAttempts}) reached for ${this.type}`,'WARN',this.type);}},CONFIG.timing.retryAfterSkipDelay);}else log(`⏸️ [RETRY-AFTER-SKIP] Max retry attempts (${CONFIG.timing.maxRetryAfterSkipAttempts}) reached for ${this.type}`,'WARN',this.type);},CONFIG.timing.retryAfterSkipDelay); return; } if(_globalNetworkRestoreInProgress){log(`🌐 [START] Global network restore in progress – skipping for ${this.type}`,'INFO',this.type);this._listRequestPending=false;clearListRequestPending(this,currentDate);_clearGlobalLoadingFlag('start:netRestore');return;} const now=Date.now(); if(!forceRefresh&&this.isLoaded&&(now-this._lastSuccessfulLoadTime){this._pendingListRequestDate=null;this._listRequestPending=false;clearListRequestPending(this,currentDate);},10000); const loadKey=`${currentDate||'empty'}_${forceRefresh}`; if(this.pendingLoadId===loadKey&&this.active&&(Date.now()-(this._lastLoadStart||0))>100){log(`⏸️ Duplicate load request ignored for ${this.type} (loadKey=${loadKey})`,'DEBUG',this.type);this._listRequestPending=false;clearListRequestPending(this,currentDate);_clearGlobalLoadingFlag('start:dupLoadKey');return;} this.pendingLoadId=loadKey;this._lastLoadStart=Date.now(); log(`🔑 [PENDING-ID] Set: ${loadKey} (clears at ${Date.now()+2000})`,'DEBUG',this.type); setTimeout(()=>{if(this.pendingLoadId===loadKey){log(`🗑️ [PENDING-ID] Cleared: ${loadKey} (reason=auto-timeout)`,'DEBUG',this.type);this.pendingLoadId=null;}},2000); if(this.suppressDateEmpty&&!currentDate){log(`⚠️ suppressDateEmpty active but date is empty for ${this.type}. Clearing flag.`,'WARN',this.type);this.suppressDateEmpty=false;} if(window.loadingLocks[this.type]&&!this.active){log(`🔓 Emergency lock cleared for ${this.type} (lock true but inactive)`,'WARN',this.type);window.loadingLocks[this.type]=false;} if(!isOnline){if(this.isLoaded){log(`🌐 Network offline but flights already loaded for ${this.type}, skipping overlay`,'INFO',this.type);this.pendingLoadId=null;this._listRequestPending=false;clearListRequestPending(this,currentDate);_clearGlobalLoadingFlag('start:offlineLoaded');return;}if(this.systemCooldownOverlay&&this.systemCooldownOverlay.style.display!=='none'){log(`⏸️ Network offline but cooldown active – not showing network overlay for ${this.type}`,'INFO',this.type);this.pendingLoadId=null;this._listRequestPending=false;clearListRequestPending(this,currentDate);_clearGlobalLoadingFlag('start:offlineCooldown');return;}if(this._isVerificationStage()){log(`🌐 Network offline but verification stage in progress – skipping network overlay for ${this.type}`,'INFO',this.type);this.pendingLoadId=null;this._listRequestPending=false;clearListRequestPending(this,currentDate);_clearGlobalLoadingFlag('start:offlineVerification');return;}log(`🌐 Network offline for ${this.type}, queuing date change`,'INFO',this.type);this.offlinePendingDate=currentDate;this.showNetworkOffline();this.pendingLoadId=null;this._listRequestPending=false;clearListRequestPending(this,currentDate);_clearGlobalLoadingFlag('start:offline');return;} if((this.active||window.loadingLocks[this.type])&&!forceRefresh){log(`⏸️ Loading already active or locked (lock=${window.loadingLocks[this.type]}), skipping`,'DEBUG',this.type);this.pendingLoadId=null;this._listRequestPending=false;clearListRequestPending(this,currentDate);_clearGlobalLoadingFlag('start:alreadyActive');return;} if(this.permanentError){ // V379: escalate permanently — do NOT auto-retry after N cycles if(this._permanentCycles>=CONFIG.timing.maxPermanentCycles){ log(`🚫 [PERM-ERROR] ${this.type}: ${this._permanentCycles} permanent cycles reached – manual retry required`,'WARN',this.type); this._listRequestPending=false;clearListRequestPending(this,currentDate);_clearGlobalLoadingFlag('start:permManualRequired');return; } log(`🚫 Loading blocked: permanent error set for ${this.type}`,'WARN',this.type);this._listRequestPending=false;clearListRequestPending(this,currentDate);_clearGlobalLoadingFlag('start:permanentError');return; } this._listRequestInFlight=true; if(this._listRequestInFlightTimer)clearTimeout(this._listRequestInFlightTimer); this._listRequestInFlightTimer=setTimeout(()=>{if(this._listRequestInFlight){log(`⚠️ [LIST] List request in flight for ${this.type} exceeded 10s, resetting flag`,'WARN',this.type);this._listRequestInFlight=false;this._listRequestPending=false;clearListRequestPending(this,currentDate);_clearGlobalLoadingFlag('start:inflightTimeout');}},CONFIG.timing.listRequestInFlightTimeout); this._maybeStaggerOtherLoader(forceRefresh); this._executeLoading(forceRefresh); } _maybeStaggerOtherLoader(forceRefresh){ const otherLoader=this.type==='departure'?window.returnLoader:window.departureLoader; if(!otherLoader||forceRefresh)return; const now=Date.now(); if(now-_lastGlobalListRequestTime{if(otherLoader.hasValidDate()&&!otherLoader.isLoaded&&!otherLoader.noFlightsConfirmed&&!otherLoader.active&&!otherLoader._listRequestInFlight&&!otherLoader.permanentError&&!otherLoader._listRequestProcessing&&!RequestQueue._listRequestProcessing){otherLoader.startLoading(true);}},delay);} } _executeLoading(forceRefresh=false){ const currentGen=this._generation; if(this.cooldownBlocked&&this.isLoaded){log(`⏸️ Loading blocked: in cooldown (display preserved)`,'WARN',this.type);this._listRequestPending=false;_clearGlobalLoadingFlag('execute:cooldownBlocked');return;} if(window.loadingLocks[this.type]&&!this.active){log(`🔓 Pre-execution lock cleanup for ${this.type}`,'WARN',this.type);window.loadingLocks[this.type]=false;} if(window.loadingLocks[this.type]||this.permanentError){log(`🚫 Loading blocked: lock=${window.loadingLocks[this.type]}, permError=${this.permanentError}`,'DEBUG',this.type);this._listRequestInFlight=false;this._listRequestPending=false;_clearGlobalLoadingFlag('execute:locked');return;} if(this.refreshInProgress){setTimeout(()=>this.startLoading(forceRefresh),300);this._listRequestPending=false;_clearGlobalLoadingFlag('execute:refreshInProgress');return;} const currentDate=this.getDateValue(); if(!currentDate&&this.lastDateValue)this.setDateValue(this.lastDateValue); const safeDate=this.getDateValue(); if(!safeDate){log(`🚫 _executeLoading aborted: date is empty for ${this.type}`,'WARN',this.type);this._releaseLock();this.stopLoading(true);this.showDateEmpty();this._listRequestInFlight=false;this._listRequestPending=false;_clearGlobalLoadingFlag('execute:noDate');return;} if(safeDate!==this.lastDateValue){log(`📅 Date changed for ${this.type}: ${this.lastDateValue} → ${safeDate}`,'INFO',this.type);this._resetForDateChange();this.lastDateValue=safeDate;dateChangePending[this.type]=true;if(dateChangeTimer)clearTimeout(dateChangeTimer);dateChangeTimer=setTimeout(()=>{dateChangePending.departure=false;dateChangePending.return=false;},CONFIG.timing.dateChangePendingWindow);if(window.RequestQueue){window.RequestQueue.abortAllForLoader(this.type);window.RequestQueue.clearListRequestsForLoader(this.type);if(this._pendingListRequestDate)window.RequestQueue.abortPendingListRequestForDate(this,this._pendingListRequestDate);}this._listRequestInFlight=false;this._listRequestPending=false;_clearGlobalLoadingFlag('execute:dateChanged');return;} if(this.isLoaded&&!forceRefresh){log(`✅ Already loaded for ${this.type}, skipping`,'DEBUG',this.type);this._listRequestInFlight=false;this._listRequestPending=false;_clearGlobalLoadingFlag('execute:alreadyLoaded');return;} if(this._suppressDateChanges){log(`🔓 [EXECUTE] Clearing _suppressDateChanges for ${this.type} – load is about to become active`,'INFO',this.type);this._suppressDateChanges=false;} if(this._suppressIntervalChecks){log(`🔓 [EXECUTE] Clearing _suppressIntervalChecks for ${this.type}`,'INFO',this.type);this._suppressIntervalChecks=false;} if(this._networkRestoreInProgress)this._networkRestoreInProgress=false; this.currentLoadId=Date.now();this.ajaxResponseReceived=false;this.firstDetectionDone=false;this.emptyResponseReceived=false;this.gracePeriodActive=false;this.noAjaxRetryCount=0;this.errorCooldownTriggered=false; this._clearAllTimers('executeLoading'); if(this.progressUpdateInterval)clearInterval(this.progressUpdateInterval); this._purgeStaleContent();this._markContainerFreshness(); window.loadingLocks[this.type]=true;_setGlobalLoadingFlag('executeLoading:'+this.type,this.type);this.active=true;this._lastActiveTime=Date.now(); if(this._suppressNewListRequests||this._dateResetInProgress){log(`🔓 [EXECUTE] Clearing _suppressNewListRequests and _dateResetInProgress for ${this.type} – load is now active`,'INFO',this.type);this._suppressNewListRequests=false;_dateResetInProgress=false;this._dateResetInProgress=false;} if(this._ignoreNextChange){log(`🔓 [EXECUTE] Resetting _ignoreNextChange to false for ${this.type}`,'INFO',this.type);this._ignoreNextChange=false;} this._processingDate=safeDate; this.startTime=Date.now();this.isLoaded=false;this.noFlightsConfirmed=false; this.showLoading(); this.progressUpdateInterval=setInterval(()=>this._updateProgress(),200); const totalFallbackTimeout=this.adaptiveBaseTimeout+this.adaptiveGracePeriod+CONFIG.timing.fallbackExtraMs; const isWeak=networkQuality==='weak'||networkQuality==='fair'; const cap=isWeak?CONFIG.timing.fallbackCapWeak:CONFIG.timing.fallbackCapFast; const hardTimeout=Math.min(totalFallbackTimeout,cap); log(`⏲️ [FALLBACK] Total fallback timeout configured: ${hardTimeout/1000}s for ${this.type} (base=${this.adaptiveBaseTimeout/1000}s + grace=${this.adaptiveGracePeriod/1000}s + extra=${CONFIG.timing.fallbackExtraMs/1000}s, capped at ${cap/1000}s)`,'INFO',this.type); this._progressFallbackTimer=setTimeout(()=>{if(this.active&&!this.ajaxResponseReceived&&this.loadingOverlay){log(`⏰ No AJAX response after ${hardTimeout/1000}s for ${this.type} – entering error cooldown`,'INFO',this.type);this.enterLocalErrorCooldown();}},hardTimeout); if(this._noFlightsFallbackTimer)clearTimeout(this._noFlightsFallbackTimer); this._noFlightsFallbackTimer=setTimeout(()=>{if(this.active&&!this.isLoaded&&!this.permanentError&&!this.ajaxResponseReceived&&!this.emptyResponseReceived&&!this.hasAnyNonOverlayContent()){log(`⏰ [NOFLIGHTS-FALLBACK] No AJAX after 10s and container empty → showing "No Flights" for ${this.type}`,'INFO',this.type);this.emptyResponseReceived=true;this.completeLoading(false,'NoFlights');}},CONFIG.timing.noFlightsFallbackDelay); if(this.detectionSettleTimer)clearTimeout(this.detectionSettleTimer); this.detectionSettleTimer=setTimeout(()=>{if(this.active&&!this.isLoaded&&!this.permanentError&&this.ajaxResponseReceived)this.detectionLoop(true);},CONFIG.timing.detectionSettleDelay); if(!this.gracePeriodActive&&this.active){this.gracePeriodActive=true;this.updateAdaptiveTimeouts();log(`⏳ Entering grace period for ${this.type} (${this.adaptiveGracePeriod}ms)`,'INFO',this.type);this._resetFallbackTimeout();if(this.postResponseEmptyCheckTimer)clearTimeout(this.postResponseEmptyCheckTimer);this.postResponseEmptyCheckTimer=setTimeout(()=>{if(this.active&&!this.isLoaded&&!this.permanentError&&this.ajaxResponseReceived){setTimeout(()=>{const hasFlights=this.detectFlights(true);const containerHasContent=this.hasAnyNonOverlayContent();if(!hasFlights&&!containerHasContent&&!this.emptyResponseReceived){log(`⚠️ Post-response empty check: container empty for ${this.type}`,'WARN',this.type);this.enterLocalErrorCooldown();}else if(!hasFlights&&containerHasContent){log(`📭 Post-response empty check: no flights but content exists for ${this.type} – forcing detection and finalization`,'WARN',this.type);this.firstDetectionDone=false;this.detectionLoop(true);}else if(!hasFlights&&this.emptyResponseReceived)this.showNoFlights();else if(!hasFlights)log(`⏸️ Post-response empty check: no flights, not forcing refresh`,'DEBUG',this.type);},500);}},this.adaptiveGracePeriod+CONFIG.timing.fallbackExtraMs);} if(!this.isLoaded&&!this.permanentError&&this.hasValidDate())setTimeout(()=>{if(this.active&&!this.isLoaded&&!this.permanentError&&this.hasValidDate())this._forcePluginRefresh();},100); } _updateProgress(){ if(!this.active||this.isLoaded||!this.loadingOverlay)return; // V379: touch heartbeat this._lastActiveTime=Date.now(); const elapsed=Date.now()-this.startTime;const totalTimeout=this.adaptiveBaseTimeout+this.adaptiveGracePeriod;let percent=Math.min(95,Math.floor((elapsed/totalTimeout)*100));let stage='';if(!this.ajaxResponseReceived){stage='Connecting';percent=Math.min(20,percent);}else if(!this.firstDetectionDone){stage='Loading flights';percent=Math.min(60,20+percent*0.4);}else if(this.doubleVerifyTimer){stage='Verifying';percent=Math.min(90,60+percent*0.3);}else{stage='Finalizing';percent=Math.min(100,90+percent*0.1);}percent=Math.min(100,percent);if(this.loadingOverlay){const fill=this.loadingOverlay.querySelector('.flight-progress-bar-fill');const percentSpan=this.loadingOverlay.querySelector('.flight-progress-percent');const stageSpan=this.loadingOverlay.querySelector('.flight-stage-text');if(fill)fill.style.width=`${percent}%`;if(percentSpan)percentSpan.textContent=`${percent}%`;if(stageSpan)stageSpan.textContent=stage;}if(percent%20===0&&percent!==this._lastLoggedProgress){this._lastLoggedProgress=percent;log(`📊 Progress: ${percent}% (${stage}) for ${this.type}`,'DEBUG',this.type);}} _queueForcedRefresh(){const runRefresh=()=>{if(this._forcedRefreshRunning)return;this._forcedRefreshRunning=true;this._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:false,force:false}).finally(()=>{this._forcedRefreshRunning=false;if(this._forcedRefreshQueue){const next=this._forcedRefreshQueue;this._forcedRefreshQueue=null;runRefresh.call(this);}});};if(this._forcedRefreshRunning){this._forcedRefreshQueue=true;log(`⏸️ [FR] Forced refresh already running for ${this.type}, queuing another`,'DEBUG',this.type);return;}runRefresh.call(this);} _cleanupForcedRefresh(){this.refreshInProgress=false;window.globalRefreshInProgress=false;this.forcedRefreshTriggered=false;this.forcedRefreshAttempted=false;this.lastForcedRefreshTime=0;if(this.refreshSafetyTimer)clearTimeout(this.refreshSafetyTimer);this.refreshSafetyTimer=null;if(this.fallbackAjaxTimer)clearTimeout(this.fallbackAjaxTimer);this._releaseLock();this._refreshRetryCount=0;if(this.detectionFallbackTimer)clearTimeout(this.detectionFallbackTimer);this.detectionFallbackTimer=null;this._listRequestInFlight=false;_clearGlobalLoadingFlag('cleanupForcedRefresh:'+this.type);} _postRefreshEmptyCheck(){if(!this.active&&!this.refreshInProgress)return;const responseAge=Date.now()-this.lastAjaxResponseTime;if(window.loaderState?.[this.type]?.ajaxResponseReceived&&responseAge<15000)return;const hasFlights=this.detectFlights(true);const containerHasContent=this.hasAnyNonOverlayContent();if(!hasFlights&&!containerHasContent){log(`⚠️ [FR] Post-refresh empty check: container empty for ${this.type}`,'WARN',this.type);this.enterLocalErrorCooldown();}else if(!hasFlights&&containerHasContent){log(`📭 [FR] Post-refresh empty check: no flights but content exists for ${this.type} – forcing detection`,'WARN',this.type);this.firstDetectionDone=false;this.detectionLoop(true);}else if(!hasFlights)log(`⏸️ [FR] Post-refresh empty check: no flights, not forcing refresh`,'DEBUG',this.type);} enterLocalErrorCooldown(){ this.pendingLoadId=null; log(`🗑️ [PENDING-ID] Cleared: (reason=enterLocalErrorCooldown) for ${this.type}`,'DEBUG',this.type); if(!isOnline){log(`📡 Skipping error cooldown: offline for ${this.type}`,'DEBUG',this.type);this.showNetworkOffline();return;} if(this.localErrorCooldown.active)return; const levels=CONFIG.flightLoader.errorCooldownLevels; const cycle=this.localErrorCooldown.cycle; // V379: cap instead of modulo — no wrap-around const levelIdx=Math.min(cycle,levels.length-1); const duration=levels[levelIdx]; this.localErrorCooldown.active=true;this.localErrorCooldown.remaining=Math.ceil(duration/1000);this.localErrorCooldown.cycle++;this.localErrorCooldown.attempts++; log(`🚨 [PERM-ERROR] permanentError cooldown for ${this.type} (cycle=${cycle} levelIdx=${levelIdx}/${levels.length-1} → ${duration/1000}s cooldown, capped)`,'WARN',this.type); log(`⏳ [PERM-ERROR] Cooldown ${this.localErrorCooldown.remaining}s, next escalation level = ${this.localErrorCooldown.cycle}`,'INFO',this.type); this.showError(`Server error. Auto-retry in ${this.localErrorCooldown.remaining}s.`,this.localErrorCooldown.remaining,false); if(this.localErrorCooldown.timer)clearInterval(this.localErrorCooldown.timer); this.localErrorCooldown.timer=setInterval(()=>{this.localErrorCooldown.remaining--;this.updateErrorCountdown(this.localErrorCooldown.remaining);if(this.localErrorCooldown.remaining<=0){clearInterval(this.localErrorCooldown.timer);this.localErrorCooldown.timer=null;this.localErrorCooldown.active=false;if(!this.hasValidDate()){this.hideError();this.showDateEmpty();return;}this.hideError();this._resetAllForcedRefreshFlags();this.active=false;this.permanentError=false;this.errorCooldownTriggered=false;log(`🔄 Local cooldown expired for ${this.type}, restarting...`,'INFO',this.type);this._releaseLock();this._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true});setTimeout(()=>this._executeLoading(true),CONFIG.timing.forcedRefreshCooldownDelay);}},1000); } handleServerError(status){ if(this.permanentError){ log(`🛡️ [SERVER] Ignoring ${status} for ${this.type} – permanentError already set (attempts=${this._consecutive5xx})`,'DEBUG',this.type); return; } this.pendingLoadId=null; log(`🗑️ [PENDING-ID] Cleared: (reason=handleServerError:${status}) for ${this.type}`,'DEBUG',this.type); log(`⚠️ Server error ${status} for ${this.type}`,'INFO',this.type); if(this._fastNoFlightsTimer){clearTimeout(this._fastNoFlightsTimer);this._fastNoFlightsTimer=null;} if(this._noFlightsFallbackTimer){clearTimeout(this._noFlightsFallbackTimer);this._noFlightsFallbackTimer=null;} if(this._tertiaryNoFlightsTimer){clearTimeout(this._tertiaryNoFlightsTimer);this._tertiaryNoFlightsTimer=null;} if(this._secondaryNoFlightsTimer){clearTimeout(this._secondaryNoFlightsTimer);this._secondaryNoFlightsTimer=null;} this._resetAllForcedRefreshFlags();this._listRequestInFlight=false;this._listRequestPending=false;this._pendingListRequestDate=null; this._recentServerError=true;this._recentServerErrorTime=Date.now();this._recentErrSetCount=(this._recentErrSetCount||0)+1;this._recentErrSetAt=this._recentServerErrorTime; log(`🚨 [RECENT-ERR] _recentServerError = true for ${this.type} (count: ${this._recentErrSetCount})`,'DEBUG',this.type); if(status===429){ this.lastResponseWas429=true;this.consecutive429Count++;log(`🚫 [429] 429 rate limit detected for ${this.type} (count: ${this.consecutive429Count})`,'WARN',this.type); if(this.consecutive429Count>=CONFIG.flightLoader.max429Retries){this.permanentError=true;this._permanentCycles++;this.showError('Rate limit exceeded. Please wait.',30,true);this.stopLoading(true);_clearGlobalLoadingFlag('serverError:429max');return;} if(!GlobalCooldownManager.canMakeRequest()){log(`⏸️ [429] 429 but global cooldown already active, skipping further actions`,'DEBUG',this.type);return;} GlobalCooldownManager.triggerCooldown(`${this.type} server error 429`,this.type,null,false); return; } const isTransient=[502,503,504,507,508,520,521,522,523,524].includes(status); const isPermanent500=status===500||status===501; if(isTransient){ if(this._transient5xxTimer){log(`🛡️ [SERVER] Ignoring ${status} for ${this.type} – transient retry already scheduled`,'DEBUG',this.type);return;} this._consecutive5xx=(this._consecutive5xx||0)+1; log(`🌩️ [SERVER] 5xx (${status}) for ${this.type} – transient=true attempt=${this._consecutive5xx}/${CONFIG.timing.maxConsecutive5xx}`,'WARN',this.type); if(this._consecutive5xx>=CONFIG.timing.maxConsecutive5xx){ this.permanentError=true; this._permanentCycles++; log(`🚨 [PERM-ERROR] ${this.type}: permanent after ${this._consecutive5xx} consecutive 5xx (permanentCycles=${this._permanentCycles})`,'ERROR',this.type); this.stopLoading(true); _clearGlobalLoadingFlag(`serverError:transientPermanent:${status}`); this.enterLocalErrorCooldown(); return; } const level=Math.min(this._consecutive5xx-1,CONFIG.timing.serverErrorBackoffLevels.length-1); const delay=CONFIG.timing.serverErrorBackoffLevels[level]; log(`⏲️ [SERVER] Transient 5xx retry #${this._consecutive5xx} in ${delay/1000}s for ${this.type} (linear→exponential backoff: ${CONFIG.timing.serverErrorBackoffLevels.map(x=>x/1000+'s').join(' → ')})`,'WARN',this.type); this.showError(`Server error (${status}). Retrying in ${Math.round(delay/1000)}s...`,Math.round(delay/1000),false); if(this._transient5xxTimer){clearTimeout(this._transient5xxTimer);this._transient5xxTimer=null;} this._transient5xxTimer=setTimeout(()=>{this._transient5xxTimer=null;this._transient5xxTimerSetAt=0;this.handleTransientRetryFire();},delay); this._transient5xxTimerSetAt=Date.now(); log(`🌩️ [SERVER] transient5xxTimer set for ${this.type} (fires in ${delay}ms, setAt=${this._transient5xxTimerSetAt})`,'DEBUG',this.type); return; } if(isPermanent500){ this.permanentError=true; this._permanentCycles++; log(`🚨 [PERM-ERROR] ${this.type}: permanent 5xx (${status}) – permanentCycles=${this._permanentCycles}`,'ERROR',this.type); this.stopLoading(true); _clearGlobalLoadingFlag(`serverError:permanent500:${status}`); this.enterLocalErrorCooldown(); return; } this.permanentError=true;this._permanentCycles++; log(`🚨 [PERM-ERROR] ${this.type}: unhandled server error (${status}) – permanentCycles=${this._permanentCycles}`,'ERROR',this.type); this.showError(`Failed to load flights (${status}). Please try again.`,5,true);this.stopLoading(true);_clearGlobalLoadingFlag(`serverError:fallback:${status}`); } handleTransientRetryFire(){ if(!this._transient5xxTimer&&!this.permanentError&&this.hasValidDate()&&isOnline){ if(this._refreshLock||this._pluginRefreshInProgress){ log(`⏸️ [SERVER] Transient retry #${this._consecutive5xx} deferred for ${this.type} – refresh lock held`,'DEBUG',this.type); const level=Math.min(this._consecutive5xx-1,CONFIG.timing.serverErrorBackoffLevels.length-1); const delay=CONFIG.timing.serverErrorBackoffLevels[Math.min(level+1,CONFIG.timing.serverErrorBackoffLevels.length-1)]; this._transient5xxTimer=setTimeout(()=>{this._transient5xxTimer=null;this._transient5xxTimerSetAt=0;this.handleTransientRetryFire();},delay); this._transient5xxTimerSetAt=Date.now(); return; } log(`🔄 [SERVER] Executing transient 5xx retry #${this._consecutive5xx} for ${this.type}`,'INFO',this.type); this.hideError(); this._softReset(`transient5xx:${this._consecutive5xx}`); this._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true}); setTimeout(()=>this._executeLoading(true),CONFIG.timing.pluginRefreshDelay); } } handleInvalidJson(){ this.pendingLoadId=null; log(`🗑️ [PENDING-ID] Cleared: (reason=handleInvalidJson) for ${this.type}`,'DEBUG',this.type); this.consecutiveInvalidResponses++;this._resetAllForcedRefreshFlags();this._listRequestInFlight=false;this._listRequestPending=false;this._pendingListRequestDate=null; if(this._fastNoFlightsTimer)clearTimeout(this._fastNoFlightsTimer); if(this._noFlightsFallbackTimer)clearTimeout(this._noFlightsFallbackTimer); if(this._tertiaryNoFlightsTimer)clearTimeout(this._tertiaryNoFlightsTimer); if(this._secondaryNoFlightsTimer)clearTimeout(this._secondaryNoFlightsTimer); this._recentServerError=true;this._recentServerErrorTime=Date.now();this._recentErrSetCount=(this._recentErrSetCount||0)+1; log(`🚨 [RECENT-ERR] _recentServerError = true for ${this.type} (count: ${this._recentErrSetCount}) (reason=invalidJson)`,'DEBUG',this.type); if(this.consecutiveInvalidResponses>=CONFIG.flightLoader.maxInvalidJsonRetries){log(`🚫 [JSON] Too many invalid JSON responses (${this.consecutiveInvalidResponses}), falling back to error overlay for ${this.type}`,'ERROR',this.type);this.permanentError=true;this._permanentCycles++;this.showError('Server error. Please try again.',5,true);return;} const delay=this._invalidJsonBackoffMs;this._invalidJsonBackoffMs=Math.min(delay*2,60000);this._invalidJsonRetryCount++; log(`⚠️ [JSON] Invalid JSON response (attempt ${this._invalidJsonRetryCount}), retrying in ${delay/1000}s for ${this.type}`,'WARN',this.type); setTimeout(()=>{if(this.hasValidDate()&&!this.permanentError&&isOnline)this.startLoading(true);},delay); _clearGlobalLoadingFlag('handleInvalidJson'); } handleNetworkError(){ this.pendingLoadId=null; log(`🗑️ [PENDING-ID] Cleared: (reason=handleNetworkError) for ${this.type}`,'DEBUG',this.type); this._listRequestInFlight=false;this._listRequestPending=false;this._pendingListRequestDate=null; if(!isOnline)this.showNetworkOffline();else this.enterLocalErrorCooldown(); _clearGlobalLoadingFlag('handleNetworkError:'+this.type); } onAjaxResponse(isEmptyValid=false,isInvalidJson=false,responsePreview=''){ if(!this.hasValidDate()){log(`📡 [Stale] Ignoring AJAX response for ${this.type}: date is now empty`,'DEBUG',this.type);return;} if(!this.active&&this.isLoaded){log(`📡 [Stale] Ignoring AJAX response for ${this.type}: not active and already loaded`,'DEBUG',this.type);return;} if(this.currentLoadId!==parseInt(this.container?.dataset?.flightLoadId)){if(VERBOSE_LOGGING)log(`🔄 Ignoring AJAX response for stale loadId (response for ${this.currentLoadId} but current is ${this.container?.dataset?.flightLoadId})`,'WARN',this.type);return;} this.lastAjaxResponseTime=Date.now(); if(isEmptyValid){this.emptyResponseReceived=true;this._serverConfirmedEmpty=true;} this.ajaxResponseReceived=true; if(this.ajaxResponseTimer)clearTimeout(this.ajaxResponseTimer); if(this.fallbackAjaxTimer)clearTimeout(this.fallbackAjaxTimer); this.consecutiveNoAjaxCount=0; if(responsePreview)log(`📄 AJAX response preview (${this.type}): ${responsePreview.substring(0,200)}`,'DEBUG',this.type); if(isEmptyValid&&!this.isLoaded&&!this.noFlightsConfirmed){ if(this._recentServerError&&(Date.now()-this._recentServerErrorTime)0){log(`✅ [SERVER] _consecutive5xx reset from ${this._consecutive5xx} to 0 for ${this.type} (200 OK) (permanentCycles preserved=${this._permanentCycles})`,'INFO',this.type);this._consecutive5xx=0;} if(this._transient5xxTimer){clearTimeout(this._transient5xxTimer);this._transient5xxTimer=null;this._transient5xxTimerSetAt=0;log(`⏹️ [SERVER] Cancelling transient 5xx retry timer for ${this.type} (reason=200OK)`,'DEBUG',this.type);} if(!this._recentServerError||(Date.now()-this._recentServerErrorTime)>CONFIG.timing.recentErrorWindowMs){ if(this._recentServerError){log(`✅ [RECENT-ERR] cleared for ${this.type} after ${Date.now()-this._recentErrSetAt}ms (count was ${this._recentErrSetCount})`,'DEBUG',this.type);} this._recentServerError=false;this._recentServerErrorTime=0; }else if(!isEmptyValid){ if(this._recentServerError){log(`✅ [RECENT-ERR] cleared for ${this.type} after ${Date.now()-this._recentErrSetAt}ms (count was ${this._recentErrSetCount}) (reason=non-empty success)`,'DEBUG',this.type);} this._recentServerError=false;this._recentServerErrorTime=0; } GlobalCooldownManager.resetCycle(); } log(`📡 Ajax response received for ${this.type} (current loadId: ${this.currentLoadId})`,'DEBUG',this.type); if(this._fastNoFlightsTimer)clearTimeout(this._fastNoFlightsTimer); if(!this.isLoaded&&!this.noFlightsConfirmed){ this._fastNoFlightsTimer=setTimeout(()=>{ if(this._pluginRefreshInProgress||_globalRefreshInProgress){log(`⏸️ [FAST-NOFLIGHTS] Skipped because refresh in progress for ${this.type}`,'DEBUG',this.type);return;} if(!this.active||this.isLoaded||this.noFlightsConfirmed){log(`⏸️ [FAST-NOFLIGHTS] Skipped because loader no longer active/loaded/confirmed for ${this.type}`,'DEBUG',this.type);return;} const hasVisibleContent=this.hasAnyNonOverlayContent()&&(this.container.querySelector(`${this.radioSelector}:not([style*="display:none"])`)!==null||this.container.querySelector(CONFIG.selectors.flightCard+':not([style*="display:none"])')!==null); if(!hasVisibleContent){log(`⚡ [FAST-NOFLIGHTS] No visible content after ${CONFIG.timing.fastNoFlightsWaitMs/1000}s → showing No Flights for ${this.type}`,'INFO',this.type);if(!this.isLoaded&&!this.noFlightsConfirmed){this.emptyResponseReceived=true;this.completeLoading(false,'NoFlights');}}else{log(`✅ [FAST-NOFLIGHTS] Visible content found after ${CONFIG.timing.fastNoFlightsWaitMs/1000}s – continuing detection for ${this.type}`,'DEBUG',this.type);if(this.active&&!this.isLoaded)this.detectionLoop(true);} },CONFIG.timing.fastNoFlightsWaitMs); } if(this.detectionSettleTimer)clearTimeout(this.detectionSettleTimer); this.detectionSettleTimer=setTimeout(()=>{if(this.active&&!this.isLoaded&&!this.permanentError&&this.ajaxResponseReceived)this.detectionLoop(true);},CONFIG.timing.detectionSettleDelay); if(!this.gracePeriodActive&&this.active){ this.gracePeriodActive=true;this.updateAdaptiveTimeouts();log(`⏳ Entering grace period for ${this.type} (${this.adaptiveGracePeriod}ms)`,'INFO',this.type); this._resetFallbackTimeout(); if(this.postResponseEmptyCheckTimer)clearTimeout(this.postResponseEmptyCheckTimer); this.postResponseEmptyCheckTimer=setTimeout(()=>{ if(this.active&&!this.isLoaded&&!this.permanentError&&this.ajaxResponseReceived){ setTimeout(()=>{ const hasFlights=this.detectFlights(true); const containerHasContent=this.hasAnyNonOverlayContent(); if(!hasFlights&&!containerHasContent&&!this.emptyResponseReceived){log(`⚠️ Post-response empty check: container empty for ${this.type}`,'WARN',this.type);this.enterLocalErrorCooldown();} else if(!hasFlights&&containerHasContent){log(`📭 Post-response empty check: no flights but content exists for ${this.type} – forcing detection and finalization`,'WARN',this.type);this.firstDetectionDone=false;this.detectionLoop(true);} else if(!hasFlights&&this.emptyResponseReceived)this.showNoFlights(); else if(!hasFlights)log(`⏸️ Post-response empty check: no flights, not forcing refresh`,'DEBUG',this.type); },500); } },this.adaptiveGracePeriod+CONFIG.timing.fallbackExtraMs); } } detectionLoop(forceCheck=false){ if(!this.active||this.permanentError||this.noFlightsConfirmed||this.isEmptyState||this.isLoaded||this.errorCooldownTriggered)return; if(!this.ajaxResponseReceived)return; // V379: abort if date is empty if(!this.hasValidDate()){log(`🛑 [DETECT] Detection aborted for ${this.type} – date is empty`,'INFO',this.type);this.stopLoading(true);this.showDateEmpty();return;} const elapsed=Date.now()-this.startTime; if(!GlobalCooldownManager.canMakeRequest()){this.completeLoading(false,'GlobalCooldown');return;} let effectiveTimeout=this.adaptiveBaseTimeout; if(this.gracePeriodActive&&(Date.now()-this.lastAjaxResponseTime)effectiveTimeout){if(this.detectFlights(true))this.completeLoading(true,'Success');else{if(!GlobalCooldownManager.canMakeRequest())this.completeLoading(false,'GlobalCooldown');else{log(`📭 Detection timeout for ${this.type}`,'INFO',this.type);this.completeLoading(false,'NoFlights');}}return;} if(this.detectFlights(true)){ if(!this.firstDetectionDone){ this.firstDetectionDone=true;log(`🔍 First detection passed for ${this.type}, scheduling verification`,'DEBUG',this.type); if(this.loadingOverlay){const primarySpinner=this.loadingOverlay.querySelector('.flight-spinner-primary');const detectionSpinner=this.loadingOverlay.querySelector('.flight-spinner-detection');if(primarySpinner&&detectionSpinner){primarySpinner.style.display='none';detectionSpinner.style.display='block';log(`🔄 Switched to detection spinner for ${this.type}`,'INFO',this.type);}} if(this.detectionFallbackTimer)clearTimeout(this.detectionFallbackTimer); if(this.doubleVerifyTimer)clearTimeout(this.doubleVerifyTimer); this.doubleVerifyTimer=setTimeout(()=>{ if(this.active&&!this.isLoaded){ if(this.detectFlights(true)){log(`✅ Double verification passed for ${this.type}`,'DEBUG',this.type);if(this.active&&!this.isLoaded&&this.detectFlights(true)&&this.currentLoadId===parseInt(this.container?.dataset?.flightLoadId)){this._finalizeSuccess();return;}} const anyCards=Array.from(this.container.querySelectorAll(CONFIG.selectors.flightCard)).some(c=>{const cardLoadId=c.closest('[data-flight-load-id]')?.dataset.flightLoadId;if(!cardLoadId||parseInt(cardLoadId)!==this.currentLoadId)return false;const txt=c.textContent||'';return(txt.includes('$')||txt.includes('USD')||/\d{1,2}:\d{2}\s*(AM|PM)/i.test(txt))&&!c.classList.contains('flight-loading-final')&&c.offsetParent!==null;}); if(anyCards){log(`✅ Tolerant detection passed for ${this.type} (found visible flight cards)`,'INFO',this.type);this._finalizeSuccess();} else{log(`⚠️ Verification failed for ${this.type}`,'WARN',this.type);this.firstDetectionDone=false;if(this._verificationFailCount===undefined)this._verificationFailCount=0;this._verificationFailCount++;if(this._verificationFailCount>=3){log(`🔄 Forcing DOM refresh after ${this._verificationFailCount} failed verifications for ${this.type}`,'INFO',this.type);this._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true});this._verificationFailCount=0;}} }else{log(`⚠️ Double verification failed for ${this.type}`,'WARN',this.type);this.firstDetectionDone=false;} },CONFIG.timing.doubleVerificationInterval); }else this.firstDetectionDone=false; } } detectFlights(checkVisibility=false){ try{ if(!this.ajaxResponseReceived)return false; const currentDate=this.getDateValue(); if(!currentDate)return false; const allRadios=Array.from(this.container.querySelectorAll(this.radioSelector)); const visibleRadiosRaw=allRadios.filter(r=>r.offsetParent!==null); const radios=this.getCurrentDateRadios(); let visibleRadios=radios; if(checkVisibility)visibleRadios=radios.filter(r=>r.offsetParent!==null); const cards=Array.from(this.container.querySelectorAll(CONFIG.selectors.flightCard)).filter(c=>{try{const cardLoadId=c.closest('[data-flight-load-id]')?.dataset.flightLoadId;if(!cardLoadId||parseInt(cardLoadId)!==this.currentLoadId)return false;if(checkVisibility&&(c.offsetParent===null||c.getBoundingClientRect().width===0))return false;const txt=c.textContent||'';const hasFlight=(txt.includes('$')||txt.includes('USD')||/\d{1,2}:\d{2}\s*(AM|PM)/i.test(txt)||/[A-Z]{2}\d+/.test(txt))&&!txt.includes('loading')&&!txt.includes('Loading')&&c.getBoundingClientRect().height>10;const isOurUI=c.classList.contains('flight-loading-final')||c.classList.contains('flight-date-empty-final')||c.classList.contains('flight-no-flights-final')||c.classList.contains('flight-error-final')||c.classList.contains('flight-cooldown-overlay')||c.classList.contains('flight-network-overlay')||c.classList.contains('flight-restored-overlay');if(isOurUI)return false;if(c.dataset.flightDate&&c.dataset.flightDate!==currentDate)return false;if(currentDate&&txt.includes(currentDate))return true;return hasFlight;}catch(e){return false;}}); const visibleCards=cards.filter(c=>c.offsetParent!==null&&c.getBoundingClientRect().height>10); if(VERBOSE_LOGGING&&this._detectionLogCounter===undefined)this._detectionLogCounter=0; if(VERBOSE_LOGGING&&this._detectionLogCounter++%10===0)log(`🔍 Detection stats for ${this.type}: totalRadios=${allRadios.length}, visibleRadios=${visibleRadiosRaw.length}, matchedRadios=${radios.length}, matchedVisible=${visibleRadios.length}, cards=${cards.length}, visibleCards=${visibleCards.length}`,'DEBUG',this.type); if(visibleRadios.length){const allMatchDate=visibleRadios.every(r=>{const wrap=r.closest(CONFIG.selectors.radioWrap)||r.closest('label')||r.parentElement;return!wrap.dataset.date||wrap.dataset.date===currentDate;});if(!allMatchDate)return false;return true;} if(visibleCards.length>0){if(VERBOSE_LOGGING)log(`🃏 [DETECT] No radios but ${visibleCards.length} visible cards with flight data for ${this.type}`,'INFO',this.type);return true;} return false; }catch(e){return false;} } getCurrentDateRadios(){const currentDate=this.getDateValue();if(!currentDate)return[];return Array.from(this.container.querySelectorAll(this.radioSelector)).filter(r=>{try{const wrap=r.closest(CONFIG.selectors.radioWrap)||r.closest('label')||r.parentElement;if(!wrap)return false;const elementLoadId=wrap.closest('[data-flight-load-id]')?.dataset.flightLoadId;if(!elementLoadId||parseInt(elementLoadId)!==this.currentLoadId)return false;const wrapDate=wrap.dataset.date;if(wrapDate&&wrapDate!==currentDate)return false;const rect=wrap.getBoundingClientRect();const visible=rect.width>0&&rect.height>0;const style=getComputedStyle(wrap);const notHidden=style.display!=='none'&&style.visibility!=='hidden';return visible&&notHidden&&r.value&&r.value.trim()!==''&&r.offsetParent!==null;}catch(e){return false;}});} completeLoading(success,reason){ if(!this.active&&!this.refreshInProgress)return; this.pendingLoadId=null; log(`🗑️ [PENDING-ID] Cleared: (reason=completeLoading:${reason}) for ${this.type}`,'DEBUG',this.type); if(!this.hasValidDate()){log(`🚫 Date empty during completeLoading, aborting - showing empty state`,'INFO',this.type);this._clearAllTimers('completeLoading:noDate');if(this.progressUpdateInterval)clearInterval(this.progressUpdateInterval);if(this.interval){clearInterval(this.interval);this.interval=null;}this.active=false;this.errorCooldownTriggered=false;this._releaseLock();window.loadingLocks[this.type]=false;this.hideLoading();this.showDateEmpty();_clearGlobalLoadingFlag('completeLoading:noDate');this._ignoreNextChange=false;this._processingDate=null;this._suppressNewListRequests=false;this._dateResetInProgress=false;_dateResetInProgress=false;return;} this._clearAllTimers('completeLoading:'+reason); if(this.progressUpdateInterval)clearInterval(this.progressUpdateInterval); if(this.interval){clearInterval(this.interval);this.interval=null;} this.active=false;this.errorCooldownTriggered=false;this._releaseLock();window.loadingLocks[this.type]=false;this.hideLoading(); this._listRequestInFlight=false;this._pendingListRequestDate=null;this._listRequestPending=false;clearListRequestPending(this,this.getDateValue()); if(this._listRequestInFlightTimer)clearTimeout(this._listRequestInFlightTimer); this._suppressIntervalChecks=false;this._suppressNewListRequests=false;this._suppressDateChanges=false;this._networkRestoreInProgress=false; _dateResetInProgress=false;this._dateResetInProgress=false;this._ignoreNextChange=false;this._processingDate=null; _clearGlobalLoadingFlag('completeLoading:'+reason); if(success){ this.consecutiveNoAjaxCount=0;this._invalidJsonBackoffMs=CONFIG.flightLoader.invalidJsonBackoffBase;this._invalidJsonRetryCount=0;this._forcedRefreshBackoffMs=CONFIG.flightLoader.minForcedRefreshInterval;this._blockForcedRefreshUntil=0;this.resetCounters(); const dateKey=this.getDateValue();if(dateKey)this._forcedRefreshRetriesForDate.delete(dateKey); if(window.RequestQueue){window.RequestQueue.clearListRequestsForLoader(this.type);window.RequestQueue.abortAllForLoader(this.type);} setTimeout(()=>{if(this.detectFlights(true))this._finalizeSuccess();else this.completeLoading(false,'VerificationFailed');},200); }else{ log(`⚠️ Loading failed for ${this.type}: ${reason}`,'INFO',this.type); if(reason==='NoFlights'){ if(this.permanentError)this.showError('Failed to load flights due to server error. Please try again.',5,true); else{ if(this._recentServerError&&(Date.now()-this._recentServerErrorTime)0){log(`✅ [SERVER] _consecutive5xx reset from ${this._consecutive5xx} to 0 for ${this.type} (finalizeSuccess)`,'INFO',this.type);this._consecutive5xx=0;} if(this._transient5xxTimer){clearTimeout(this._transient5xxTimer);this._transient5xxTimer=null;this._transient5xxTimerSetAt=0;} log(`✅ [RECENT-ERR] _recentServerError cleared on successful finalization for ${this.type}`,'DEBUG',this.type); if(window.RequestQueue){window.RequestQueue.clearListRequestsForLoader('departure');window.RequestQueue.clearListRequestsForLoader('return');window.RequestQueue.abortAllForLoader('departure');window.RequestQueue.abortAllForLoader('return');} const dateKey=this.getDateValue(); if(dateKey){const key=`${this.type}:${dateKey}`;_globalListDedupeMap.delete(key);_listRequestActiveLock.delete(key);_activeListKeys.delete(key);_activeListPromises.delete(key);} GlobalCooldownManager.resetCycle(); this._forcedRefreshBackoffMs=CONFIG.flightLoader.minForcedRefreshInterval;this._blockForcedRefreshUntil=0; if(this.lockReleaseTimer){clearTimeout(this.lockReleaseTimer);this.lockReleaseTimer=null;} if(this.detectionFallbackTimer)clearTimeout(this.detectionFallbackTimer); this.forcedRefreshAttempted=false; if(dateKey)this._forcedRefreshRetriesForDate.delete(dateKey); if(otherLoader){if(otherLoader._secondaryNoFlightsTimer)clearTimeout(otherLoader._secondaryNoFlightsTimer);if(otherLoader._tertiaryNoFlightsTimer)clearTimeout(otherLoader._tertiaryNoFlightsTimer);otherLoader._serverConfirmedEmpty=false;} this.forceReflow();setTimeout(()=>this.forceReflow(),50); if(this._secondaryNoFlightsTimer)clearTimeout(this._secondaryNoFlightsTimer); if(this._tertiaryNoFlightsTimer)clearTimeout(this._tertiaryNoFlightsTimer); if(this._noFlightsFallbackTimer)clearTimeout(this._noFlightsFallbackTimer); if(this._fastNoFlightsTimer)clearTimeout(this._fastNoFlightsTimer); this._serverConfirmedEmpty=false; const finalizeStartTime=Date.now(); requestAnimationFrame(()=>{ let attempts=0; const maxAttempts=CONFIG.timing.finalizationMaxAttempts; const delays=[500,800,1200,1800,2500,3500,4500,5000]; const tryFinalize=()=>{ if(Date.now()-finalizeStartTime>CONFIG.timing.finalizationTimeoutMs){log(`⏰ Finalization timeout after 120 seconds for ${this.type}, giving up`,'ERROR',this.type);this.completeLoading(false,'VerificationFailed');return;} let hasFlights=this.detectFlights(true); if(!hasFlights&&attempts>2){const anyCards=Array.from(this.container.querySelectorAll(CONFIG.selectors.flightCard)).some(c=>{const cardLoadId=c.closest('[data-flight-load-id]')?.dataset.flightLoadId;if(!cardLoadId||parseInt(cardLoadId)!==this.currentLoadId)return false;const txt=c.textContent||'';const isVisible=c.offsetParent!==null&&c.getBoundingClientRect().height>10;return isVisible&&(txt.includes('$')||txt.includes('USD')||/\d{1,2}:\d{2}\s*(AM|PM)/i.test(txt));});if(anyCards)hasFlights=true;} if(!hasFlights||this.currentLoadId!==parseInt(this.container?.dataset?.flightLoadId)){ if(attempts{attempts++;tryFinalize();},delay);return;} else{log(`❌ Success finalization failed after ${maxAttempts} attempts for ${this.type}, aborting`,'ERROR',this.type);this.completeLoading(false,'VerificationFailed');return;} } this.isLoaded=true;this._lastLoadedDate=this.getDateValue();this._lastSuccessfulLoadTime=Date.now();this.permanentError=false;this._permanentCycles=0;this.forcedRefreshRetries=0;this.cooldownBlocked=false;this.consecutiveInvalidResponses=0;this.consecutive429Count=0;this.lastResponseWas429=false;this.consecutiveNoAjaxCount=0;this.noFlightsConfirmed=false;this._noFlightsDate=null;this.offlinePendingDate=null;this.noAjaxRetryCount=0;this._forcedRefreshBackoffMs=CONFIG.flightLoader.minForcedRefreshInterval;this._invalidJsonBackoffMs=CONFIG.flightLoader.invalidJsonBackoffBase;this._invalidJsonRetryCount=0;this._terminalStateLocked=false;this._terminalStateSetAt=0;this.hideAllOverlays(); if(this.progressUpdateInterval)clearInterval(this.progressUpdateInterval); const loadedDate=this.getDateValue(); if(loadedDate&&loadedDate!==this.lastDateValue){this.lastDateValue=loadedDate;log(`💾 lastDateValue synced from successful load for ${this.type}: "${loadedDate}"`,'DEBUG',this.type);} const currentDate=this.getDateValue(); Array.from(this.container.querySelectorAll(this.radioSelector)).forEach(r=>{const wrap=r.closest(CONFIG.selectors.radioWrap)||r.closest('label')||r.parentElement;if(wrap)wrap.dataset.date=currentDate;}); this.forceReflow(); if(this._secondaryNoFlightsTimer)clearTimeout(this._secondaryNoFlightsTimer); if(this._tertiaryNoFlightsTimer)clearTimeout(this._tertiaryNoFlightsTimer); if(this._fastNoFlightsTimer)clearTimeout(this._fastNoFlightsTimer); log(`✅ Loading completed for ${this.type} (loadId: ${this.currentLoadId}) – _permanentCycles reset to 0`,'INFO',this.type); this._showNotify(`${this.type==='departure'?'Departure':'Return'} flights loaded ✓`); setTimeout(()=>window.enhanceAllFlights?.(),100); if(this.type==='departure')_departureFlightsLoaded=true;else _returnFlightsLoaded=true; log(`🌍 GLOBAL: ${this.type} flights loaded.`,'INFO','system'); if(window.RequestQueue){window.RequestQueue.clearListRequestsForLoader('departure');window.RequestQueue.clearListRequestsForLoader('return');window.RequestQueue.abortAllForLoader('departure');window.RequestQueue.abortAllForLoader('return');} _clearGlobalLoadingFlag('finalizeSuccess:'+this.type); setTimeout(()=>{this._crossTriggerOtherLoader();},CONFIG.timing.crossTriggerRecheckDelay); }; tryFinalize(); }); }finally{ this._finalizing=false;_clearGlobalLoadingFlag('finalizeSuccess:finally:'+this.type); this._ignoreNextChange=false;this._processingDate=null;this._suppressNewListRequests=false;this._dateResetInProgress=false;_dateResetInProgress=false; } } _crossTriggerOtherLoader(){ const otherType=this.type==='departure'?'return':'departure'; const otherLoader=otherType==='departure'?window.departureLoader:window.returnLoader; if(!otherLoader){log(`⚠️ [CROSS] Other loader (${otherType}) not found`,'WARN',this.type);return;} const now=Date.now(); const perLoaderCooldown=_perLoaderCrossTriggerCooldown[otherType]||0; if(perLoaderCooldown>now){log(`⏸️ [CROSS] Per-loader cooldown active for ${otherType} (${Math.ceil((perLoaderCooldown-now)/1000)}s remaining) – skipping`,'DEBUG',this.type);return;} if(now-_globalCrossTriggerThrottleitem.requestType==='list'&&item.loader===otherLoader); if(hasPendingQueue){log(`⏸️ [CROSS] Target ${otherType} has pending list requests in queue – skipping force refresh`,'DEBUG',this.type);return;} if(otherLoader._refreshLock&&(Date.now()-otherLoader._lastPluginRefreshTime)>30000){log(`🔓 [CROSS] Target ${otherType} appears stuck – aborting its refresh lock`,'WARN',this.type);otherLoader._refreshLock=false;otherLoader._refreshScheduled=false;window.RequestQueue?.abortAllForLoader(otherType);window.RequestQueue?.clearListRequestsForLoader(otherType);otherLoader._listRequestInFlight=false;otherLoader._listRequestPending=false;otherLoader._pendingListRequestDate=null;otherLoader._pluginRefreshInProgress=false;_clearGlobalLoadingFlag('crossTrigger:unstick');} const targetFieldDate=otherLoader.getDateValue(); if(targetFieldDate&&isValidDateString(targetFieldDate)&&otherLoader._dateEmpty){log(`🔧 [CROSS] Target ${otherType} has date "${targetFieldDate}" but _dateEmpty is true – forcing sync and refresh`,'INFO',this.type);otherLoader._dateEmpty=false;otherLoader._lastKnownDateValue=targetFieldDate;otherLoader.hideDateEmpty();otherLoader._refreshLock=false;otherLoader._refreshScheduled=false;otherLoader._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true});_globalCrossTriggerThrottle=now;_perLoaderCrossTriggerCooldown[otherType]=now+CONFIG.timing.perLoaderCrossTriggerCooldownMs;return;} if(!otherLoader._isTerminalForDate(otherDate)&&!otherLoader.permanentError){log(`🔄 [CROSS] Forcing cross-trigger for ${otherType} because current has noFlights or empty`,'INFO',this.type);otherLoader._suppressNewListRequests=false;otherLoader._suppressIntervalChecks=false;otherLoader._suppressDateChanges=false;otherLoader._networkRestoreInProgress=false;otherLoader._dateResetInProgress=false;_dateResetInProgress=false;if(otherLoader._refreshDebounceTimer){clearTimeout(otherLoader._refreshDebounceTimer);otherLoader._refreshDebounceTimer=null;}otherLoader._refreshLock=false;otherLoader._refreshScheduled=false;otherLoader._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true});_globalCrossTriggerThrottle=now;_perLoaderCrossTriggerCooldown[otherType]=now+CONFIG.timing.perLoaderCrossTriggerCooldownMs;return;} else{log(`⏸️ [CROSS] ${otherType} is terminal or has permanent error – cannot force refresh`,'DEBUG',this.type);return;} } if(otherLoader.active||otherLoader._listRequestInFlight||otherLoader._listRequestPending||otherLoader._listRequestProcessing||otherLoader._pluginRefreshInProgress||otherLoader._refreshLock){log(`⏸️ [CROSS] Target loader (${otherType}) is active or has pending requests – skipping (non-force)`,'DEBUG',this.type);return;} if(otherLoader._refreshDebounceTimer||otherLoader._refreshScheduled){log(`⏸️ [CROSS] Target loader (${otherType}) has a scheduled refresh – skipping`,'DEBUG',this.type);return;} if(_globalNetworkRestoreInProgress){log(`🌐 [CROSS] Primary network restore in progress – skipping cross-trigger`,'DEBUG',this.type);return;} if(otherLoader.isLoaded&&otherLoader._lastLoadedDate===otherDate){log(`⏸️ [CROSS] Other loader (${otherType}) is already loaded for date ${otherDate} – skipping`,'DEBUG',this.type);return;} if(this._crossTriggerLock){log(`⏸️ [CROSS] Cross-trigger already in progress for ${this.type}, skipping`,'DEBUG',this.type);return;} this._crossTriggerLock=true;setTimeout(()=>{this._crossTriggerLock=false;},5000); _globalCrossTriggerThrottle=now;_perLoaderCrossTriggerCooldown[otherType]=now+CONFIG.timing.perLoaderCrossTriggerCooldownMs; const isTerminal=otherLoader._isTerminalForDate(otherDate); if(isTerminal){let reason='';if(otherLoader.isLoaded&&otherLoader._lastLoadedDate===otherDate)reason='loaded';else if(otherLoader.noFlightsOverlay&&otherLoader._noFlightsDate===otherDate)reason='overlay present';else if(otherLoader._serverConfirmedEmpty&&otherLoader._noFlightsDate===otherDate)reason='server confirmed empty';else if(otherLoader.noFlightsConfirmed&&otherLoader._noFlightsDate===otherDate&&otherLoader._terminalStateLocked)reason='locked';else if(otherLoader.noFlightsConfirmed&&otherLoader._noFlightsDate===otherDate&&(Date.now()-otherLoader._terminalStateSetAt){if(this.noFlightsOverlay&&otherLoader.noFlightsOverlay){log(`🔍 [CROSS] Re-checking both No Flights after cooldown for ${this.type}`,'DEBUG',this.type);if(this.detectFlights(true)){this._clearNoFlightsState();this._finalizeSuccess();}if(otherLoader.detectFlights(true)){otherLoader._clearNoFlightsState();otherLoader._finalizeSuccess();}}this._crossTriggerLock=false;},CONFIG.timing.noFlightsCrossCooldownMs+500);return;} if(otherLoader.active||otherLoader._listRequestInFlight||otherLoader._listRequestPending||otherLoader._listRequestProcessing||RequestQueue._listRequestProcessing){log(`⏸️ [CROSS] ${otherType} is already loading – waiting up to ${CONFIG.timing.crossTriggerWaitTimeout/1000}s for completion`,'INFO',this.type);const startTime=Date.now();const checkInterval=setInterval(()=>{if(!otherLoader.active&&!otherLoader._listRequestInFlight&&!otherLoader._listRequestPending&&!otherLoader._listRequestProcessing&&!RequestQueue._listRequestProcessing){clearInterval(checkInterval);this._crossTriggerLock=false;if(otherLoader.isLoaded&&otherLoader._lastLoadedDate===otherLoader.getDateValue())log(`✅ [CROSS] ${otherType} is loaded with correct date after waiting`,'INFO',this.type);else if(otherLoader.hasValidDate()&&!otherLoader.isLoaded&&!otherLoader.noFlightsConfirmed&&!otherLoader.permanentError){if(!otherLoader._isTerminalForDate(otherLoader.getDateValue())){log(`🔄 [CROSS] ${otherType} finished previous request but not loaded – triggering fresh load`,'INFO',this.type);otherLoader._suppressNewListRequests=false;otherLoader._suppressIntervalChecks=false;otherLoader._suppressDateChanges=false;otherLoader._networkRestoreInProgress=false;otherLoader._dateResetInProgress=false;_dateResetInProgress=false;if(otherLoader._refreshDebounceTimer){clearTimeout(otherLoader._refreshDebounceTimer);otherLoader._refreshDebounceTimer=null;}if(!otherLoader._refreshLock&&!otherLoader._refreshScheduled){otherLoader._refreshLock=false;otherLoader._refreshScheduled=false;otherLoader._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true});}}else log(`⏸️ [CROSS] ${otherType} finished but now terminal – skipping trigger`,'DEBUG',this.type);}else log(`⏸️ [CROSS] ${otherType} finished but conditions not met for trigger`,'DEBUG',this.type);}else if(Date.now()-startTime>CONFIG.timing.crossTriggerWaitTimeout){clearInterval(checkInterval);this._crossTriggerLock=false;log(`⏰ [CROSS] ${otherType} timed out while loading (${CONFIG.timing.crossTriggerWaitTimeout/1000}s) – forcing refresh if still not loaded`,'WARN',this.type);if(!otherLoader.isLoaded&&!otherLoader.noFlightsConfirmed&&otherLoader.hasValidDate()&&!otherLoader._isTerminalForDate(otherLoader.getDateValue())){log(`🔄 [CROSS] Timeout: forcing refresh on ${otherType}`,'INFO',this.type);otherLoader._suppressNewListRequests=false;otherLoader._suppressIntervalChecks=false;otherLoader._suppressDateChanges=false;otherLoader._networkRestoreInProgress=false;otherLoader._dateResetInProgress=false;_dateResetInProgress=false;if(otherLoader._refreshDebounceTimer){clearTimeout(otherLoader._refreshDebounceTimer);otherLoader._refreshDebounceTimer=null;}if(!otherLoader._refreshLock&&!otherLoader._refreshScheduled){otherLoader._refreshLock=false;otherLoader._refreshScheduled=false;otherLoader._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true});}}}},500);return;} if(otherLoader._dateChangePending||_dateChangePending[otherType]){log(`⏸️ [CROSS] ${otherType} has pending user date change – skipping cross-trigger`,'DEBUG',this.type);this._crossTriggerLock=false;return;} if(_globalCrossTriggerLock[otherType]){log(`⏸️ [CROSS] Cross-trigger already in progress for ${otherType}, skipping`,'DEBUG',this.type);this._crossTriggerLock=false;return;} _globalCrossTriggerLock[otherType]=true;setTimeout(()=>{_globalCrossTriggerLock[otherType]=false;},5000); log(`🔎 [CROSS-DETAIL] ${this.type} → ${otherType}: force=${force}, otherDate="${otherDate}", other.isLoaded=${otherLoader.isLoaded}, other.lastLoaded="${otherLoader._lastLoadedDate}", other.active=${otherLoader.active}, other._listRequestInFlight=${otherLoader._listRequestInFlight}, other._listRequestPending=${otherLoader._listRequestPending}, other._listRequestProcessing=${otherLoader._listRequestProcessing}, other.permanentError=${otherLoader.permanentError}, isTerminal=${isTerminal}`,'DEBUG',this.type); if(!otherLoader.isLoaded||otherLoader._lastLoadedDate!==otherLoader.getDateValue()){ if(!otherLoader.hasValidDate()||otherLoader.permanentError){log(`⏸️ [CROSS] ${otherType} has no valid date or permanent error, skipping`,'DEBUG',this.type);this._crossTriggerLock=false;return;} if(!GlobalCooldownManager.canMakeRequest()){log(`⏸️ [CROSS] Global cooldown active, rescheduling for ${otherType}`,'DEBUG',this.type);const remaining=GlobalCooldownManager.getRemainingTime();setTimeout(()=>{if(otherLoader&&otherLoader.hasValidDate()&&!otherLoader.isLoaded&&!otherLoader.noFlightsConfirmed&&!otherLoader.permanentError&&GlobalCooldownManager.canMakeRequest()){if(!otherLoader._isTerminalForDate(otherLoader.getDateValue())&&!otherLoader._pluginRefreshInProgress&&!otherLoader._refreshLock&&!otherLoader._refreshScheduled&&!otherLoader.active){otherLoader._suppressNewListRequests=false;otherLoader._suppressIntervalChecks=false;otherLoader._suppressDateChanges=false;otherLoader._networkRestoreInProgress=false;otherLoader._dateResetInProgress=false;_dateResetInProgress=false;if(otherLoader._refreshDebounceTimer){clearTimeout(otherLoader._refreshDebounceTimer);otherLoader._refreshDebounceTimer=null;}otherLoader._refreshLock=false;otherLoader._refreshScheduled=false;otherLoader._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true});}}this._crossTriggerLock=false;},remaining+500);return;} if(!isTerminal&&!otherLoader._pluginRefreshInProgress&&!otherLoader._refreshLock&&!otherLoader._refreshScheduled&&!otherLoader.active){log(`🔄 [CROSS] Triggering ${otherType} loader (not loaded or date mismatch)`,'INFO',this.type);otherLoader._suppressNewListRequests=false;otherLoader._suppressIntervalChecks=false;otherLoader._suppressDateChanges=false;otherLoader._networkRestoreInProgress=false;otherLoader._dateResetInProgress=false;_dateResetInProgress=false;if(otherLoader._refreshDebounceTimer){clearTimeout(otherLoader._refreshDebounceTimer);otherLoader._refreshDebounceTimer=null;}otherLoader._refreshLock=false;otherLoader._refreshScheduled=false;otherLoader._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true});_globalCrossTriggerThrottle=now;_perLoaderCrossTriggerCooldown[otherType]=now+CONFIG.timing.perLoaderCrossTriggerCooldownMs;}else log(`⏸️ [CROSS] ${otherType} became terminal, in refresh, or active before trigger – skipping`,'DEBUG',this.type); }else log(`⏸️ [CROSS] ${otherType} already loaded with correct date, skipping`,'DEBUG',this.type); this._crossTriggerLock=false; } _showNotify(msg){const existing=document.querySelectorAll('.flight-notify-final');existing.forEach(n=>n.remove());const n=document.createElement('div');n.className='flight-notify-final';n.innerHTML=`✓ ${msg}`;document.body.appendChild(n);setTimeout(()=>n.remove(),3000);} _clearAllTimers(reason='unspecified'){ if(this.ajaxResponseTimer){clearTimeout(this.ajaxResponseTimer);this.ajaxResponseTimer=null;} if(this.doubleVerifyTimer){clearTimeout(this.doubleVerifyTimer);this.doubleVerifyTimer=null;} if(this.finalizeStabilityTimer){clearTimeout(this.finalizeStabilityTimer);this.finalizeStabilityTimer=null;} if(this.fallbackTimeout){clearTimeout(this.fallbackTimeout);this.fallbackTimeout=null;} if(this.detectionSettleTimer){clearTimeout(this.detectionSettleTimer);this.detectionSettleTimer=null;} if(this.postResponseEmptyCheckTimer){clearTimeout(this.postResponseEmptyCheckTimer);this.postResponseEmptyCheckTimer=null;} if(this.postRefreshEmptyCheckTimer){clearTimeout(this.postRefreshEmptyCheckTimer);this.postRefreshEmptyCheckTimer=null;} if(this.containerEmptyCheckTimer){clearTimeout(this.containerEmptyCheckTimer);this.containerEmptyCheckTimer=null;} if(this.refreshSafetyTimer){clearTimeout(this.refreshSafetyTimer);this.refreshSafetyTimer=null;} if(this.mutationStableTimer){clearTimeout(this.mutationStableTimer);this.mutationStableTimer=null;} if(this.noAjaxRetryTimer){clearTimeout(this.noAjaxRetryTimer);this.noAjaxRetryTimer=null;} if(this.detectionFallbackTimer){clearTimeout(this.detectionFallbackTimer);this.detectionFallbackTimer=null;} if(this.zeroRadiosEarlyAbortTimer){clearTimeout(this.zeroRadiosEarlyAbortTimer);this.zeroRadiosEarlyAbortTimer=null;} if(this.lockReleaseTimer){clearTimeout(this.lockReleaseTimer);this.lockReleaseTimer=null;} if(this.fallbackAjaxTimer){clearTimeout(this.fallbackAjaxTimer);this.fallbackAjaxTimer=null;} if(this._inputDebounceTimer){clearTimeout(this._inputDebounceTimer);this._inputDebounceTimer=null;} if(this._progressFallbackTimer){clearTimeout(this._progressFallbackTimer);this._progressFallbackTimer=null;log(`⏹️ [FALLBACK] Cancelling pending fallback timer for ${this.type} (reason=${reason})`,'DEBUG',this.type);} if(this._noFlightsFallbackTimer){clearTimeout(this._noFlightsFallbackTimer);this._noFlightsFallbackTimer=null;log(`⏹️ [NOFLIGHTS-FB] Cancelling fallback timer for ${this.type} (reason=${reason})`,'DEBUG',this.type);} if(this._secondaryNoFlightsTimer){clearTimeout(this._secondaryNoFlightsTimer);this._secondaryNoFlightsTimer=null;} if(this._tertiaryNoFlightsTimer){clearTimeout(this._tertiaryNoFlightsTimer);this._tertiaryNoFlightsTimer=null;} if(this._pendingListRequestTimer){clearTimeout(this._pendingListRequestTimer);this._pendingListRequestTimer=null;} if(this._listRequestInFlightTimer){clearTimeout(this._listRequestInFlightTimer);this._listRequestInFlightTimer=null;} if(this._refreshLockSafetyTimer){clearTimeout(this._refreshLockSafetyTimer);this._refreshLockSafetyTimer=null;} if(this._retryAfterSkipTimer){clearTimeout(this._retryAfterSkipTimer);this._retryAfterSkipTimer=null;} if(this._transient5xxTimer){clearTimeout(this._transient5xxTimer);this._transient5xxTimer=null;this._transient5xxTimerSetAt=0;log(`⏹️ [SERVER] Cancelling transient 5xx retry timer for ${this.type} (reason=${reason})`,'DEBUG',this.type);} } _resetFallbackTimeout(){if(this.fallbackTimeout)clearTimeout(this.fallbackTimeout);const timeoutDuration=this.adaptiveBaseTimeout+this.adaptiveGracePeriod+CONFIG.timing.fallbackExtraMs;const isWeak=networkQuality==='weak'||networkQuality==='fair';const cap=isWeak?CONFIG.timing.fallbackCapWeak:CONFIG.timing.fallbackCapFast;const capped=Math.min(timeoutDuration,cap);this.fallbackTimeout=setTimeout(()=>{if(this.active&&!this.isLoaded&&!this.permanentError){log(`⏰ Fallback timeout after ${capped}ms for ${this.type}`,'WARN',this.type);this.completeLoading(false,'FallbackTimeout');}},capped);} _startAjaxResponseTimer(){const isWeak=networkQuality==='weak'||networkQuality==='fair';const ajaxTimeout=isWeak?CONFIG.flightLoader.ajaxResponseTimeoutWeak:CONFIG.flightLoader.ajaxResponseTimeoutFast;this.ajaxResponseTimer=setTimeout(()=>{if(this.active&&!this.isLoaded&&!this.permanentError&&!this.ajaxResponseReceived){log(`⚠️ No AJAX response received after ${ajaxTimeout}ms for ${this.type}`,'WARN',this.type);this._handleNoAjaxResponse();}},ajaxTimeout);} _handleNoAjaxResponse(){if(!this.active||this.isLoaded||this.permanentError)return;this._releaseLock();this._listRequestInFlight=false;this._listRequestPending=false;this._pendingListRequestDate=null;if(this.noAjaxRetryCount{if(this.active&&!this.isLoaded&&!this.permanentError){this.hideError();const currentDate=this.getDateValue();if(currentDate){log(`🔄 [NO-AJAX] Retrying plugin refresh for ${this.type}`,'INFO',this.type);this._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true});setTimeout(()=>this._executeLoading(true),CONFIG.timing.pluginRefreshDelay);}else{this._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true});setTimeout(()=>this._executeLoading(true),CONFIG.timing.pluginRefreshDelay);}}},delay);}else{this.showError('Server is not responding. Please try again later.',0,true);this.permanentError=true;this._permanentCycles++;this.completeLoading(false,'NoAjaxResponse');}_clearGlobalLoadingFlag('handleNoAjaxResponse:'+this.type);} onNetworkOffline(){log(`🌐 Network offline handler for ${this.type}`,'INFO',this.type);if(this.active)this.stopLoading(true);this._resetAllForcedRefreshFlags();this._listRequestInFlight=false;this._listRequestPending=false;this._pendingListRequestDate=null;const currentDate=this.getDateValue();if(!currentDate){log(`📭 Network offline but date is empty – showing empty state for ${this.type}`,'INFO',this.type);this.showDateEmpty();return;}const fallbackDate=currentDate||this._originalDateBeforeRefresh||this.lastDateValue;if(fallbackDate){this.offlinePendingDate=fallbackDate;log(`🌐 ${this.type} offlinePendingDate queued: ${fallbackDate}`,'INFO',this.type);}else{this.offlinePendingDate=null;log(`⚠️ ${this.type} no date available to queue while offline`,'WARN',this.type);}if(this.isLoaded){log(`🌐 Network offline but flights already loaded for ${this.type}, preserving display`,'INFO',this.type);return;}if(this._isVerificationStage()){log(`🌐 Network offline but verification stage in progress – not showing overlay for ${this.type}`,'INFO',this.type);return;}this.showNetworkOffline();_clearGlobalLoadingFlag('onNetworkOffline:'+this.type);} _resetForNetworkRestore(date){log(`🔄 [NET-RESTORE] Resetting state for ${this.type} for date ${date} without clearing dedupe maps`,'INFO',this.type);this.stopLoading(true);this._abortAllAndClearForLoader();this.isLoaded=false;this._lastLoadedDate=null;this._clearNoFlightsState();this.emptyResponseReceived=false;this.ajaxResponseReceived=false;this.firstDetectionDone=false;this.gracePeriodActive=false;this.permanentError=false;this.cooldownBlocked=false;this.forcedRefreshRetries=0;this.consecutiveInvalidResponses=0;this.consecutive429Count=0;this.lastResponseWas429=false;this.consecutiveNoAjaxCount=0;this.errorCooldownTriggered=false;this._lastKnownDateValue=date;this._lastResetDate=date;this._processingDate=null;this._listRequestInFlight=false;this._listRequestPending=false;this._pendingListRequestDate=null;clearListRequestPending(this,date);const otherLoader=this.type==='departure'?window.returnLoader:window.departureLoader;if(otherLoader){const otherDate=otherLoader.getDateValue();if(otherDate){clearListRequestPending(otherLoader,otherDate);log(`🧹 [NET-RESTORE] Also cleared dedupe for other loader (${otherLoader.type}) date ${otherDate}`,'DEBUG',this.type);}}this._suppressNewListRequests=true;this._dateResetInProgress=true;this._suppressDateChanges=true;this._suppressIntervalChecks=true;_dateResetInProgress=true;this.hideAllOverlays();this._networkRestoreInProgress=true;log(`✅ [NET-RESTORE] State reset for ${this.type} complete (dedupe cleared for both loaders, restore flag set)`,'INFO',this.type);} onNetworkRestored(isPrimary=true){ if(!isPrimary){log(`🌐 Network restored (secondary) for ${this.type}: resetting state only, will show overlay after delay`,'INFO',this.type);this._blockForcedRefreshUntil=0;_listCooldownUntil[this.type]=0;this._pluginRefreshInProgress=false;this._suppressEmptyDuringRefresh=false;this._refreshLock=false;this._lastPluginRefreshTime=0;this._lastForcedRefreshCall=0;this.hideNetworkOffline();this.permanentError=false;this.consecutive429Count=0;this.lastResponseWas429=false;this.consecutiveInvalidResponses=0;this.consecutiveNoAjaxCount=0;this._listRequestInFlight=false;this._listRequestPending=false;this._pendingListRequestDate=null;if(_globalNetworkRestoreInProgress){this._pendingSecondaryRestore=true;log(`⏸️ [NET-RESTORE] Primary restore in progress, secondary will be triggered later (pending flag set)`,'INFO',this.type);return;}if(this.hasValidDate()&&!this.isLoaded&&!this.noFlightsConfirmed){this._pendingSecondaryRestore=false;log(`🌐 Network restored (secondary): delaying restore for ${this.type} by ${CONFIG.network.secondaryRestoreDelay}ms`,'INFO',this.type);setTimeout(()=>{if(_globalNetworkRestoreInProgress){this._pendingSecondaryRestore=true;log(`⏸️ [NET-RESTORE] Primary restore started while secondary was waiting – setting pending flag`,'INFO',this.type);return;}if(this.isLoaded&&this._lastLoadedDate===this.getDateValue()){log(`🌐 Network restored but flights already loaded for ${this.type} – skipping overlay`,'INFO',this.type);return;}this.showNetworkRestored();let countdown=CONFIG.network.restoreCountdown;this.updateRestoredCountdown(countdown);if(this.restoredCountdownInterval)clearInterval(this.restoredCountdownInterval);this.restoredCountdownInterval=setInterval(()=>{countdown--;this.updateRestoredCountdown(countdown);if(countdown<=0){clearInterval(this.restoredCountdownInterval);this.hideNetworkRestored();if(!this.hasValidDate()||this.isLoaded||this.noFlightsConfirmed||this.permanentError){log(`⏸️ [NET-RESTORE] Skipping refresh because loader is already in terminal state`,'INFO',this.type);return;}if(this._dateChangeInProgress||this._suppressNewListRequests){log(`⏸️ [NET-RESTORE] Skipping refresh because reset or suppression in progress`,'INFO',this.type);this._suppressNewListRequests=false;this._dateChangeInProgress=false;this._suppressDateChanges=false;this._suppressIntervalChecks=false;this._networkRestoreInProgress=false;_dateResetInProgress=false;this._dateResetInProgress=false;log(`🔓 [NET-RESTORE] Cleared suppression flags, retrying refresh for ${this.type}`,'INFO',this.type);this._fullResetAndAbort();this._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true});return;}log(`🧹 [NET-RESTORE] Performing full reset before refresh for ${this.type}`,'INFO',this.type);this._fullResetAndAbort();log(`🌐 [NET-RESTORE] Countdown ended, triggering plugin refresh for ${this.type}`,'INFO',this.type);this._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true});}},1000);},CONFIG.network.secondaryRestoreDelay);}return;} log(`🌐 Network restored (primary) for ${this.type}`,'INFO',this.type); if(this._restoreDebounceTimer){clearTimeout(this._restoreDebounceTimer);log(`⏸️ [NET-RESTORE] Debouncing restore for ${this.type}`,'DEBUG',this.type);} this._restoreDebounceTimer=setTimeout(()=>{this._restoreDebounceTimer=null;_globalNetworkRestoreInProgress=true;this._doNetworkRestore(true);setTimeout(()=>{if(_globalNetworkRestoreInProgress){log(`⏰ [NET-RESTORE] Safety timer: clearing _globalNetworkRestoreInProgress for ${this.type}`,'WARN',this.type);_globalNetworkRestoreInProgress=false;}},15000);},CONFIG.network.restoreDebounceMs); } _doNetworkRestore(fromPrimary=false){ log(`🌐 [NET-RESTORE] Executing network restore for ${this.type} (fromPrimary=${fromPrimary}) – preserving DETAIL retries`,'INFO',this.type); if(window.RequestQueue){window.RequestQueue.clearListRequestsAndAbortPending('departure');window.RequestQueue.clearListRequestsAndAbortPending('return');window.RequestQueue.clearListRequestsForLoader('departure');window.RequestQueue.clearListRequestsForLoader('return');log(`🧹 [NET-RESTORE] Cleared LIST requests only (DETAIL preserved)`,'INFO','queue');} if(fromPrimary){const currentDate=this.getDateValue();if(!currentDate){const fallbackDate=this.offlinePendingDate||this.lastDateValue;if(fallbackDate&&isValidDateString(fallbackDate)){log(`📅 [NET-RESTORE] Secondary date was empty – restoring from offlinePendingDate/lastDateValue: ${fallbackDate}`,'INFO',this.type);this.setDateValue(fallbackDate);}else{log(`⚠️ [NET-RESTORE] Secondary has no fallback date – showing empty overlay`,'WARN',this.type);this.showDateEmpty();_globalNetworkRestoreInProgress=false;return;}}} if(_dateResetInProgress||this._suppressNewListRequests||this._dateChangeInProgress||this._suppressDateChanges){log(`⏸️ [NET-RESTORE] Skipping refresh for ${this.type} because reset or suppression is in progress`,'INFO',this.type);this.hideNetworkOffline();this._blockForcedRefreshUntil=0;_listCooldownUntil[this.type]=0;if(this.isLoaded&&this._lastLoadedDate===this.getDateValue()){log(`⏸️ [NET-RESTORE] This loader is already loaded – skipping refresh`,'INFO',this.type);_globalNetworkRestoreInProgress=false;return;}if(this.noFlightsConfirmed&&this._noFlightsDate===this.getDateValue()){log(`⏸️ [NET-RESTORE] This loader has No Flights confirmed – skipping refresh`,'INFO',this.type);_globalNetworkRestoreInProgress=false;return;}this._pluginRefreshInProgress=false;this._suppressEmptyDuringRefresh=false;this._refreshLock=false;this._lastPluginRefreshTime=0;this._lastForcedRefreshCall=0;this.permanentError=false;this.consecutive429Count=0;this.lastResponseWas429=false;this.consecutiveInvalidResponses=0;this.consecutiveNoAjaxCount=0;this._listRequestInFlight=false;this._listRequestPending=false;this._pendingListRequestDate=null;this._fullResetAndAbort();this._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true});_globalNetworkRestoreInProgress=false;return;} this._blockForcedRefreshUntil=0;_listCooldownUntil[this.type]=0; if(this.isLoaded&&this._lastLoadedDate===this.getDateValue()){log(`🌐 Network restored but flights already loaded and date matches – skipping refresh for ${this.type}`,'INFO',this.type);const otherLoader=this.type==='departure'?window.returnLoader:window.departureLoader;if(otherLoader&&otherLoader._pendingSecondaryRestore){log(`🌐 [NET-RESTORE] Primary is terminal (loaded) but secondary has pending restore – triggering secondary restore`,'INFO',this.type);otherLoader._pendingSecondaryRestore=false;_globalNetworkRestoreInProgress=false;_globalNetworkRestoreInProgress=true;otherLoader._doNetworkRestore(true);return;}_globalNetworkRestoreInProgress=false;return;} if(this.noFlightsConfirmed&&this._noFlightsDate===this.getDateValue()){log(`🌐 Network restored but "No Flights" confirmed for current date – skipping refresh for ${this.type}`,'INFO',this.type);const otherLoader=this.type==='departure'?window.returnLoader:window.departureLoader;if(otherLoader&&otherLoader._pendingSecondaryRestore){log(`🌐 [NET-RESTORE] Primary is terminal (NoFlights) but secondary has pending restore – triggering secondary restore`,'INFO',this.type);otherLoader._pendingSecondaryRestore=false;_globalNetworkRestoreInProgress=false;_globalNetworkRestoreInProgress=true;otherLoader._doNetworkRestore(true);return;}_globalNetworkRestoreInProgress=false;return;} this._pluginRefreshInProgress=false;this._suppressEmptyDuringRefresh=false;this._refreshLock=false;this._lastPluginRefreshTime=0;this._lastForcedRefreshCall=0; this.hideNetworkOffline();this.permanentError=false;this.consecutive429Count=0;this.lastResponseWas429=false;this.consecutiveInvalidResponses=0;this.consecutiveNoAjaxCount=0; const currentDate=this.getDateValue(); if(this.isLoaded&&this._lastLoadedDate===currentDate){_globalNetworkRestoreInProgress=false;return;} else if(this.isLoaded&&this._lastLoadedDate!==currentDate){log(`📅 [NET-RESTORE] Date changed while offline (${this._lastLoadedDate} → ${currentDate}) – forcing refresh for ${this.type}`,'INFO',this.type);this._resetForNetworkRestore(currentDate);this._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true});_globalNetworkRestoreInProgress=false;return;} const queuedDate=this.offlinePendingDate||this.lastDateValue; if(this.noFlightsConfirmed&&this._noFlightsDate===queuedDate){_globalNetworkRestoreInProgress=false;return;} if(this.noFlightsConfirmed&&this._noFlightsDate!==queuedDate){log(`🌐 Network restored but "No Flights" date (${this._noFlightsDate}) != queued date (${queuedDate}) – resetting flags and refreshing for ${this.type}`,'INFO',this.type);this._clearNoFlightsState();} if(this.isLoaded||this.noFlightsConfirmed){log(`🌐 Network restored but flights already loaded or no-flights confirmed for ${this.type}, refreshing silently`,'INFO',this.type);if(this.hasValidDate()&&!this.isLoaded){this._resetForNetworkRestore(currentDate);window.RequestQueue.clear(true);window.RequestQueue.clearListRequestsAndAbortPending(this.type);this.startLoading(true);}_globalNetworkRestoreInProgress=false;return;} if(this._isVerificationStage()){log(`🌐 Network restored but verification stage in progress – not showing overlay for ${this.type}`,'INFO',this.type);_globalNetworkRestoreInProgress=false;return;} if(!currentDate){this.showDateEmpty();_globalNetworkRestoreInProgress=false;return;} if(this.isLoaded&&queuedDate&&this.getDateValue()===queuedDate&&this.hasAnyNonOverlayContent()){log(`🌐 Network restored, flights already loaded and date matches for ${this.type}`,'INFO',this.type);this.offlinePendingDate=null;_globalNetworkRestoreInProgress=false;return;} if(GlobalCooldownManager.getRemainingTime()>0){log(`🌐 Network restored but global cooldown active – not showing restored overlay for ${this.type}`,'INFO',this.type);_globalNetworkRestoreInProgress=false;return;} if(!queuedDate||!isValidDateString(queuedDate)){if(this.hasValidDate()){this._resetForNetworkRestore(currentDate);this.startLoading(true);}else this.showDateEmpty();_globalNetworkRestoreInProgress=false;return;} log(`📅 Network restore: applying queued date ${queuedDate} for ${this.type}`,'INFO',this.type); const wasProgrammatic=window.isProgrammaticUpdate; window.isProgrammaticUpdate=false;this.setDateValue(queuedDate);window.isProgrammaticUpdate=wasProgrammatic; if(!this.hasValidDate()){this.showDateEmpty();_globalNetworkRestoreInProgress=false;return;} this._resetForNetworkRestore(queuedDate); if(window.RequestQueue)window.RequestQueue.clear(true); this.showNetworkRestored(); let countdown=CONFIG.network.restoreCountdown; this.updateRestoredCountdown(countdown); if(this.restoredCountdownInterval)clearInterval(this.restoredCountdownInterval); this.restoredCountdownInterval=setInterval(()=>{countdown--;this.updateRestoredCountdown(countdown);if(countdown<=0){clearInterval(this.restoredCountdownInterval);this.hideNetworkRestored();log(`🔓 [NET-RESTORE] Countdown ended – clearing suppression flags for ${this.type}`,'INFO',this.type);this._suppressNewListRequests=false;this._dateChangeInProgress=false;this._suppressDateChanges=false;this._suppressIntervalChecks=false;this._networkRestoreInProgress=false;_dateResetInProgress=false;this._dateResetInProgress=false;if(!this.hasValidDate()||this.isLoaded||this.noFlightsConfirmed||this.permanentError){log(`⏸️ [NET-RESTORE] Skipping refresh because loader is already in terminal state`,'INFO',this.type);_globalNetworkRestoreInProgress=false;return;}if(this._dateChangeInProgress||this._suppressNewListRequests){log(`⏸️ [NET-RESTORE] Skipping refresh because reset or suppression still in progress (unexpected)`,'WARN',this.type);_globalNetworkRestoreInProgress=false;return;}log(`🧹 [NET-RESTORE] Performing full reset before refresh for ${this.type}`,'INFO',this.type);this._fullResetAndAbort();log(`🌐 [NET-RESTORE] Countdown ended, triggering plugin refresh for ${this.type}`,'INFO',this.type);this._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true});const otherLoader=this.type==='departure'?window.returnLoader:window.departureLoader;if(otherLoader&&otherLoader._pendingSecondaryRestore){log(`🌐 [NET-RESTORE] Primary restore done, now triggering secondary restore for ${otherLoader.type}`,'INFO',this.type);otherLoader._pendingSecondaryRestore=false;_globalNetworkRestoreInProgress=false;_globalNetworkRestoreInProgress=true;otherLoader._doNetworkRestore(true);}else _globalNetworkRestoreInProgress=false;}},1000); } onNetworkRestoredSilent(){log(`🌐 Network restored (silent) for ${this.type}`,'INFO',this.type);this._blockForcedRefreshUntil=0;_listCooldownUntil[this.type]=0;this._pluginRefreshInProgress=false;this._suppressEmptyDuringRefresh=false;this._refreshLock=false;this._lastPluginRefreshTime=0;this._lastForcedRefreshCall=0;this.hideNetworkOffline();this.permanentError=false;this.consecutive429Count=0;this.lastResponseWas429=false;this.consecutiveInvalidResponses=0;this.consecutiveNoAjaxCount=0;this._listRequestInFlight=false;this._listRequestPending=false;this._pendingListRequestDate=null;} forceContainerClear(){log(`⚠️ forceContainerClear called but deprecated - ignoring`,'WARN',this.type);} hasAnyNonOverlayContent(){const children=Array.from(this.container.children);for(const child of children){const isOurOverlay=child.classList&&(child.classList.contains('flight-loading-final')||child.classList.contains('flight-date-empty-final')||child.classList.contains('flight-no-flights-final')||child.classList.contains('flight-error-final')||child.classList.contains('flight-cooldown-overlay')||child.classList.contains('flight-network-overlay')||child.classList.contains('flight-restored-overlay')||child.classList.contains('flight-cooldown-toast'));if(!isOurOverlay)return true;}return false;} _purgeStaleContent(){if(!this.container)return;log(`🧹 Purging stale content for ${this.type} (loadId: ${this.currentLoadId})`,'DEBUG',this.type);const selectorsToRemove=['.jet-listing-grid__item','.jet-listing-dynamic-post','.elementor-widget','[data-elementor-type]','.jet-form-builder__field-wrap.radio-wrap','.flight-card-final','.flight-check-final','.flight-selected-final'];selectorsToRemove.forEach(selector=>{const elements=this.container.querySelectorAll(selector);elements.forEach(el=>{const isOurUI=el.closest('.flight-loading-final')||el.closest('.flight-date-empty-final')||el.closest('.flight-no-flights-final')||el.closest('.flight-error-final')||el.closest('.flight-cooldown-overlay')||el.closest('.flight-network-overlay')||el.closest('.flight-restored-overlay');if(!isOurUI)el.remove();});});const allElements=this.container.querySelectorAll('[data-flight-load-id]');allElements.forEach(el=>{const id=parseInt(el.dataset.flightLoadId);if(!isNaN(id)&&id!==this.currentLoadId)el.remove();});const freshElements=this.container.querySelectorAll('.jet-listing-grid__item, .jet-listing-dynamic-post, .elementor-widget');freshElements.forEach(el=>{const loadId=el.closest('[data-flight-load-id]')?.dataset.flightLoadId;if(!loadId||parseInt(loadId)!==this.currentLoadId)el.remove();});} _markContainerFreshness(){if(!this.container)return;this.container.dataset.flightLoadId=this.currentLoadId;this.container.dataset.flightLoadStart=Date.now();} forceReflow(){if(!this.container)return;const originalDisplay=this.container.style.display;this.container.style.display='block';void this.container.offsetHeight;this.container.style.display=originalDisplay;} updateAdaptiveTimeouts(){const isWeak=networkQuality==='weak'||networkQuality==='fair';this.adaptiveBaseTimeout=isWeak?CONFIG.flightLoader.baseDetectionTimeoutWeak:CONFIG.flightLoader.baseDetectionTimeoutFast;this.adaptiveGracePeriod=isWeak?CONFIG.flightLoader.gracePeriodWeak:CONFIG.flightLoader.gracePeriodFast;this.adaptiveForcedRefreshDelay=isWeak?CONFIG.flightLoader.forcedRefreshDelayWeak:CONFIG.flightLoader.forcedRefreshDelayFast;this.adaptiveEmergencyLockDelay=isWeak?CONFIG.timing.emergencyLockReleaseDelayWeak:CONFIG.timing.emergencyLockReleaseDelayFast;log(`⏱️ Adaptive timeouts for ${this.type}: base=${this.adaptiveBaseTimeout}ms, grace=${this.adaptiveGracePeriod}ms, lock=${this.adaptiveEmergencyLockDelay}ms`,'DEBUG',this.type);} stopLoading(force=false){this.active=false;if(force)this._releaseLock();window.loadingLocks[this.type]=false;this.hideLoading();this._clearAllTimers('stopLoading');if(this.progressUpdateInterval)clearInterval(this.progressUpdateInterval);if(this.interval)clearInterval(this.interval);if(this.lockReleaseTimer)clearTimeout(this.lockReleaseTimer);this.pendingLoadId=null;this.hideCooldownToast();this._listRequestInFlight=false;this._pendingListRequestDate=null;this._listRequestPending=false;clearListRequestPending(this,this.getDateValue());if(this._tertiaryNoFlightsTimer)clearTimeout(this._tertiaryNoFlightsTimer);if(this._secondaryNoFlightsTimer)clearTimeout(this._secondaryNoFlightsTimer);if(this._fastNoFlightsTimer)clearTimeout(this._fastNoFlightsTimer);_clearGlobalLoadingFlag('stopLoading:'+this.type);this._suppressIntervalChecks=false;this._suppressNewListRequests=false;this._suppressDateChanges=false;this._networkRestoreInProgress=false;_dateResetInProgress=false;this._dateResetInProgress=false;this._ignoreNextChange=false;this._processingDate=null;if(this._retryAfterSkipTimer){clearTimeout(this._retryAfterSkipTimer);this._retryAfterSkipTimer=null;}this._retryAfterSkipAttempts=0;log(`⏹️ Stopped loading for ${this.type}${force?' (forced)':''}`,'DEBUG',this.type);} updateNetworkQuality(){this.updateAdaptiveTimeouts();} } function createCheckButton(){const btn=document.createElement('div');btn.className='flight-check-final';return btn;} function createBadge(type){const b=document.createElement('div');b.className='flight-selected-final';b.textContent=type==='departure'?'SELECTED DEPARTURE FLIGHT':'SELECTED RETURN FLIGHT';return b;} function findButtonToReplace(container){const sel=['.elementor-button-wrapper','.jet-listing-dynamic-link__content','a[href*="book"]','button[type="button"]','.btn','[class*="button"]:not(.flight-check-final)','.elementor-button'];for(const s of sel){const btn=container.querySelector(s);if(btn&&btn.offsetWidth>30&&btn.offsetHeight>30&&getComputedStyle(btn).display!=='none')return btn;}return null;} let pendingSelectionTimeout=null; function enhanceFlight(radio){ try{ let wrap=radio.closest(CONFIG.selectors.radioWrap); if(!wrap)wrap=radio.closest('label'); if(!wrap)wrap=radio.parentElement; if(!wrap||wrap.offsetParent===null||wrap.offsetWidth<100)return false; let card=wrap.querySelector(CONFIG.selectors.flightCard)||wrap; if(card.classList.contains('flight-card-final'))return false; card.classList.add('flight-card-final');card.style.position='relative';card.style.overflow='visible'; const existingCheck=card.querySelector('.flight-check-final');if(existingCheck)existingCheck.remove(); const existingBadge=card.querySelector('.flight-selected-final');if(existingBadge)existingBadge.remove(); const check=createCheckButton(); const type=radio.name.includes('departure')?'departure':'return'; const badge=createBadge(type); const origBtn=findButtonToReplace(card); if(origBtn&&origBtn.parentNode){origBtn.style.opacity='0';origBtn.style.visibility='hidden';origBtn.parentNode.appendChild(check);} else card.appendChild(check); card.appendChild(badge); const loader=(type==='departure'?window.departureLoader:window.returnLoader); if(!loader)return false; const updateUI=()=>{const sel=radio.checked;check.classList.toggle('selected',sel);card.classList.toggle('selected',sel);badge.style.display=sel?'block':'none';}; const handleSelect=(event)=>{ if(_suppressUIChangeEvents||(loader&&loader._suppressUIChangeEvents)){log(`🛡️ [UI-SUPPRESS] Ignoring click because suppression active for ${type}`,'DEBUG','ui');return;} if(pendingSelectionTimeout)clearTimeout(pendingSelectionTimeout); if(radio.checked){log(`🔘 [DESELECT] Deselecting ${type} radio ${radio.value}`,'INFO','ui');setSuppressListRequests(true,600);abortDetailRequest(loader,radio.value,true,true);radio.checked=false;setTimeout(()=>{radio.dispatchEvent(new Event('change',{bubbles:true}));log(`🔄 [DESELECT] Dispatched change event for ${type} radio ${radio.value} after uncheck`,'DEBUG','ui');setTimeout(()=>{setSuppressListRequests(false);},500);},50);updateUI();if(_currentSelectedRadio[type]===radio.value)_currentSelectedRadio[type]=null;return;} const currentRadio=_currentSelectedRadio[type]; if(currentRadio!==null&&currentRadio!==radio.value){abortDetailRequest(loader,currentRadio,true,true);const oldRadio=loader.container.querySelector(`${loader.radioSelector}[value="${currentRadio}"]`);if(oldRadio&&oldRadio.checked){oldRadio.checked=false;const oldWrap=oldRadio.closest(CONFIG.selectors.radioWrap)||oldRadio.closest('label')||oldRadio.parentElement;if(oldWrap){let oldCard=oldWrap.querySelector(CONFIG.selectors.flightCard)||oldWrap;if(oldCard.classList.contains('flight-card-final')){oldCard.classList.remove('selected');const oldCheck=oldCard.querySelector('.flight-check-final');if(oldCheck)oldCheck.classList.remove('selected');const oldBadge=oldCard.querySelector('.flight-selected-final');if(oldBadge)oldBadge.style.display='none';}}}} log(`🔘 [SELECT] Selecting ${type} radio ${radio.value}`,'INFO','ui'); activateDetailBurst();window.setUserSelectionRequest(true,type); const grp=type==='departure'?CONFIG.selectors.departureRadios:CONFIG.selectors.returnRadios; Array.from(document.querySelectorAll(grp)).forEach(r=>{if(r!==radio&&r.checked){r.checked=false;const w=r.closest(CONFIG.selectors.radioWrap)||r.closest('label')||r.parentElement;if(w){let c=w.querySelector(CONFIG.selectors.flightCard)||w;if(c.classList.contains('flight-card-final')){c.classList.remove('selected');const cb=c.querySelector('.flight-check-final');if(cb)cb.classList.remove('selected');const b=c.querySelector('.flight-selected-final');if(b)b.style.display='none';}}}}); radio.checked=true;_currentSelectedRadio[type]=radio.value;updateUI(); radio.dispatchEvent(new Event('change',{bubbles:true})); const toast=document.createElement('div');toast.className='detail-initial-toast';toast.innerHTML=` Updating details...`; const checkBtn=card.querySelector('.flight-check-final'); if(checkBtn){const cardRect=card.getBoundingClientRect();const btnRect=checkBtn.getBoundingClientRect();const centerX=btnRect.left+btnRect.width/2-cardRect.left;toast.style.position='absolute';toast.style.left=`${centerX}px`;toast.style.transform='translateX(-50%)';toast.style.top=`${btnRect.top-cardRect.top-30}px`;} else{toast.style.position='relative';toast.style.margin='8px auto 0 auto';toast.style.display='inline-block';toast.style.left='50%';toast.style.transform='translateX(-50%)';} card.appendChild(toast); const toastKey=`${loader.type}:${String(radio.value)}`; const existing=activeInitialToasts.get(toastKey); if(existing&&existing.remove)existing.remove(); activeInitialToasts.set(toastKey,toast); log(`🍞 [INITIAL-TOAST] Created initial toast for ${loader.type} radio ${radio.value}`,'INFO','ui'); setTimeout(()=>window.setUserSelectionRequest(false,null),500); pendingSelectionTimeout=null; }; card.addEventListener('click',(e)=>{if(e.target.closest('.flight-check-final')||e.target.closest('a')||e.target.closest('button'))return;e.preventDefault();handleSelect(e);}); check.addEventListener('click',(e)=>{e.preventDefault();e.stopPropagation();handleSelect(e);}); updateUI(); return true; }catch(e){return false;} } let _lastEnhancedRadioIds=''; function enhanceAllFlights(){ const radioEls=Array.from(document.querySelectorAll(CONFIG.selectors.departureRadios+','+CONFIG.selectors.returnRadios)); const ids=radioEls.map(r=>r.value+(r.checked?'*':'')).join(','); if(ids===_lastEnhancedRadioIds&&ids!==''){log(`✨ [ENHANCE] Skipped – content unchanged (radios=${radioEls.length})`,'DEBUG','ui');return 0;} if(ids)_lastEnhancedRadioIds=ids; let c=0; Array.from(document.querySelectorAll(CONFIG.selectors.departureRadios)).forEach(r=>{if(enhanceFlight(r))c++;}); Array.from(document.querySelectorAll(CONFIG.selectors.returnRadios)).forEach(r=>{if(enhanceFlight(r))c++;}); if(c>0)log(`✨ Enhanced ${c} flights`,'DEBUG','ui'); return c; } window.enhanceAllFlights=enhanceAllFlights; let dateChangeDebounceTimers={departure:null,return:null}; let _dateChangeActiveLocks={departure:null,return:null}; let _dateChangePending={departure:false,return:false}; function setupDateListeners(){ const start=document.querySelector(CONFIG.selectors.startDate); const end=document.querySelector(CONFIG.selectors.endDate); const startSelect=document.querySelector(CONFIG.selectors.startDateSelect); const endSelect=document.querySelector(CONFIG.selectors.endDateSelect); const immediateDateChange=(type,loader,source='input')=>{ if(!loader)return; if(window.isProgrammaticUpdate){log(`⏸️ [IMMEDIATE] Skipped because isProgrammaticUpdate is true for ${type}`,'DEBUG','date');return;} if(loader._forcingAjax){log(`⏸️ [IMMEDIATE] Skipped because _forcingAjax is true for ${type} (forced AJAX trigger)`,'DEBUG','date');return;} if(loader._pluginRefreshInProgress||loader._dateChangeInProgress){log(`⏸️ [IMMEDIATE] Skipped because plugin refresh or date change in progress for ${type}`,'DEBUG','date');return;} const newDate=loader.getDateValue(); const oldDate=loader._lastKnownDateValue; if(newDate===oldDate)return; log(`⚡ [IMMEDIATE] ${type} date changed from "${oldDate}" to "${newDate}" (${source}) – cancelling stale requests`,'INFO','date'); loader._clearNoFlightsState(); loader._lastKnownDateValue=newDate; loader._immediateDateReset(); loader._lastKnownDateValue=newDate; }; if(start){start.addEventListener('input',function(e){const loader=window.departureLoader;if(loader)immediateDateChange('departure',loader,'input');});start.addEventListener('change',function(e){const loader=window.departureLoader;if(loader)immediateDateChange('departure',loader,'change');});} if(end){end.addEventListener('input',function(e){const loader=window.returnLoader;if(loader)immediateDateChange('return',loader,'input');});end.addEventListener('change',function(e){const loader=window.returnLoader;if(loader)immediateDateChange('return',loader,'change');});} const handleDateChange=(type,loader,source='change')=>{ if(loader._pluginRefreshSuppressUntil&&Date.now(){_dateChangeGlobalLock=false;},CONFIG.timing.dateChangeGlobalLockTimeout); if(loader._areDetailsPopulated()){log(`🧹 [DATE-RESET] Details populated, calling _resetDateToClearDetails for ${type}`,'INFO',type);loader._resetDateToClearDetails(newDate).then(()=>{_processDateChange(type,loader,source,oldDate,newDate);}).catch(()=>{_processDateChange(type,loader,source,oldDate,newDate);});} else _processDateChange(type,loader,source,oldDate,newDate); }; function _processDateChange(type,loader,source,oldDate,newDate){ const wasEmpty=!oldDate||oldDate===''; loader.permanentError=false;loader._permanentCycles=0;loader.cooldownBlocked=false;loader.consecutive429Count=0;loader.lastResponseWas429=false;loader.consecutiveNoAjaxCount=0;loader.consecutiveInvalidResponses=0;loader._clearNoFlightsState();loader.errorCooldownTriggered=false; if(loader._consecutive5xx>0){log(`🔄 [RESET] _consecutive5xx reset ${loader._consecutive5xx}→0 for ${type} (reason=dateChange)`,'DEBUG',type);loader._consecutive5xx=0;} if(loader._transient5xxTimer){clearTimeout(loader._transient5xxTimer);loader._transient5xxTimer=null;loader._transient5xxTimerSetAt=0;log(`⏹️ [SERVER] Cancelling transient 5xx retry timer for ${type} (reason=dateChange)`,'DEBUG',type);} log(`🧹 Reset error counters and "No Flights" flags for ${type} on valid date change`,'DEBUG',type); const otherLoader=type==='departure'?window.returnLoader:window.departureLoader; if(otherLoader){otherLoader._lastListRequestCallTime=0;otherLoader._listRequestPending=false;otherLoader._pendingListRequestDate=null;if(otherLoader._listRequestProcessing&&!otherLoader._listRequestInFlight){otherLoader._listRequestProcessing=false;RequestQueue._listRequestProcessing=false;}log(`🔄 Reset other loader (${otherLoader.type}) list flags (not terminal)`,'DEBUG',type);} if(newDate!==oldDate){loader.lastDateValue=newDate;loader._lastKnownDateValue=newDate;log(`💾 lastDateValue UPDATED for ${type}: "${oldDate||'(empty)'}" → "${newDate}"`,'INFO',type);} if(window.RequestQueue){window.RequestQueue.clearListRequestsForLoader(type);window.RequestQueue.abortAllForLoader(type);if(otherLoader){window.RequestQueue.clearListRequestsForLoader(otherLoader.type);window.RequestQueue.abortAllForLoader(otherLoader.type);}window.RequestQueue.queue=window.RequestQueue.queue.filter(item=>!(item.requestType==='list'&&(item.loader?.type===type||(otherLoader&&item.loader?.type===otherLoader.type))));log(`🧹 [DATE-CHANGE] Cleared all pending LIST requests for both loaders`,'INFO',type);} const clearKey=`${type}:${newDate}`; _globalListDedupeMap.delete(clearKey);_listRequestActiveLock.delete(clearKey);_activeListKeys.delete(clearKey);_activeListPromises.delete(clearKey); if(otherLoader){const otherDate=otherLoader.getDateValue();const otherKey=`${otherLoader.type}:${otherDate}`;_globalListDedupeMap.delete(otherKey);_listRequestActiveLock.delete(otherKey);_activeListKeys.delete(otherKey);_activeListPromises.delete(otherKey);} loader._lastResetDate=newDate; if(wasEmpty&&!loader._skipEmptyState){const oldSuppress=loader._suppressEmptyDuringRefresh,oldSkip=loader._skipEmptyState;loader._suppressEmptyDuringRefresh=false;loader._skipEmptyState=false;log(`🚀 [DATE-RESTORED] ${type} date restored from empty, starting load immediately`,'INFO',type);loader.startLoading(true);setTimeout(()=>{loader._suppressEmptyDuringRefresh=oldSuppress;loader._skipEmptyState=oldSkip;},500);} else loader.startLoading(); setTimeout(()=>{if(_dateChangeActiveLocks[type]===newDate)_dateChangeActiveLocks[type]=null;},2000); } const debouncedHandleChange=(type,loader,source='input')=>{if(loader&&loader._pluginRefreshSuppressUntil&&Date.now(){_dateChangePending[type]=false;if(loader)loader._suppressIntervalChecks=false;handleDateChange(type,loader,source);},CONFIG.timing.dateChangeDebounceMs);}; if(start){start.addEventListener('change',()=>debouncedHandleChange('departure',window.departureLoader,'change'));start.addEventListener('input',()=>debouncedHandleChange('departure',window.departureLoader,'input'));} if(end){end.addEventListener('change',()=>debouncedHandleChange('return',window.returnLoader,'change'));end.addEventListener('input',()=>debouncedHandleChange('return',window.returnLoader,'input'));} if(endSelect){endSelect.addEventListener('change',()=>{if(!window.isProgrammaticUpdate){const date=extractDateFromSelectOption(endSelect);if(date&&isValidDateString(date))setReturnDate(date);debouncedHandleChange('return',window.returnLoader,'select');}});} if(startSelect){startSelect.addEventListener('change',()=>{if(!window.isProgrammaticUpdate){const date=extractDateFromSelectOption(startSelect);if(date&&isValidDateString(date)){setDepartureDate(date);log(`📅 Client-side extraction: start_date_select → departure_date = ${date} (passive)`,'DEBUG','fieldUpdate');}}});} if(startSelect){setTimeout(()=>{const date=extractDateFromSelectOption(startSelect);if(date&&isValidDateString(date)){setDepartureDate(date);log(`📅 Initial client-side extraction set departure_date to ${date}`,'INFO','fieldUpdate');}},CONFIG.timing.initialLoadDelay);} if(endSelect)setTimeout(()=>updateReturnDateField(true),CONFIG.timing.initialLoadDelay); if(end){const observer=new MutationObserver((mutations)=>{mutations.forEach((mutation)=>{if(mutation.type==='attributes'&&mutation.attributeName==='value'){const loader=window.returnLoader;if(!window.isProgrammaticUpdate&&!loader?.suppressDateEmpty&&!loader?._pluginRefreshInProgress&&!loader?._suppressDateChanges&&!loader?._ignoreNextChange&&!loader?._skipEmptyState&&!loader?._suppressEmptyDuringRefresh&&!_globalRefreshInProgress&&!loader?._networkRestoreInProgress){if(!window.isProgrammaticUpdate)immediateDateChange('return',loader,'mutation');}}});});observer.observe(end,{attributes:true,attributeFilter:['value']});} } function updateDepartureDateField(synchronous=false){log(`⏸️ updateDepartureDateField called but flight loading is now isolated to start_date only`,'DEBUG','fieldUpdate');} function updateReturnDateField(synchronous=false){if(window.returnFieldUpdateInProgress)return;window.returnFieldUpdateInProgress=true;const select=document.querySelector(CONFIG.selectors.endDateSelect);const date=extractDateFromSelectOption(select);if(date&&isValidDateString(date)){log(`📅 Client-side date extraction (return): ${date}`,'INFO','fieldUpdate');setReturnDate(date);const triggerLoader=()=>{window.returnFieldUpdateInProgress=false;if(window.returnLoader){const currentDate=window.returnLoader.getDateValue();if(currentDate&&currentDate.trim()!=='')RequestQueue.enqueue(async()=>{window.returnLoader.startLoading();return true;},2,window.returnLoader,'list',null,null,null,false);else setTimeout(()=>{const retryDate=window.returnLoader.getDateValue();if(retryDate&&retryDate.trim()!=='')RequestQueue.enqueue(async()=>{window.returnLoader.startLoading();return true;},2,window.returnLoader,'list',null,null,null,false);else window.returnLoader.showDateEmpty();},500);}};if(synchronous)triggerLoader();else setTimeout(triggerLoader,CONFIG.timing.clientSideLoadDelay);}else{window.returnFieldUpdateInProgress=false;if(window.returnLoader)window.returnLoader.showDateEmpty();}} function init(){ log('✈️ === Flight System V379 — Owner-Aware Watchdog + Escalating Recovery ===','INFO','system'); log(`🎛️ [CONFIG] Verbose mode: ${_flightDebugMode} (use ?flight_debug=1|quiet to override)`,'INFO','system'); log(`🎛️ [CONFIG] Server 5xx backoff schedule: ${CONFIG.timing.serverErrorBackoffLevels.map(x=>x/1000+'s').join(' → ')} (linear→exponential)`,'INFO','system'); log(`🎛️ [CONFIG] 429 cycle reset window: ${CONFIG.timing._429ResetWindowMs/1000}s; maxPermanentCycles=${CONFIG.timing.maxPermanentCycles}; transient5xxStaleMs=${CONFIG.timing.transient5xxStaleMs}`,'INFO','system'); window.loadingLocks={departure:false,return:false}; window.loaderState={departure:{ajaxResponseReceived:false,lastSuccessfulResponseTime:0,hasValidResponse:false},return:{ajaxResponseReceived:false,lastSuccessfulResponseTime:0,hasValidResponse:false}}; window.isProgrammaticUpdate=false;window.globalRefreshInProgress=false;window.fieldUpdateInProgress=false;window.returnFieldUpdateInProgress=false; RequestQueue.init(); window.departureLoader=new FlightLoader('departure',CONFIG.selectors.departureContainer,CONFIG.selectors.departureRadios,CONFIG.selectors.startDate,CONFIG.selectors.startDateSelect); window.returnLoader=new FlightLoader('return',CONFIG.selectors.returnContainer,CONFIG.selectors.returnRadios,CONFIG.selectors.endDate,CONFIG.selectors.endDateSelect); installInterceptors(); setupSyncFromDepartureDate(); setupDateListeners(); startNetworkMonitoring(); setTimeout(()=>{enhanceAllFlights();},500); log('✅ Initialization complete (owner-aware per-loader watchdog on both loaders)','INFO','system'); log('📒 [LEDGER] window.flightLedger available for inspection (last 200 entries)','INFO','system'); } window.flightRetryFinal=(type,forceRefresh=true)=>{ const loader=type==='departure'?window.departureLoader:window.returnLoader; if(!loader)return; log(`🔄 Manual retry requested for ${type}`,'INFO','retry'); clearUISuppression();_dateResetInProgress=false; loader.pendingLoadId=null; log(`🗑️ [PENDING-ID] Cleared: (reason=flightRetryFinal) for ${type}`,'DEBUG',type); if(loader._consecutive5xx>0){log(`🔄 [RESET] _consecutive5xx reset ${loader._consecutive5xx}→0 for ${type} (reason=manualRetry)`,'DEBUG',type);loader._consecutive5xx=0;} // V379: reset permanentCycles on manual retry if(loader._permanentCycles>0){log(`🔄 [RESET] _permanentCycles reset ${loader._permanentCycles}→0 for ${type} (reason=manualRetry)`,'INFO',type);loader._permanentCycles=0;} if(loader._transient5xxTimer){clearTimeout(loader._transient5xxTimer);loader._transient5xxTimer=null;loader._transient5xxTimerSetAt=0;log(`⏹️ [SERVER] Cancelling transient 5xx retry timer for ${type} (reason=manualRetry)`,'DEBUG',type);} const otherLoader=type==='departure'?window.returnLoader:window.departureLoader; if(window.RequestQueue){window.RequestQueue.abortAllForLoader(type);window.RequestQueue.clearListRequestsForLoader(type);if(otherLoader){window.RequestQueue.abortAllForLoader(otherLoader.type);window.RequestQueue.clearListRequestsForLoader(otherLoader.type);}window.RequestQueue.queue=window.RequestQueue.queue.filter(item=>!(item.requestType==='list'));window.RequestQueue._listRequestProcessing=false;} const currentDate=loader.getDateValue(); if(currentDate){clearListRequestPending(loader,currentDate);if(otherLoader){const otherDate=otherLoader.getDateValue();if(otherDate){clearListRequestPending(otherLoader,otherDate);log(`🧹 [RETRY] Also cleared dedupe for other loader (${otherLoader.type}) date ${otherDate}`,'DEBUG','retry');}}} loader._fullReset(); log(`🧹 [RETRY] Full reset performed on ${type} only (permanentError cleared)`,'INFO','retry'); if(otherLoader){otherLoader._listRequestInFlight=false;otherLoader._listRequestPending=false;otherLoader._pendingListRequestDate=null;log(`🧹 [RETRY] Cleared list flags for other loader (${otherLoader.type})`,'INFO','retry');} _clearGlobalLoadingFlag('flightRetryFinal:'+type); GlobalCooldownManager.reset(); _crossTriggerThrottle.departure=0;_crossTriggerThrottle.return=0;_globalCrossTriggerThrottle=0;_perLoaderCrossTriggerCooldown={departure:0,return:0}; window.RequestQueue?.clear(true); loader._releaseLock();_clearGlobalLoadingFlag('flightRetryFinal:release:'+type); for(const [key,wrapper] of _pendingDetailPromises){if(key.startsWith(type)){if(wrapper&&wrapper.reject)wrapper.reject(new Error('Manual retry'));if(wrapper&&wrapper.cleanup)wrapper.cleanup();}} for(const key of _pendingDetailKeys){if(key.startsWith(type))_pendingDetailKeys.delete(key);} for(const key of _detailThrottle.keys()){if(key.startsWith(type))_detailThrottle.delete(key);} for(const key of _detailQueueSet){if(key.startsWith(type))_detailQueueSet.delete(key);} for(const key of _detailLastRequestTime.keys()){if(key.startsWith(type))_detailLastRequestTime.delete(key);} clearUISuppression();_dateResetInProgress=false; if(!loader.hasValidDate()){ if(loader.lastDateValue){log(`📅 [Retry] ${type} date empty, restoring lastDateValue="${loader.lastDateValue}"`,'INFO','retry');loader.setDateValue(loader.lastDateValue);setTimeout(()=>{if(!loader.hasValidDate()){log(`⚠️ [Retry] Restore failed for ${type}, focusing field`,'WARN','retry');window.focusFlightDateFinal(type);return;}loader._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true});setTimeout(()=>{if(!loader.isLoaded&&!loader.active&&loader.hasValidDate())loader.startLoading(true);},500);},300);return;} else{log(`⚠️ [Retry] ${type} has no date and no lastDateValue. Focusing field.`,'WARN','retry');window.focusFlightDateFinal(type);return;} } loader._triggerPluginRefresh({bypassDateCheck:true,bypassThrottle:true,force:true}); setTimeout(()=>{if(!loader.isLoaded&&!loader.active&&loader.hasValidDate()){log(`🔄 [Retry] Forcing startLoading after plugin refresh for ${type}`,'INFO','retry');loader.startLoading(true);}},500); }; window.focusFlightDateFinal=(type)=>{const sel=type==='departure'?CONFIG.selectors.startDate:CONFIG.selectors.endDate;const input=document.querySelector(sel);if(input){input.scrollIntoView({behavior:'smooth',block:'center'});setTimeout(()=>{input.focus();if(input.showPicker&&typeof input.showPicker==='function'){try{input.showPicker();}catch(e){}}input.style.boxShadow='0 0 0 3px rgba(59,130,246,0.3)';input.style.borderColor='var(--flight-primary-blue)';setTimeout(()=>{input.style.boxShadow='';input.style.borderColor='';},2000);},500);}}; if(document.readyState==='loading')window.addEventListener('load',()=>setTimeout(init,200)); else setTimeout(init,200); })();

Flights Order Summery

## Dep. Details:

No departure flight selected

Flight Code:

Flight ID

Flight Carrier:

flight Route

Dep. Date

Dep. Fare

       $

## Ret. Details:

No return flight selected

Flight Code:

Flight ID

Flight Carrier:

flight Route

Ret. Date

Ret. Fare

       $

## Flight Total Costs:

## PAX NO.

     0.00 PAX

## Flights cost p.p

     0.00 U$D        Flights Sub-total: Adult () + Children ()  0.00 U$D

✔ Local Licensed Operator

✔ Secure Payments

✔ No Hidden Fees

       Back Continue to Customize

## Step 3 of 4 — Personalize Your Experience

Enhance your journey by upgrading accommodations and adding optional services. All inclusions and upgrades are reflected instantly in your booking summary.

### Customize Stay & Extras

Default included: twin-share standard hotels & comfortable camping. Upgrade hotels, choose tent types, add single supplements, and pick optional extras. Prices are applied per person where noted. All costs update live!

## Accommodation Upgrades

Your package includes comfortable standard accommodation. You may upgrade to higher-category hotels, premium camps, or private room options.

          Al-Jawhara Hotel (Basic-1 Star)Summerland Hotel (luxury-3 Stars)       0.00      0.00

## Hotel Optimization:

No hotel accommodation in this trip

Your trip includes comfortable standard hotel accommodation (double occupancy). Want something more? You can easily upgrade to a premium hotel option—just select the upgrade when booking.

    Hotel Upgrades Standard (Included) Upgrade to Summerland Hotel    ( hotel nights) x ($ per night) x ( Room Units) for total of ( pax) =  U$D   $  0.00       ![437152332 10224431738887732 6101781708825204094 n](http://socotratravels.com/wp-content/uploads/2025/05/437152332_10224431738887732_6101781708825204094_n.jpg)

view more details

   ![Summerland Hotel view1](http://socotratravels.com/wp-content/uploads/2025/05/Summerland-Hotel-view1.jpg)

view more details

## Hotel Single Supplement

Traveling solo or prefer more privacy? Add hotel room/s as single supplement to enjoy private accommodation throughout your trip.

   Add single supplement (flat fee per trip) Hotel Single SupplementRecommended for solo travelers who prefer private rooms.      Extra Room Units            0.00 $    X ( hotel nights) X ($ per night) =  U$D

## Camping Enhancement:

No camping in this trip

This trip includes a standard tent on a double-sharing basis—ideal for a true outdoor experience. Prefer more comfort? Upgrade to a premium tent accommodation by choosing the upgrade options.

    Camping Tent Upgrades Standard Tent (Included) High Quality Tent Luxury Tent      Tent Upgrade Sub-total:   0.00 $    ( camping nights) X ($ per night) X ( tent units) for total of ( pax) =  U$D

Standard tent

 ![428486077 335328972837538 6386270610206329913 n](http://socotratravels.com/wp-content/uploads/2025/05/428486077_335328972837538_6386270610206329913_n.jpg)

view tent details

High Quality tent

 ![433974102 264382756730924 5861717803526489778 n](http://socotratravels.com/wp-content/uploads/2025/05/433974102_264382756730924_5861717803526489778_n.jpg)

view tent details

Luxury Tent

 ![429003773 7861962340520849 2355483091338116703 n](http://socotratravels.com/wp-content/uploads/2025/05/429003773_7861962340520849_2355483091338116703_n.jpg)

view tent details

## Tent Single Supplement

Traveling solo or prefer more privacy? Add camping tent/s as single supplement to enjoy private accommodation throughout your trip.

   Add single supplement (flat fee per trip)!      Extra Tent Units           Tent S.Supplement Sub-total:   0.00 $    X ( camping nights) X ($ per night) =  U$D

## Optional Add-Ons

Add optional services to make your trip more comfortable, exclusive, or adventurous.

     Optional Services Drone Permit (+$150 per trip) Satellite Internet / Phone / Emergency Equipment (+$45 per trip) Diving Equipments (+$70 per traveler per dive) Special Meals & Dietary Requests (+$30 per traveler per day)

Drone Permit

         $  0.00

Emergency Equipment

         $  0.00

Diving Equipment

   Traveler/s            $  0.00

Special Meals & Dietary

   Traveler/s            $  0.00

**Sub-Total**

     $  0.00

Accommodation Order

**Hotel included:**

No hotel accommodation in this trip

   ( hotel nights) x ( Room Units) for total of ( pax) In

**Camping included:**

No camping in this trip

   ( camping nights) X ( tent units) for total of ( pax) In  U$D

Upgrade Details

**Hotel Balance**

 $  ( hotel nights) x ($ per night) x ( Room Units) for total of ( pax) =  U$D   ( ExtraRoom Units) X ( hotel nights) X ($ per night) =  U$D     Total Hotel Upgrade Balance  0.00 $

**Tent Balance**

 $  ( camping nights) X ($ per night) X ( tent units) for total of ( pax) =  U$D   ( Extra tent units) X ( camping nights) X ($ per night) =  U$D     Total Tents Upgrade Balance  0.00 $

Upgrade balance

Upgrade sub-total

   $

##### Trust & Safety

- Local licensed operator
- Secure payments
- Free changes up to 30 days
- No Hidden Fees

       Back Continue to Review

## Step 4 of 4 — Review & Complete Your Booking

Almost done! Review your full trip summary, fill traveler details for each passenger exactly as in official documents for visas application, flights and permits, and choose how you’d like to pay. Secure your booking with a flexible payment option. You can reserve with a deposit or pay in full!

## Traveler/s Information

Please provide accurate information for each traveler as it appears on their passport. This is required for flight bookings and permits.

   Travelers Details: ( Adults)     First Name *      Surname         Gender  MaleFemale           Birthday       Phone      Email

Please complete the additional fields below, as they are required for processing your visa application.

    Your Country *  Select Your CountryAfghanistanÅland IslandsAlbaniaAlgeriaAmerican SamoaAndorrAAngolaAnguillaAntarcticaAntigua and BarbudaArgentinaArmeniaArubaAustraliaAustriaAzerbaijanBahamasBahrainBangladeshBarbadosBelarusBelgiumBelizeBeninBermudaBhutanBoliviaBosnia and HerzegovinaBotswanaBouvet IslandBrazilBritish Indian Ocean TerritoryBrunei DarussalamBulgariaBurkina FasoBurundiCambodiaCameroonCanadaCape VerdeCayman IslandsCentral African RepublicChadChileChinaChristmas IslandCocos (Keeling) IslandsColombiaComorosCongoCongo, The Democratic Republic of theCook IslandsCosta RicaCote D'IvoireCroatiaCubaCyprusCzech RepublicDenmarkDjiboutiDominicaDominican RepublicEcuadorEgyptEl SalvadorEquatorial GuineaEritreaEstoniaEthiopiaFalkland Islands (Malvinas)Faroe IslandsFijiFinlandFranceFrench GuianaFrench PolynesiaFrench Southern TerritoriesGabonGambiaGeorgiaGermanyGhanaGibraltarGreeceGreenlandGrenadaGuadeloupeGuamGuatemalaGuernseyGuineaGuinea-BissauGuyanaHaitiHeard Island and Mcdonald IslandsHoly See (Vatican City State)HondurasHong KongHungaryIcelandIndiaIndonesiaIran, Islamic Republic OfIraqIrelandIsle of ManIsraelItalyJamaicaJapanJerseyJordanKazakhstanKenyaKiribatiKorea, Democratic People'S Republic ofKorea, Republic ofKuwaitKyrgyzstanLao People'S Democratic RepublicLatviaLebanonLesothoLiberiaLibyan Arab JamahiriyaLiechtensteinLithuaniaLuxembourgMacaoMacedonia, The Former Yugoslav Republic ofMadagascarMalawiMalaysiaMaldivesMaliMaltaMarshall IslandsMartiniqueMauritaniaMauritiusMayotteMexicoMicronesia, Federated States ofMoldova, Republic ofMonacoMongoliaMontserratMoroccoMozambiqueMyanmarNamibiaNauruNepalNetherlandsNetherlands AntillesNew CaledoniaNew ZealandNicaraguaNigerNigeriaNiueNorfolk IslandNorthern Mariana IslandsNorwayOmanPakistanPalauPalestinian Territory, OccupiedPanamaPapua New GuineaParaguayPeruPhilippinesPitcairnPolandPortugalPuerto RicoQatarReunionRomaniaRussian FederationRWANDASaint HelenaSaint Kitts and NevisSaint LuciaSaint Pierre and MiquelonSaint Vincent and the GrenadinesSamoaSan MarinoSao Tome and PrincipeSaudi ArabiaSenegalSerbia and MontenegroSeychellesSierra LeoneSingaporeSlovakiaSloveniaSolomon IslandsSomaliaSouth AfricaSouth Georgia and the South Sandwich IslandsSpainSri LankaSudanSurinameSvalbard and Jan MayenSwazilandSwedenSwitzerlandSyrian Arab RepublicTaiwan, Province of ChinaTajikistanTanzania, United Republic ofThailandTimor-LesteTogoTokelauTongaTrinidad and TobagoTunisiaTurkeyTurkmenistanTurks and Caicos IslandsTuvaluUgandaUkraineUnited Arab EmiratesUnited KingdomUnited StatesUnited States Minor Outlying IslandsUruguayUzbekistanVanuatuVenezuelaViet NamVirgin Islands, BritishVirgin Islands, U.S.Wallis and FutunaWestern SaharaYemenZambiaZimbabwe      Occupation                     Your Photo         Maximum file size: 5 MB  Upload a recent passport-style portrait photo (4x4 cm). This is required for your visa application.                 Copy of Your Passport         Maximum file size: 5 MB  Upload a clear, colored copy of your valid passport (information page only). Required for visa processing.

## Additional Information:

   Massage            Travelers Details: ( Children)     First Name      Surname         Gender  MaleFemale           Birthday

Please complete the additional fields below, as they are required for processing your visa application.

                Your Photo         Maximum file size: 5 MB  Upload a recent passport-style portrait photo (4x4 cm). This is required for your visa application.                  Copy of Your Passport         Maximum file size: 5 MB  Upload a clear, colored copy of your valid passport (information page only). Required for visa processing.

## Payment Options

Choose your preferred payment method. Secure your booking by paying a fixed deposit, a percentage of the total cost, or the full amount. Balance will be collected later if you choose a deposit.

   Choose Your Payment Option Reserve with Fixed $100 Deposit Pay 10% to Confirm Pay Full Amount

Your Trip Summary

Review all selected trip details, flights, accommodations, and extras before proceeding to payment.

## Trip Details:

Trip Destination:

Trip Name:

Trip Duration:

Trip Starts:

Trip Ends:

Hotel Nights:

    Nights

Camping Nights:

    Nights

## Flights Details:

## Dep. Details:

No departure flight selected

Flight Code:

Flight ID

Flight Carrier:

flight Route

Dep. Date

Dep. Fare

   $

## Ret. Details:

No return flight selected

Flight Code:

Flight ID

Flight Carrier:

flight Route

Ret. Date

Ret. Fare

   $

## Order Details:

      0.00        Sub-Total- Adults () + Children()  0.00 PAX

**Trip Sub-total**

    U$D

**Flights Sub-total**

    U$D

**Extra Services Sub-total**

      0.00 $

Your Booking Summary shows the final total.

## Grand Total

      0.00 $

## Due Payment:

      0.00 $

## Remaining payment:

      0.00 $

##### Why Trust Us?

## ✔

Licensed local operator in Yemen

## ✔

20+ years of local experience

## ✔

Secure payments & easy refunds

## ✔

Free changes up to 30 days

By continuing you agree to our Terms & Conditions and confirm traveler information is accurate.

Questions? Contact [info@socotratravels.com](mailto:info@socotratravels.com)

Confirm your booking to add to cart.

   Back Add to Cart & Proceed to Payment

**Not ready to commit?**
You can submit a request with a small deposit or simply review your trip — our team will confirm all details with you before anything is finalized.
