import { Component, AfterViewInit, Input, ChangeDetectionStrategy, ChangeDetectorRef, OnDestroy, ElementRef, ViewChild, HostListener, NgZone } from '@angular/core';
import { trigger, transition, style, animate } from '@angular/animations';
import { HttpClient } from '@angular/common/http';
import { ActivatedRoute } from '@angular/router';
import * as Cesium from 'cesium';
import * as turf from '@turf/turf';
import { MatDialog } from '@angular/material/dialog';
import { LocationsService } from 'src/app/Services/locations.service';
import { MapService } from 'src/app/Services/map.service';
import { I18nService, SupportedLanguage } from 'src/app/Services/i18n.service';
import { SectorService } from 'src/app/Services/sector.service';
import { SidebarConfig, SidebarConfigService } from 'src/app/Services/sidebar-config.service';
import { TextStyleService, TextStyleSettings } from 'src/app/Services/text-style.service';
import { SettingsService } from 'src/app/Services/settings.service';
import { PolygonContentDialogComponent } from '../polygon-content-dialog/polygon-content-dialog.component';
import { LocationRequest } from '../Models/location-request.model';
import { LocationDTO, LocationContentDTO, LocationType } from '../Models/location.model';
import { SectorDTO } from '../Models/sector.model';
import Swal from 'sweetalert2';
import { Subject, forkJoin, of, Observable } from 'rxjs';
import { debounceTime, takeUntil, catchError, tap, distinctUntilChanged } from 'rxjs/operators';

type SidebarHierarchyView = 'district' | 'governorate' | 'project';

interface SidebarHierarchyProjectNode {
  project: LocationDTO;
  image: string | null;
  description: string;
}

interface SidebarHierarchyGovernorateNode {
  districtId: string;
  index: number;
  name: string;
  displayName: string;
  location: LocationDTO | null;
  image: string | null;
  description: string;
  projects: SidebarHierarchyProjectNode[];
}

interface SidebarHierarchyDistrictNode {
  id: string;
  name: string;
  displayName: string;
  location: LocationDTO | null;
  image: string | null;
  description: string;
  governorates: SidebarHierarchyGovernorateNode[];
}


@Component({
  selector: 'app-cesium-map',
  templateUrl: './cesium-map.component.html',
  styleUrls: ['./cesium-map.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  animations: [
    trigger('fadeInOut', [
      transition(':enter', [
        style({ opacity: 0 }),
        animate('300ms ease-out', style({ opacity: 1 }))
      ]),
      transition(':leave', [
        animate('300ms ease-in', style({ opacity: 0 }))
      ])
    ])
  ]
})
export class CesiumMapComponent implements AfterViewInit, OnDestroy {
private _inflight = new Map<string, Promise<any>>();

private deduped<T>(key: string, factory: () => Promise<T>): Promise<T> {
  if (this._inflight.has(key)) return this._inflight.get(key)!;
  const p = factory().finally(() => this._inflight.delete(key));
  this._inflight.set(key, p);
  return p;
}
  @ViewChild('mapSection', { static: false }) mapSectionRef?: ElementRef<HTMLElement>;

  @Input() hideSidebar: boolean = false;
  @Input() hideTopbar: boolean = false;
  @Input() landingMode: boolean = false;
  @Input() hideIntroOverlay: boolean = false;
  @Input() hideBrief: boolean = false;
  @Input() transparentBackground: boolean = false;
  @Input() showIntroUI: boolean = true; // controls progress bar/title visibility (rotation always runs if runIntroNightTheme=true)
  @Input() showBlueBackdrop: boolean = true; // controls whether blue backdrop is applied
  private _allowPointerEvents: boolean = true;
  @Input() set allowPointerEvents(v: boolean) { this._allowPointerEvents = !!v; this.updatePointerEvents(); }
  get allowPointerEvents(): boolean { return this._allowPointerEvents; }
toggleControlsMenu(): void {
  this.controlsMenuOpen = !this.controlsMenuOpen;
  this.cdr.detectChanges(); // immediate, bypasses debounce
}
  // If true, run the intro rotation animation. Night imagery is applied separately
  // so the globe can stay in night mode while the background remains soft blue.
  // Default enabled so the globe performs the intro spin before showing.
  runIntroNightTheme: boolean = true;
  // Progress bar for intro animation
  introProgress: number = 0; // 0 to 100
  showIntroOverlay: boolean = false; // Show loading overlay during intro
  introPhase: 'idle' | 'rotating' | 'complete' = 'idle';
private _projectsCache: { mapId: number; data: LocationDTO[]; ts: number } | null = null;
private _locationDetailsCache = new Map<number, { data: LocationDTO; ts: number }>();
private readonly CACHE_TTL_MS = 5 * 60 * 1000; 
  viewer!: Cesium.Viewer;
  bingLayer: Cesium.ImageryLayer | null = null;
  currentMap!: any;
  isDefaultMap: boolean = false;
  countryLocationId: number | null = null;
  defaultMapCountry: string = '';
  selectedSector: any = null;
  drawingPolygon = false;
  drawingPoint = false;
  polygonPoints: number[][] = [];
  tempPolygonEntity: Cesium.Entity | null = null;
  tempPointEntities: Cesium.Entity[] = [];
  currentGovernorateIndex: number = 0;
  selectedName: string | null = null;
  selectedContents: LocationContentDTO[] = [];
  selectedLocation: LocationDTO | null = null;
  currentZoneChildPoints: LocationDTO[] = [];
  currentZoneId: number | null = null;
private _selectedPinEntity: Cesium.Entity | null = null;
private _selectedPinOriginalImage: string | null = null;
private _selectedPinOriginalScale: number | null = null;
  allLocations: LocationDTO[] = [];
  filteredLocations: LocationDTO[] = [];
  navigableLocations: LocationDTO[] = [];
  selectedFilter: string = 'ALL';
  showLocationsList: boolean = true;
  currentLocationIndex: number = 0;
  showRightSidebar: boolean = false;
  private sidebarForceHidden: boolean = false;
  selectedItem: Record<string, any> | null = null;
  isAnimating: boolean = false;
  selectedRightSidebarTab: string = 'ALL';
  filteredDistricts: any[] = [];
  filteredGovernorates: any[] = [];
  allDistricts: any[] = [];
  allGovernorates: any[] = [];
  openSectors: Set<number> = new Set();
  allSectors: SectorDTO[] = [];
  allProjects: LocationDTO[] = [];
  projectsBySector: Map<number, LocationDTO[]> = new Map();
  filteredSectors: SectorDTO[] = [];
  selectedProject: LocationDTO | null = null;
  displayedProjectEntities: Cesium.Entity[] = [];
  currentZoneProjectEntities: Cesium.Entity[] = [];
  currentZoneProjectZoneId: number | string | null = null;
  private highlightedProjectEntity: Cesium.Entity | null = null;
  private readonly projectHighlightScale = 0.65;
  countryBorders: any[] = [];
  displayedCountryBorders: Cesium.Entity[] = [];
  countryLabelEntities: Cesium.Entity[] = [];
  districtLabelEntities: Cesium.Entity[] = [];
  displayedLocationEntities: Cesium.Entity[] = [];
  selectedCountry: string = '';
  selectedDistrict: { id: string; meta: any } | null = null;
  activeSidebarItemKey: string | null = null;
  currentLanguage: SupportedLanguage = 'en';
  districtLabelData: Map<string, { lon: number; lat: number; meta: any }> = new Map();
  tunisiaDistrictMatchedFeatures: { [districtId: string]: any[] } = {};
  displayedDistrictEntities: Cesium.Entity[] = [];
  displayedDistrictDataSources: Cesium.DataSource[] = [];
  zoneChildMap: Map<number, LocationDTO[]> = new Map();
  zoneChildEntities: Map<number, Cesium.Entity[]> = new Map();
  private governorateEntities: Map<string, Cesium.Entity> = new Map<string, Cesium.Entity>();
  private districtEntities: Map<string, Cesium.Entity> = new Map<string, Cesium.Entity>();
  private mapEventHandler: Cesium.ScreenSpaceEventHandler | null = null;
  private grabbedProjectEntityId: any = null;
  private tunisiaGovernorateOutlineEntities: Cesium.Entity[] = [];
  private districtPolygonEntities: Map<string, Cesium.Entity[]> = new Map<string, Cesium.Entity[]>();
  private districtPolygonBaseFillColor: WeakMap<Cesium.Entity, Cesium.Color> = new WeakMap<Cesium.Entity, Cesium.Color>();
  private districtPolygonBaseOutlineColor: WeakMap<Cesium.Entity, Cesium.Color> = new WeakMap<Cesium.Entity, Cesium.Color>();
  private districtPolygonBaseOutlineWidth: WeakMap<Cesium.Entity, number> = new WeakMap<Cesium.Entity, number>();
  private hoveredDistrictId: string | null = null;
  districtHoverPopupVisible: boolean = false;
  districtHoverPopupText: string = '';
  districtHoverPopupX: number = 0;
  districtHoverPopupY: number = 0;
  districtHoverPopupBackground: string = 'rgba(0, 230, 255, 0.9)';
  districtHoverPopupBorder: string = 'rgba(255, 255, 255, 0.85)';
  districtHoverPopupTextColor: string = '#032b38';
  districtHoverPopupShadow: string = '0 0 0 1px rgba(0, 0, 0, 0.18), 0 0 38px rgba(0, 230, 255, 0.85)';

  private readonly districtBaseOutlineColor = Cesium.Color.WHITE.withAlpha(0.95);
  private readonly districtBaseOutlineWidth = 3;
  private readonly sidebarAnimationDurationMs: number = 300;
  private sidebarAnimationToken: number = 0;

  defaultMapBorderColor: string = '#FFFF00';
  defaultMapFillOpacity: number = 0.3;
  defaultMapBorderWidth: number = 2;
  defaultCameraPosition: any = null;
  // Solid backdrop color used behind the globe (replaces previous sunset image)
  private readonly globeBackdropImageUrl: string = '#08044C';
  // Governorate pins: dark blue
  private readonly governoratePinColor: string = '#051f4a';
  // District pins: gold
  private readonly districtPinColor: string = '#d4af37';
  // Project dots: very dark red / burgundy
  private readonly projectDotColor: string = '#382428';
  // Render diamond pin images at high source resolution, then downscale in billboard settings.
  private readonly diamondPinSourceSize = 512;
  private readonly diamondPinBillboardBaseSize = 160;
  private readonly governoratePinScale = 0.60;
  private readonly projectPinScale = 0.40;
  private readonly zonePinScale = 0.4;
  // Default thumbnail used for districts/governorates/projects when no custom image set
  defaultLocationImageUrl: string = 'assets/district-images/default-location.png';
  private readonly focusCountryName: string = 'Tunisia';
  private readonly tunisiaUprightDuration = 1.8;
  private readonly tunisiaArabicUprightRectangle = Cesium.Rectangle.fromDegrees(5.9, 29.8, 11.9, 38.0);
  private readonly tunisiaNonArabicUprightRectangle = Cesium.Rectangle.fromDegrees(6.5, 29.8, 12.8, 38.0);
  private readonly tunisiaMinOverviewHeight = 980000;

  // ─── SINGLE SOURCE OF TRUTH: sidebar-visible projects ───────────────────────
  // This array drives BOTH the sidebar list AND the map pins.
  // Never read allProjects directly for display — always read sidebarProjects.
  sidebarProjects: LocationDTO[] = [];
  private sidebarProjectsBase: LocationDTO[] = [];
  sidebarSearchTerm: string = '';
  sidebarConfig: SidebarConfig;
  private sidebarAutoHideTimer: ReturnType<typeof setTimeout> | null = null;

  private selectedEntityChangedListener?: (entity: Cesium.Entity | undefined) => void;
  private cameraChangedListener?: () => void;
  private isApplyingTunisiaOrientationLock: boolean = false;
  private hasCompletedTunisiaIntroSpin: boolean = false;
  private governorateFocusRequestId: number = 0;
  hierarchyState: {
    view: SidebarHierarchyView | null;
    selectedDistrict: SidebarHierarchyDistrictNode | null;
    selectedGovernorateIndex: number;
    selectedProjectIndex: number;
  } = {
    view: null,
    selectedDistrict: null,
    selectedGovernorateIndex: 0,
    selectedProjectIndex: 0,
  };
  hierarchyTransition: 'idle' | 'slide-in' | 'slide-out' = 'idle';
  private readonly hierarchyTransitionMs: number = 220;
  private hierarchyTransitionToken: number = 0;
  private hierarchyTransitionTimer: ReturnType<typeof setTimeout> | null = null;
  private backdropPreRenderListener?: () => void;
  private infoBoxObserver: MutationObserver | null = null;

  unifiedNavList: Array<{
    type: 'district' | 'governorate' | 'location' | 'project';
    id: string | number;
    label: string;
    data: any;
  }> = [];
  unifiedNavIndex: number = -1;

  private pinIconCache = new Map<string, string>();
  private changeDetectionSubject = new Subject<void>();
  private mapClickSubject = new Subject<any>();
  private destroy$ = new Subject<void>();
  private isHandlingMapClick: boolean = false;
  private textStyleSettings: TextStyleSettings = {};
  controlsMenuOpen: boolean = false;
  showBrief: boolean = false;
  legendVisible: boolean = false;
  legendDragging: boolean = false;
  legendPosition: { x: number; y: number } = { x: 16, y: 112 };
  private legendDragOffset: { x: number; y: number } = { x: 0, y: 0 };
  private legendPointerMode: 'mouse' | 'touch' | null = null;
  isMapFocused: boolean = false;
  isMapFullscreen: boolean = false;
  private mapScrollPassthroughBound: boolean = false;
  private mapTouchScrollLastY: number | null = null;
  // When the browser fullscreen API makes the Cesium canvas the fullscreen element,
  // move the sidebar DOM node into that fullscreen element so it remains visible.
  private fullscreenSidebarOriginalParent: Node | null = null;
  private fullscreenSidebarOriginalNextSibling: Node | null = null;
  // Mobile responsive: expand sidebar to occupy full map section
  mobileSidebarExpanded: boolean = false;

  // ════════════════════════════════════════════════════════════════════════════
  // UNIFIED ZOOM CONSTANTS — ONE set of values, used EVERYWHERE
  // ════════════════════════════════════════════════════════════════════════════
private readonly ZOOM_HEIGHT   = 1_000_000;                  // metres
  private readonly ZOOM_PITCH    = -Cesium.Math.PI_OVER_TWO;   // keep Tunisia upright/top-down
  private readonly ZOOM_HEADING  = 0;
  private readonly ZOOM_DURATION = 0.8;                          // seconds
  // Higher values reduce zoom intensity and keep more context visible.
 private readonly GOVERNORATE_ZOOM_HEIGHT = 800_000;           // metres (higher = more context)
private readonly DISTRICT_OVERVIEW_ZOOM_HEIGHT = 800_000    // metres (higher = more context)
private readonly PROJECT_PINS_ZOOM_HEIGHT = 800_000;       // metres
  // Explore More next/previous for projects should zoom in closer.
  private readonly EXPLORE_PROJECT_ZOOM_HEIGHT = 300_000;      // metres
  private readonly BUTTON_ZOOM_IN_FACTOR = 0.72;
  private readonly BUTTON_ZOOM_OUT_FACTOR = 1.38;
  private readonly MIN_BUTTON_ZOOM_HEIGHT = 25_000;
  private readonly MAX_BUTTON_ZOOM_HEIGHT = 25_000_000;
  private readonly NEON_FILL_ALPHA = 0.72;
  private readonly NEON_HOVER_FILL_ALPHA = 0.5;
  private readonly NEON_OUTLINE_WIDTH = 18;
  private readonly NEON_GOV_OUTLINE_WIDTH = 6;
  private readonly GLOW_ANIM_MS = 280;
  private readonly BASE_GOV_OUTLINE_WIDTH = 1.5;
  private readonly BASE_GOV_OUTLINE_ALPHA = 0.55;
  private readonly NEON_EDGE_COLOR = Cesium.Color.fromCssColorString('#20fff4');
  private readonly NEON_SELECTED_FILL_COLOR = Cesium.Color.fromCssColorString('#ffe54f');
  private selectedGlowDistrictId: string | null = null;
  private _bloomHaloEntities: Map<Cesium.Entity, Cesium.Entity[]> = new Map();
  constructor(
    private locationsService: LocationsService,
    private mapService: MapService,
    private i18nService: I18nService,
    private sectorService: SectorService,
    private sidebarConfigService: SidebarConfigService,
    private dialog: MatDialog,
    private route: ActivatedRoute,
    private http: HttpClient,
    private cdr: ChangeDetectorRef,
    private textStyleService: TextStyleService,
    private settingsService: SettingsService
    ,
    private ngZone: NgZone
  ) {
    this.sidebarConfig = this.sidebarConfigService.getConfig();

    this.changeDetectionSubject
      .pipe(debounceTime(16), takeUntil(this.destroy$))
      .subscribe(() => { this.cdr.detectChanges(); });

    this.sidebarConfigService.getConfig$()
      .pipe(takeUntil(this.destroy$))
      .subscribe(() => {
        this.applyResolvedSidebarConfig();
        this.scheduleChangeDetection();
      });

    // Subscribe to text-style updates (font color/size/weight) and apply immediately
    this.textStyleService.settings$
      .pipe(takeUntil(this.destroy$))
      .subscribe(settings => {
        const fallbackColor = this.sidebarConfig?.textColor ?? 'rgba(255, 255, 255, 0.92)';
        const fallbackSize = this.sidebarConfig?.fontSizePx ?? 13;
        this.textStyleSettings = {
          fontColor: settings?.fontColor ?? fallbackColor,
          fontSize: settings?.fontSize ?? fallbackSize,
          fontWeight: settings?.fontWeight ?? 'normal',
        };
        // eslint-disable-next-line no-console
        console.debug('CesiumMap: received textStyleSettings', this.textStyleSettings);
          console.log('CesiumMap: received textStyleSettings', this.textStyleSettings);
        // ensure change detection runs immediately so styles apply
        try { this.cdr.markForCheck(); } catch (e) {}
        this.scheduleChangeDetection();
      });

    // Subscribe to layout settings (showBrief / layoutMode)
    this.settingsService.settings$
      .pipe(takeUntil(this.destroy$))
      .subscribe(s => {
        const resolvedLayoutMode = s?.layoutMode ?? (s?.showBrief ? 'split' : 'full');
        this.showBrief = !this.hideBrief && !!s?.showBrief && resolvedLayoutMode === 'split';
        if (!this.showBrief) {
          this.isMapFocused = false;
        }

        // Apply brief customization values
        this.briefBgColor = s?.briefBgColor ?? '#CFE8FF';
        this.briefText = s?.briefText ?? '';
        this.briefTextColor = s?.briefTextColor ?? '#032b38';

        // Sidebar overlay effect settings
        this.sidebarOverlayEffect = s?.sidebarOverlayEffect ?? 'none';
        this.sidebarOverlayOpacity = (typeof s?.sidebarOverlayOpacity === 'number') ? s.sidebarOverlayOpacity : 0.5;

        // Do not auto-hide split when interacting with the map or when sidebar opens.
        // The blue split is governed only by `showBrief` and user actions.

        this.resizeViewerAsync();
        this.scheduleChangeDetection();
      });
  }
  private isCacheValid(ts: number): boolean {
  return (Date.now() - ts) < this.CACHE_TTL_MS;
}

private invalidateProjectsCache(): void {
  this._projectsCache = null;
}

private invalidateLocationCache(id: number): void {
  this._locationDetailsCache.delete(id);
}

  private resizeViewerAsync(): void {
    setTimeout(() => {
      try {
        if (this.viewer && (this.viewer as any).resize) {
          (this.viewer as any).resize();
        }
      } catch {}
    }, 0);
  }

  // Hint popup controls: always visible; buttons explicitly toggle split
  // brief customization state (bound from SettingsService)
  briefBgColor: string = '#CFE8FF';
  briefText: string = '';
  briefTextColor: string = '#032b38';
  // Sidebar overlay effect (bound from SettingsService)
  sidebarOverlayEffect: string = 'none';
  sidebarOverlayOpacity: number = 0.5;

  toggleSplitFromPopup(): void {
    try {
      if (this.showBrief) {
        this.settingsService.updateSettings({ showBrief: false, layoutMode: 'full' });
      } else {
        this.settingsService.updateSettings({ showBrief: true, layoutMode: 'split' });
      }
    } catch (e) {}
  }

  toggleMapFullscreen(): void {
    this.isMapFullscreen = !this.isMapFullscreen;
    if (this.isMapFullscreen) {
      this.legendVisible = true;
    }
    this.syncMapFullscreenState();
  }

  @HostListener('document:fullscreenchange')
  @HostListener('document:webkitfullscreenchange')
  @HostListener('document:msfullscreenchange')
  onBrowserFullscreenChange(): void {
    const browserFs = this.isBrowserFullscreen();
    // Mirror browser fullscreen state to component state so click logic works
    if (browserFs !== this.isMapFullscreen) {
      this.isMapFullscreen = browserFs;
      if (browserFs) {
        this.legendVisible = true;
      }
      this.syncMapFullscreenState();
    }

    try {
      const fsEl: any = document.fullscreenElement || (document as any).webkitFullscreenElement || (document as any).msFullscreenElement || null;
      const sidebar = document.querySelector('.info-panel') as HTMLElement | null;
      if (fsEl && sidebar) {
        // If the fullscreen element does not already contain the sidebar, move it into the fullscreen element
        if (!fsEl.contains(sidebar)) {
          this.fullscreenSidebarOriginalParent = sidebar.parentNode;
          this.fullscreenSidebarOriginalNextSibling = sidebar.nextSibling;
          try { fsEl.appendChild(sidebar); } catch (e) { /* ignore DOM move failures */ }
        }
      } else if (!fsEl && sidebar && this.fullscreenSidebarOriginalParent) {
        // Exiting fullscreen: attempt to restore original position
        try {
          if (this.fullscreenSidebarOriginalNextSibling && this.fullscreenSidebarOriginalParent) {
            this.fullscreenSidebarOriginalParent.insertBefore(sidebar, this.fullscreenSidebarOriginalNextSibling);
          } else if (this.fullscreenSidebarOriginalParent) {
            this.fullscreenSidebarOriginalParent.appendChild(sidebar);
          }
        } catch (e) { /* ignore restore failures */ }
        this.fullscreenSidebarOriginalParent = null;
        this.fullscreenSidebarOriginalNextSibling = null;
      }
    } catch (e) {
      // ignore errors from DOM operations in environments without full DOM
    }
  }

  private isBrowserFullscreen(): boolean {
    try {
      const doc: any = document as any;
      return !!(document.fullscreenElement || doc.webkitFullscreenElement || doc.msFullscreenElement || false);
    } catch (e) { return false; }
  }

  private syncMapFullscreenState(): void {
    this.setMapZoomInteractionFromFullscreenState();
    this.bindMapScrollPassthrough();
    if (this.legendVisible) this.keepLegendWithinBounds();
    this.resizeViewerAsync();
    this.scheduleChangeDetection();
  }

  private bindMapScrollPassthrough(): void {
    if (this.mapScrollPassthroughBound) return;

    try {
      const container = document.getElementById('cesiumContainer');
      if (!container) return;
      container.addEventListener('touchstart', (evt: TouchEvent) => {
        if (evt.touches && evt.touches.length === 1) {
          this.mapTouchScrollLastY = evt.touches[0].clientY;
        } else {
          this.mapTouchScrollLastY = null;
        }
      }, { capture: true, passive: true } as AddEventListenerOptions);

      container.addEventListener('touchmove', (evt: TouchEvent) => {
        if (this.isMapFullscreen) return;
        if (!evt.touches || evt.touches.length !== 1) {
          this.mapTouchScrollLastY = null;
          return;
        }

        const currentY = evt.touches[0].clientY;
        const previousY = this.mapTouchScrollLastY ?? currentY;
        const deltaY = previousY - currentY;
        this.mapTouchScrollLastY = currentY;

        if (Math.abs(deltaY) < 0.5) return;
        if (evt.cancelable) evt.preventDefault();
        try { evt.stopImmediatePropagation(); } catch (e) {}
        this.scrollNearestContainer(deltaY);
      }, { capture: true, passive: false } as AddEventListenerOptions);

      container.addEventListener('touchend', () => {
        this.mapTouchScrollLastY = null;
      }, { capture: true, passive: true } as AddEventListenerOptions);

      container.addEventListener('touchcancel', () => {
        this.mapTouchScrollLastY = null;
      }, { capture: true, passive: true } as AddEventListenerOptions);

      this.mapScrollPassthroughBound = true;
    } catch (e) {
      // Ignore passthrough binding failures and keep map usable.
    }
  }

  private scrollNearestContainer(deltaY: number): void {
    const mapHost = this.mapSectionRef?.nativeElement;
    const scrollable = this.findScrollableAncestor(mapHost);
    if (scrollable) {
      scrollable.scrollTop += deltaY;
      return;
    }

    window.scrollBy({ top: deltaY, left: 0, behavior: 'auto' });
  }

  private findScrollableAncestor(start: HTMLElement | undefined): HTMLElement | null {
    let current: HTMLElement | null = start?.parentElement ?? null;
    while (current) {
      const overflowY = window.getComputedStyle(current).overflowY;
      const canScroll = (overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay');
      if (canScroll && current.scrollHeight > current.clientHeight + 1) {
        return current;
      }
      current = current.parentElement;
    }

    const scrollingElement = document.scrollingElement as HTMLElement | null;
    if (scrollingElement && scrollingElement.scrollHeight > scrollingElement.clientHeight + 1) {
      return scrollingElement;
    }

    return null;
  }


  onMapClick(): void {
    this.isMapFocused = true;
    const browserFs = this.isBrowserFullscreen();
    if ((this.isMapFullscreen || browserFs) && !this.showRightSidebar) {
      this.showRightSidebar = true;
      this.showLocationsList = true;
      this.applyRightSidebarFilter();
    }
    this.resizeViewerAsync();
    this.scheduleChangeDetection();
  }

  toggleLegendVisibility(): void {
    this.legendVisible = !this.legendVisible;
    if (!this.legendVisible) {
      this.stopLegendDrag();
    } else {
      this.placeLegendAtBottom();
    }
    this.scheduleChangeDetection();
  }

  startLegendDrag(event: MouseEvent | TouchEvent): void {
    if (!this.legendVisible) return;
    const target = event.target as HTMLElement | null;
    if (target?.closest('.legend-close')) return;
    const point = this.getPointerClientPoint(event);
    const mapRect = this.getMapSectionRect();
    if (!point || !mapRect) return;

    const legendElement = document.querySelector('.map-section .map-legend-fixed') as HTMLElement | null;
    const legendRect = legendElement?.getBoundingClientRect();
    const legendLeft = legendRect ? legendRect.left - mapRect.left : this.legendPosition.x;
    const legendTop = legendRect ? legendRect.top - mapRect.top : this.legendPosition.y;

    this.legendDragOffset = {
      x: point.x - (mapRect.left + legendLeft),
      y: point.y - (mapRect.top + legendTop),
    };
    this.legendPointerMode = event instanceof MouseEvent ? 'mouse' : 'touch';
    this.legendDragging = true;
    if (event.cancelable) event.preventDefault();
    this.scheduleChangeDetection();
  }

  @HostListener('document:mousemove', ['$event'])
  onLegendMouseMove(event: MouseEvent): void {
    if (this.legendPointerMode !== 'mouse' || !this.legendDragging) return;
    this.moveLegendToClient(event.clientX, event.clientY);
  }

  @HostListener('document:mouseup')
  onLegendMouseUp(): void {
    if (this.legendPointerMode === 'mouse') this.stopLegendDrag();
  }

  @HostListener('document:touchmove', ['$event'])
  onLegendTouchMove(event: TouchEvent): void {
    if (this.legendPointerMode !== 'touch' || !this.legendDragging) return;
    const touch = event.touches?.[0];
    if (!touch) return;
    this.moveLegendToClient(touch.clientX, touch.clientY);
    if (event.cancelable) event.preventDefault();
  }

  @HostListener('document:touchend')
  @HostListener('document:touchcancel')
  onLegendTouchEnd(): void {
    if (this.legendPointerMode === 'touch') this.stopLegendDrag();
  }

  @HostListener('window:resize')
  onWindowResize(): void {
    if (this.legendVisible) {
      this.keepLegendWithinBounds();
      this.scheduleChangeDetection();
    }
  }

 getMapPlanBannerStyle(): Record<string, string> {
  const sidebarOnRight = this.currentLanguage === 'ar';
  const sidebarGap = this.isSidebarVisible ? 'calc(var(--panel-overlay-effective-width) + 20px)' : '20px';
  const isNarrowScreen = typeof window !== 'undefined' && window.innerWidth <= 900;

  return {
    bottom: isNarrowScreen && this.isSidebarVisible ? 'calc(50vh + 20px)' : '20px',
    left: sidebarOnRight ? '20px' : sidebarGap,
    right: sidebarOnRight ? sidebarGap : '20px',
    top: 'auto',
    transform: 'none',
    'max-width': isNarrowScreen
      ? 'calc(100% - 40px)'
      : this.isSidebarVisible
        ? 'calc(100% - var(--panel-overlay-effective-width) - 40px)'
        : 'min(760px, calc(100% - 36px))',
    'text-align': sidebarOnRight ? 'left' : 'right',
  };
}

  private isSidebarAnchoredRight(): boolean {
    return this.isSidebarVisible && this.currentLanguage === 'ar';
  }

  private isSidebarAnchoredLeft(): boolean {
    return this.isSidebarVisible && this.currentLanguage !== 'ar';
  }

  private moveLegendToClient(clientX: number, clientY: number): void {
    const mapRect = this.getMapSectionRect();
    if (!mapRect) return;

    const legendElement = document.querySelector('.map-section .map-legend-fixed') as HTMLElement | null;
    const legendWidth = legendElement?.offsetWidth || 280;
    const legendHeight = legendElement?.offsetHeight || 154;
    const reservedRight = this.getRightSidebarReservedWidth(mapRect);
    const margin = 12;
    const minX = margin;
    const maxX = Math.max(minX, mapRect.width - legendWidth - reservedRight - margin);
    const minY = 66;
    const maxY = Math.max(minY, mapRect.height - legendHeight - margin);

    const x = Math.round(Math.min(maxX, Math.max(minX, clientX - mapRect.left - this.legendDragOffset.x)));
    const y = Math.round(Math.min(maxY, Math.max(minY, clientY - mapRect.top - this.legendDragOffset.y)));
    this.legendPosition = { x, y };
    this.scheduleChangeDetection();
  }

  private keepLegendWithinBounds(): void {
    const mapRect = this.getMapSectionRect();
    if (!mapRect) return;

    const legendElement = document.querySelector('.map-section .map-legend-fixed') as HTMLElement | null;
    const legendWidth = legendElement?.offsetWidth || 280;
    const legendHeight = legendElement?.offsetHeight || 154;
    const reservedRight = this.getRightSidebarReservedWidth(mapRect);
    const margin = 12;
    const minX = margin;
    const maxX = Math.max(minX, mapRect.width - legendWidth - reservedRight - margin);
    const minY = 66;
    const maxY = Math.max(minY, mapRect.height - legendHeight - margin);

    this.legendPosition = {
      x: Math.round(Math.min(maxX, Math.max(minX, this.legendPosition.x))),
      y: Math.round(Math.min(maxY, Math.max(minY, this.legendPosition.y))),
    };
  }

  private placeLegendAtBottom(): void {
    const mapRect = this.getMapSectionRect();
    if (!mapRect) return;

    const legendElement = document.querySelector('.map-section .map-legend-fixed') as HTMLElement | null;
    const legendWidth = legendElement?.offsetWidth || 280;
    const legendHeight = legendElement?.offsetHeight || 154;
    const reservedRight = this.getRightSidebarReservedWidth(mapRect);
    const margin = 12;
    const minX = margin;
    const maxX = Math.max(minX, mapRect.width - legendWidth - reservedRight - margin);
    const minY = 66;
    const maxY = Math.max(minY, mapRect.height - legendHeight - margin);

    const defaultX = this.currentLanguage === 'ar' ? maxX : minX;
    this.legendPosition = {
      x: Math.round(Math.min(maxX, Math.max(minX, defaultX))),
      y: Math.round(maxY),
    };
  }

  private getMapSectionRect(): DOMRect | null {
    const mapElement = this.mapSectionRef?.nativeElement;
    return mapElement ? mapElement.getBoundingClientRect() : null;
  }

  private getRightSidebarReservedWidth(mapRect: DOMRect): number {
    if (!this.isSidebarAnchoredRight()) return 0;
    const sidebar = document.querySelector('.map-section .info-panel.panel-visible') as HTMLElement | null;
    if (!sidebar) return 0;
    const sidebarRect = sidebar.getBoundingClientRect();
    return Math.max(0, mapRect.right - sidebarRect.left + 8);
  }

  private getPointerClientPoint(event: MouseEvent | TouchEvent): { x: number; y: number } | null {
    if (event instanceof MouseEvent) {
      return { x: event.clientX, y: event.clientY };
    }
    const touch = event.touches?.[0] || event.changedTouches?.[0];
    if (!touch) return null;
    return { x: touch.clientX, y: touch.clientY };
  }

  private stopLegendDrag(): void {
    if (!this.legendDragging) return;
    this.legendDragging = false;
    this.legendPointerMode = null;
    this.scheduleChangeDetection();
  }

  openSectorIds: Set<number> = new Set<number>();
  pendingFocus: string | null = null;

  get isSidebarVisible(): boolean { return this.showRightSidebar && !this.sidebarForceHidden; }

  ngOnDestroy() {
    if (this.mapEventHandler) {
      this.mapEventHandler.destroy();
      this.mapEventHandler = null;
    }
    if (this.viewer && this.selectedEntityChangedListener) {
      this.viewer.selectedEntityChanged.removeEventListener(this.selectedEntityChangedListener);
      this.selectedEntityChangedListener = undefined;
    }
    if (this.viewer && this.cameraChangedListener) {
      this.viewer.camera.changed.removeEventListener(this.cameraChangedListener);
      this.cameraChangedListener = undefined;
    }
    if (this.hierarchyTransitionTimer) {
      clearTimeout(this.hierarchyTransitionTimer);
      this.hierarchyTransitionTimer = null;
    }
    if (this.sidebarAutoHideTimer) {
      clearTimeout(this.sidebarAutoHideTimer);
      this.sidebarAutoHideTimer = null;
    }
    if (this.viewer && this.backdropPreRenderListener) {
      this.viewer.scene.preRender.removeEventListener(this.backdropPreRenderListener);
      this.backdropPreRenderListener = undefined;
    }
    if (this.infoBoxObserver) {
      this.infoBoxObserver.disconnect();
      this.infoBoxObserver = null;
    }
    this.destroy$.next();
    this.destroy$.complete();
    this.mapClickSubject.complete();
    this.pinIconCache.clear();
  }

  toggleMobileSidebar(): void {
    this.sidebarForceHidden = false;
    if (!this.showRightSidebar) {
      this.showRightSidebar = true;
    }
    this.mobileSidebarExpanded = !this.mobileSidebarExpanded;
    this.scheduleChangeDetection();
  }

  private applyResolvedSidebarConfig(): void {
    const resolved = this.currentMap?.id
        ? this.sidebarConfigService.getConfigForMap(this.currentMap.id)
        : this.sidebarConfigService.getConfig();
      this.sidebarConfig = { ...resolved };
  }


  /** Close the sidebar completely in responsive mode */
  closeSidebar(): void {
    this.mobileSidebarExpanded = false;
    this.clearSelection(true);
    this.sidebarForceHidden = true;
    this.scheduleChangeDetection();
  }

  /** Toggle sidebar visibility in responsive mode */
  toggleSidebar(): void {
    this.sidebarForceHidden = false;
    this.showRightSidebar = !this.showRightSidebar;
    if (!this.showRightSidebar) {
      this.mobileSidebarExpanded = false;
    }
    this.scheduleChangeDetection();
  }

  getSidebarPanelClassMap(): Record<string, boolean> {
    const sidebarOnRight = this.currentLanguage === 'ar';
    return {
      'position-inside-right': sidebarOnRight,
      'position-inside-left': !sidebarOnRight,
      'position-left': !sidebarOnRight,
      'position-right': sidebarOnRight,
      'position-top': this.sidebarConfig.position === 'top',
      'position-bottom': this.sidebarConfig.position === 'bottom',
      'sidebar-left': !sidebarOnRight,
      'sidebar-right': sidebarOnRight,
      'sidebar-top': this.sidebarConfig.position === 'top',
      'sidebar-bottom': this.sidebarConfig.position === 'bottom',
      'anim-fade': this.sidebarConfig.animationStyle === 'fade',
      'anim-expand': this.sidebarConfig.animationStyle === 'expand',
      // overlay effect classes (applied to .info-panel)
      'overlay-smoke': this.sidebarOverlayEffect === 'smoke',
      'overlay-dark': this.sidebarOverlayEffect === 'dark',
      'overlay-gradient': this.sidebarOverlayEffect === 'gradient',
      'overlay-glass': this.sidebarOverlayEffect === 'glass',
      'overlay-white': this.sidebarOverlayEffect === 'white',
      'overlay-opacity': this.sidebarOverlayEffect === 'opacity'
    };
  }

  getSidebarPanelStyle(): Record<string, string> {
    const sidebarOnRight = this.currentLanguage === 'ar';
    const sidebarWidth = `calc(${this.sidebarConfig.widthVw * 1.85}vw + 130px)`;
    const style: Record<string, string> = {
      '--panel-overlay-width': sidebarWidth,
      '--sidebar-width': sidebarWidth,
      width: sidebarWidth,
      'max-width': sidebarWidth,
      'font-family': this.sidebarConfig.fontFamily,
    };

    // Source of truth: SidebarConfig from Map Settings.
    // TextStyleService is fallback only.
    const resolvedFontSize = (this.sidebarConfig?.fontSizePx ?? this.textStyleSettings?.fontSize ?? 13);
    const resolvedColor = (this.sidebarConfig?.textColor ?? this.textStyleSettings?.fontColor ?? 'rgba(255, 255, 255, 0.92)');
    const resolvedWeight = (this.textStyleSettings?.fontWeight ?? 'normal');

    style['--sidebar-dynamic-text-color'] = resolvedColor;
    style['--sidebar-font-size'] = `${resolvedFontSize}px`;
    style['font-size'] = `${resolvedFontSize}px`;
    style['color'] = resolvedColor;
    style['font-weight'] = resolvedWeight;
    style['text-align'] = sidebarOnRight ? 'right' : 'left';
    style['direction'] = sidebarOnRight ? 'rtl' : 'ltr';

    if (this.sidebarConfig.backgroundType === 'solid') {
      style['--sidebar-background'] = this.sidebarConfig.backgroundColor;
    } else if (this.sidebarConfig.backgroundImage) {
      const opacity = (this.sidebarConfig as any).backgroundImageOpacity ?? 0.9;
      style['background-image'] = `linear-gradient(rgba(0,0,0,${1 - opacity}), rgba(0,0,0,${1 - opacity})), url(${this.sidebarConfig.backgroundImage})`;
      style['background-size'] = 'cover';
      style['background-position'] = 'center';
      // ensure underlying panel background is transparent so image shows through
      style['--sidebar-background'] = 'transparent';
    }

    // expose overlay opacity for the overlay effect classes
    style['--overlay-opacity'] = String((this.sidebarOverlayOpacity ?? 0.5));

    return style;
  }

  private clearSidebarAutoHideTimer(): void {
    if (!this.sidebarAutoHideTimer) return;
    clearTimeout(this.sidebarAutoHideTimer);
    this.sidebarAutoHideTimer = null;
  }

  private scheduleSidebarAutoHide(): void {
    this.clearSidebarAutoHideTimer();
    // Keep sidebar state fully user-driven: no automatic hide/reopen behavior.
  }

  private scheduleChangeDetection() { this.changeDetectionSubject.next(); }

  private normalizeSelectionName(value: string): string {
    return (value || '').toLowerCase().replace(/\s+/g, '').normalize('NFD').replace(/\p{Diacritic}/gu, '');
  }

  private wait(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  private async animateSidebarForMapSelection(
    selectedItem: Record<string, any>,
    applySelection: () => Promise<void> | void
  ): Promise<void> {
    const token = ++this.sidebarAnimationToken;
    this.selectedItem = selectedItem;
    this.isAnimating = true;
    const shouldReplaySidebar = this.showRightSidebar && !this.isMapFullscreen && !this.isBrowserFullscreen();

    try {
      if (shouldReplaySidebar) {
        this.sidebarForceHidden = true;
        this.scheduleChangeDetection();
        await this.wait(Math.min(180, this.sidebarAnimationDurationMs));
        if (token !== this.sidebarAnimationToken) return;
      }

      await Promise.resolve(applySelection());
      if (token !== this.sidebarAnimationToken) return;

      if (!this.showRightSidebar) {
        this.showRightSidebar = true;
      }

      if (shouldReplaySidebar) {
        await this.wait(40);
        if (token !== this.sidebarAnimationToken) return;
      }

      this.scheduleSidebarAutoHide();
    } finally {
      if (token === this.sidebarAnimationToken) {
        this.sidebarForceHidden = false;
        this.isAnimating = false;
        this.scheduleChangeDetection();
      }
    }
  }

  private registerDistrictPolygonEntity(districtId: string, entity: Cesium.Entity): void {
    const existing = this.districtPolygonEntities.get(districtId) || [];
    if (!existing.includes(entity)) existing.push(entity);
    this.districtPolygonEntities.set(districtId, existing);

    if (entity.polygon) {
      if (!this.districtPolygonBaseFillColor.has(entity)) {
        this.districtPolygonBaseFillColor.set(
          entity,
          this.getEntityFillColor(entity, Cesium.Color.WHITE.withAlpha(0.25))
        );
      }
      if (!this.districtPolygonBaseOutlineColor.has(entity)) {
        this.districtPolygonBaseOutlineColor.set(
          entity,
          this.getPropertyColor(entity.polygon.outlineColor, this.districtBaseOutlineColor)
        );
      }
      if (!this.districtPolygonBaseOutlineWidth.has(entity)) {
        this.districtPolygonBaseOutlineWidth.set(
          entity,
          this.getPropertyNumber(entity.polygon.outlineWidth, this.districtBaseOutlineWidth)
        );
      }
    }
  }

  private getPropertyColor(property: any, fallback: Cesium.Color): Cesium.Color {
    if (!property) return Cesium.Color.clone(fallback);
    if (typeof property.getValue === 'function') {
      const value = property.getValue(Cesium.JulianDate.now());
      if (value) return Cesium.Color.clone(value);
    }
    if (property instanceof Cesium.Color) return Cesium.Color.clone(property);
    return Cesium.Color.clone(fallback);
  }

  private getPropertyNumber(property: any, fallback: number): number {
    if (property == null) return fallback;
    if (typeof property.getValue === 'function') {
      const value = Number(property.getValue(Cesium.JulianDate.now()));
      return Number.isFinite(value) ? value : fallback;
    }
    const numberValue = Number(property);
    return Number.isFinite(numberValue) ? numberValue : fallback;
  }

  private getEntityFillColor(entity: Cesium.Entity, fallback: Cesium.Color): Cesium.Color {
    if (!entity.polygon?.material) return Cesium.Color.clone(fallback);

    const material: any = entity.polygon.material;
    if (material instanceof Cesium.ColorMaterialProperty) {
      return this.getPropertyColor(material.color, fallback);
    }

    if (typeof material.getValue === 'function') {
      const value = material.getValue(Cesium.JulianDate.now());
      if (value?.color) {
        if (typeof value.color.getValue === 'function') {
          const resolved = value.color.getValue(Cesium.JulianDate.now());
          if (resolved) return Cesium.Color.clone(resolved);
        } else if (value.color instanceof Cesium.Color) {
          return Cesium.Color.clone(value.color);
        }
      }
    }

    if (material instanceof Cesium.Color) return Cesium.Color.clone(material);
    return Cesium.Color.clone(fallback);
  }

  private isEntityRendered(entity: Cesium.Entity): boolean {
    if (!this.viewer) return false;
    if (this.viewer.entities.contains(entity)) return true;
    for (let i = 0; i < this.viewer.dataSources.length; i++) {
      const dataSource = this.viewer.dataSources.get(i);
      if (dataSource?.entities?.contains(entity)) return true;
    }
    return false;
  }

  private compactDistrictPolygonRegistry(): void {
    this.districtPolygonEntities.forEach((entities, districtId) => {
      const active = entities.filter(entity => this.isEntityRendered(entity));
      if (active.length > 0) this.districtPolygonEntities.set(districtId, active);
      else this.districtPolygonEntities.delete(districtId);
    });
  }

private animateDistrictOutline(entity: Cesium.Entity, targetColor: Cesium.Color, targetWidth: number): void {
    if (!entity.polygon) return;
    entity.polygon.outline = new Cesium.ConstantProperty(true);
    entity.polygon.outlineWidth = new Cesium.ConstantProperty(targetWidth);
    entity.polygon.outlineColor = new Cesium.ConstantProperty(Cesium.Color.clone(targetColor));
  }
 

 private animateDistrictFillColor(entity: Cesium.Entity, targetColor: Cesium.Color): void {
    if (!entity.polygon) return;
    entity.polygon.material = new Cesium.ColorMaterialProperty(Cesium.Color.clone(targetColor));
  }

  private setDistrictPinsVisible(visible: boolean): void {
    const entities = [...this.districtLabelEntities, ...Array.from(this.districtEntities.values())];
    entities.forEach(entity => {
      try {
        entity.show = visible;
      } catch (_) {}
    });
  }

  private setGovernoratePinsVisible(visible: boolean): void {
    // Show/hide governorate pins (billboards) and outline entities
    this.governorateEntities.forEach(entity => {
      try {
        entity.show = visible;
      } catch (_) {}
    });
    // Also update displayed district entities that are governorate-related
    this.displayedDistrictEntities.forEach(entity => {
      try {
        const props = entity.properties;
        if (props) {
          const isGov = props['isGovernorate']?.getValue ? props['isGovernorate'].getValue(Cesium.JulianDate.now()) : props['isGovernorate'];
          if (isGov) {
            entity.show = visible;
          }
        }
      } catch (_) {}
    });
  }

private getDistrictOverrideHex(districtId: string): string | null {
  const id = String(districtId);
  if (id === 'district1' || id === '1') {
    return '#4a90d9';        // normal blue
  }
  if (id === 'district2' || id === '2') {
    return '#d4af37';        // gold
  }
  if (id === 'district3' || id === '3') {
    return '#1a3a6b';        // dark blue
  }
  if (id === 'district4' || id === '4') {
    return '#d4af37';        // gold
  }
  if (id === 'district5' || id === '5') {
    return '#1a3a6b';        // dark blue
  }
  return null;
}

  private getDistrictThemeColor(districtId: string, fallback?: Cesium.Color): Cesium.Color {
    const overrideHex = this.getDistrictOverrideHex(districtId);
    if (overrideHex) {
      return Cesium.Color.fromCssColorString(overrideHex).withAlpha(1.0);
    }
    const entities = this.districtPolygonEntities.get(districtId) || [];
    const activeEntity = entities.find(entity => this.isEntityRendered(entity));

    if (activeEntity) {
      const baseColor = this.districtPolygonBaseFillColor.get(activeEntity)
        || this.getEntityFillColor(activeEntity, fallback || Cesium.Color.WHITE.withAlpha(0.3));
      return Cesium.Color.fromAlpha(baseColor, 1.0);
    }

    const districtKeys = Object.keys(this.tunisiaDistrictsMapping);
    const districtIndex = Math.max(0, districtKeys.indexOf(districtId));
    const totalDistricts = districtKeys.length || 5;
    const hue = Math.round((districtIndex / totalDistricts) * 360);
    const districtHex = this.hslToHex(hue, 70, 50);
    return Cesium.Color.fromCssColorString(districtHex).withAlpha(1.0);
  }

  private colorToRgbaCss(color: Cesium.Color, alpha?: number): string {
    const r = Math.round(color.red * 255);
    const g = Math.round(color.green * 255);
    const b = Math.round(color.blue * 255);
    const a = alpha != null ? alpha : color.alpha;
    return `rgba(${r}, ${g}, ${b}, ${Math.max(0, Math.min(1, a)).toFixed(3)})`;
  }

  private getContrastTextColor(color: Cesium.Color): string {
    const r = color.red * 255;
    const g = color.green * 255;
    const b = color.blue * 255;
    const luminance = 0.299 * r + 0.587 * g + 0.114 * b;
    return luminance >= 165 ? '#0a2330' : '#ffffff';
  }

  private updateDistrictHoverPopupTheme(districtId: string): void {
    const themeColor = this.getDistrictThemeColor(districtId);
    // District-specific neon override (district 3 should glow dark green)
const overrideNeonColor = this.getDistrictNeonOverride(districtId);
    const nr = Math.min(1.0, (overrideNeonColor ? overrideNeonColor.red   : themeColor.red)   * 3.5);
    const ng = Math.min(1.0, (overrideNeonColor ? overrideNeonColor.green : themeColor.green) * 3.5);
    const nb = Math.min(1.0, (overrideNeonColor ? overrideNeonColor.blue  : themeColor.blue)  * 3.5);
    const neon = new Cesium.Color(nr, ng, nb, 1.0);
    const neonCss = this.colorToRgbaCss(neon, 1.0);
    const neonMid = this.colorToRgbaCss(neon, 0.65);
    const neonFar = this.colorToRgbaCss(neon, 0.35);
    const neonAmb = this.colorToRgbaCss(neon, 0.18);
    this.districtHoverPopupBackground = this.colorToRgbaCss(themeColor, 0.92);
    this.districtHoverPopupBorder     = neonCss;
    this.districtHoverPopupTextColor  = this.getContrastTextColor(themeColor);
    this.districtHoverPopupShadow = [
      `0 0 6px  2px ${neonCss}`,
      `0 0 18px 5px ${neonMid}`,
      `0 0 42px 10px ${neonFar}`,
      `0 0 90px 20px ${neonAmb}`,
    ].join(', ');
  }
private setDistrictGlowState(districtId: string, glowing: boolean, isSelected: boolean = false): void {
  const entities = this.districtPolygonEntities.get(districtId) || [];
  entities
    .filter(entity => this.isEntityRendered(entity))
    .forEach(entity => {
            const baseFill    = this.districtPolygonBaseFillColor.get(entity) || this.getEntityFillColor(entity, Cesium.Color.WHITE.withAlpha(0.3));
            const baseOutline = this.districtPolygonBaseOutlineColor.get(entity) || this.districtBaseOutlineColor;
            const baseWidth   = this.districtPolygonBaseOutlineWidth.get(entity) || this.districtBaseOutlineWidth;
      const theme = this.getDistrictThemeColor(districtId, baseFill);
      // District-specific neon override (district 3 uses dark green neon)
const overrideNeonColor = this.getDistrictNeonOverride(districtId);
      const nr = Math.min(1.0, (overrideNeonColor ? overrideNeonColor.red   : theme.red)   * 2.5);
      const ng = Math.min(1.0, (overrideNeonColor ? overrideNeonColor.green : theme.green) * 2.5);
      const nb = Math.min(1.0, (overrideNeonColor ? overrideNeonColor.blue  : theme.blue)  * 2.5);
      const neon = new Cesium.Color(nr, ng, nb, 1.0);
      const fillAlpha   = glowing ? (isSelected ? this.NEON_FILL_ALPHA : 0.38) : baseFill.alpha;
      const outlineW    = glowing ? (isSelected ? this.NEON_OUTLINE_WIDTH : this.NEON_OUTLINE_WIDTH * 0.6) : baseWidth;
      const targetFill    = glowing ? Cesium.Color.fromAlpha(neon, fillAlpha) : baseFill;
      const targetOutline = glowing ? neon : baseOutline;
      this.animateDistrictFillColor(entity, targetFill);
      this.animateDistrictOutline(entity, targetOutline, outlineW);
      this._applyBloomHalo(entity, districtId, glowing, isSelected, neon);
    });
  this.setGovernorateGlowForDistrict(districtId, glowing, isSelected);
}

private _applyBloomHalo(
  sourceEntity: Cesium.Entity,
  districtId: string,
  glowing: boolean,
  isSelected: boolean,
  neon: Cesium.Color
): void {
  const oldHalos = this._bloomHaloEntities.get(sourceEntity) || [];
  oldHalos.forEach(h => { try { this.viewer.entities.remove(h); } catch (_) {} });
  this._bloomHaloEntities.set(sourceEntity, []);
  if (!glowing || !sourceEntity.polygon) return;
  // Create expensive halo entities only for explicit selections (not hover)
  if (!isSelected) {
    this.viewer.scene.requestRender();
    return;
  }
  const haloWidths = [28, 20, 12];
  const haloAlphas = [0.25, 0.45, 0.65];
  const halos: Cesium.Entity[] = [];
  haloWidths.forEach((width, i) => {
    const hierarchy = sourceEntity.polygon!.hierarchy;
    if (!hierarchy) return;
    const haloEntity = this.viewer.entities.add({
      polygon: {
        hierarchy,
        height: 0,
        fill: false,
        outline: true,
        outlineColor: new Cesium.ConstantProperty(Cesium.Color.fromAlpha(neon, haloAlphas[i])),
        outlineWidth: new Cesium.ConstantProperty(width),
      }
    });
    halos.push(haloEntity);
  });
  this._bloomHaloEntities.set(sourceEntity, halos);
  this.viewer.scene.requestRender();
}

private setGovernorateGlowForDistrict(districtId: string, glowing: boolean, isSelected: boolean): void {
  const theme = this.getDistrictThemeColor(districtId);
const overrideNeonColor = this.getDistrictNeonOverride(districtId);
  const nr = Math.min(1.0, (overrideNeonColor ? overrideNeonColor.red   : theme.red)   * 3.5);
  const ng = Math.min(1.0, (overrideNeonColor ? overrideNeonColor.green : theme.green) * 3.5);
  const nb = Math.min(1.0, (overrideNeonColor ? overrideNeonColor.blue  : theme.blue)  * 3.5);
  const neonGov     = new Cesium.Color(nr, ng, nb, 1.0);
  const baseGov     = Cesium.Color.WHITE.withAlpha(this.BASE_GOV_OUTLINE_ALPHA);
  const targetColor = glowing ? neonGov : baseGov;
  const targetWidth = glowing
    ? (isSelected ? this.NEON_GOV_OUTLINE_WIDTH : this.NEON_GOV_OUTLINE_WIDTH * 0.6)
    : this.BASE_GOV_OUTLINE_WIDTH;
  const fillAlpha   = glowing ? (isSelected ? 0.22 : 0.10) : 0.0;
  const fillColor   = glowing ? new Cesium.Color(nr, ng, nb, fillAlpha) : Cesium.Color.TRANSPARENT;
  const applyGov = (entity: Cesium.Entity) => {
    if (!entity?.polygon) return;
    this.animateDistrictOutline(entity, targetColor, targetWidth);
    this.animateDistrictFillColor(entity, fillColor);
  };
  for (const entity of this.tunisiaGovernorateOutlineEntities) {
    const now = Cesium.JulianDate.now();
    const p   = entity.properties;
    if (!p) continue;
    const eid = p['districtId']?.getValue ? p['districtId'].getValue(now) : p['districtId'];
    if (String(eid) === String(districtId)) applyGov(entity);
  }
  for (const entity of this.displayedDistrictEntities) {
    const now = Cesium.JulianDate.now();
    const p   = entity.properties;
    if (!p) continue;
    const eid = p['districtId']?.getValue ? p['districtId'].getValue(now) : p['districtId'];
    const isGov = p['isGovernorate']?.getValue ? p['isGovernorate'].getValue(now) : p['isGovernorate'];
    if (String(eid) === String(districtId) && isGov) applyGov(entity);
  }
}

  private getDistrictIdFromPicked(picked: any): string | null {
    if (!Cesium.defined(picked) || !picked?.id?.properties) return null;
    const now = Cesium.JulianDate.now();
    const props: any = picked.id.properties;
    const getProp = (key: string) => {
      const value = props[key];
      return value?.getValue ? value.getValue(now) : value;
    };

    const districtId = getProp('districtId') || getProp('id');
    const labelType = getProp('labelType');
    const isDistrictPolygon = !!getProp('isDistrictPolygon');
    if (!districtId) return null;
    if (isDistrictPolygon || labelType === 'district' || labelType === 'districtPolygon') {
      return String(districtId);
    }
    return null;
  }

private handleDistrictHover(movement: any): void {
    if (!movement?.endPosition || !this.viewer) return;
    const picked            = this.viewer.scene.pick(movement.endPosition);
    const hoveredDistrictId = this.getDistrictIdFromPicked(picked);

    // Only governorate, district, and project billboard pins get the grab cursor.
    let overGovOrProjectPin = false;
    try {
      if (picked && picked.id && picked.id.properties) {
        const now = Cesium.JulianDate.now();
        const props: any = picked.id.properties;
        const getProp = (key: string) => { const v = props[key]; return v?.getValue ? v.getValue(now) : v; };
        const isGovernorate = !!getProp('isGovernorate');
        const isDistrictPin = !!getProp('isDistrictPin') || getProp('labelType') === 'district';
        const locationObj = getProp('location');
        const typeProp = getProp('type');
        const isProjectProp = !!(typeProp === 'project' || (locationObj && String(locationObj.category).toUpperCase() === 'PROJECT'));
        const isBillboard = !!(picked && picked.id && (picked.id as any).billboard);
        overGovOrProjectPin = isBillboard && (isGovernorate || isDistrictPin || isProjectProp);
      }
    } catch (e) { /* ignore */ }

    if (hoveredDistrictId !== this.hoveredDistrictId) {
      if (this.hoveredDistrictId && this.hoveredDistrictId !== this.selectedGlowDistrictId) {
        this.setDistrictGlowState(this.hoveredDistrictId, false, false);
      }
      this.hoveredDistrictId = hoveredDistrictId;
      if (hoveredDistrictId && hoveredDistrictId !== this.selectedGlowDistrictId) {
        this.setDistrictGlowState(hoveredDistrictId, true, false);
      }
    }

    // District polygon hover: tooltip only — NO grab cursor on districts
    if (hoveredDistrictId) {
      this.districtHoverPopupText = this.getDistrictDisplayName(hoveredDistrictId);
      this.updateDistrictHoverPopupTheme(hoveredDistrictId);
      this.districtHoverPopupVisible = true;
      this.updateDistrictHoverPopupPosition(movement.endPosition);
      try { this.setCanvasCursor('pointer'); } catch (e) {}
      this.scheduleChangeDetection();
      return;
    }

    // Blue (governorate) or burgundy (project) diamond pin → grab cursor
    if (overGovOrProjectPin) {
      this.districtHoverPopupText = this.getHoverPopupTextFromPicked(picked);
      this.updateGenericHoverPopupTheme(picked);
      this.districtHoverPopupVisible = !!this.districtHoverPopupText;
      if (this.districtHoverPopupVisible) {
        this.updateDistrictHoverPopupPosition(movement.endPosition);
      }
      const pickedId = picked?.id;
      if (this.grabbedProjectEntityId && pickedId && pickedId === this.grabbedProjectEntityId) {
   try { this.setCanvasCursor('pointer'); } catch (e) {}
      } else {
try { this.setCanvasCursor('pointer'); } catch (e) {}
      }
      this.scheduleChangeDetection();
      return;
    }

    // Everything else: default cursor
    if (this.districtHoverPopupVisible) {
      this.districtHoverPopupVisible = false;
      this.districtHoverPopupText    = '';
    }
    try { this.setCanvasCursor(''); } catch (e) {}
    this.scheduleChangeDetection();
  }

  private getHoverPopupTextFromPicked(picked: any): string {
    if (!picked?.id) return '';

    const entity = picked.id as any;
    const now = Cesium.JulianDate.now();
    const props: any = entity.properties;
    const getProp = (key: string) => {
      if (!props) return undefined;
      const value = props[key];
      return value?.getValue ? value.getValue(now) : value;
    };

    if (getProp('isGovernorate')) {
      const districtId = String(getProp('districtId') || '').trim();
      const memberIndexRaw = Number(getProp('memberIndex'));
      const memberIndex = Number.isFinite(memberIndexRaw) ? memberIndexRaw : -1;
      const governorateLocalizedName = this.currentLanguage === 'ar'
        ? String(getProp('name_ar') || '').trim()
        : this.currentLanguage === 'fr'
          ? String(getProp('name_fr') || getProp('name_en') || '').trim()
          : String(getProp('name_en') || getProp('name_fr') || '').trim();
      if (governorateLocalizedName) return governorateLocalizedName;

      if (districtId && memberIndex >= 0) {
        const governorateName = this.getGovernorateDisplayName(districtId, memberIndex);
        if (governorateName) return governorateName;
      }

      const governorateFallback = String(getProp('name_en') || getProp('name_fr') || getProp('name_ar') || '').trim();
      if (governorateFallback) return governorateFallback;
    }

    const projectData = getProp('projectData');
    if (projectData) {
      const projectName = this.currentLanguage === 'ar'
        ? String(projectData?.name_ar || projectData?.name || '').trim()
        : this.currentLanguage === 'fr'
          ? String(projectData?.name_fr || projectData?.name || '').trim()
          : String(projectData?.name_en || projectData?.name || '').trim();
      if (projectName) return projectName;

      const projectNameFallback = this.getProjectSidebarDisplayName(projectData);
      if (projectNameFallback) return projectNameFallback;
    }

    const directNameCandidates = [
      entity.name,
      getProp('displayName'),
      getProp('name'),
      getProp('title'),
      getProp('projectName'),
      getProp('locationName'),
      getProp('zoneName'),
      getProp('name_en'),
      getProp('name_fr'),
      getProp('name_ar'),
    ];

    for (const candidate of directNameCandidates) {
      const text = String(candidate || '').trim();
      if (text) return text;
    }

    const locationObj = getProp('location');
    if (locationObj && typeof locationObj === 'object') {
      const fallback = String(locationObj.name || locationObj.displayName || locationObj.label || '').trim();
      if (fallback) return fallback;
    }

    if (getProp('isGovernorate')) {
      const districtId = String(getProp('districtId') || '').trim();
      const memberIndexRaw = Number(getProp('memberIndex'));
      const memberIndex = Number.isFinite(memberIndexRaw) ? memberIndexRaw : -1;
      const governorateName = districtId && memberIndex >= 0
        ? this.getGovernorateDisplayName(districtId, memberIndex)
        : '';
    }

    return '';
  }

  private updateGenericHoverPopupTheme(picked: any): void {
    const now = Cesium.JulianDate.now();
    const props: any = picked?.id?.properties;
    const getProp = (key: string) => {
      if (!props) return undefined;
      const value = props[key];
      return value?.getValue ? value.getValue(now) : value;
    };

    const locationObj = getProp('location');
    const isProject = !!(getProp('type') === 'project' || (locationObj && String(locationObj.category).toUpperCase() === 'PROJECT'));

    const background = isProject ? 'rgba(120, 62, 10, 0.94)' : 'rgba(14, 74, 154, 0.94)';
    const border = isProject ? 'rgba(255, 205, 118, 0.98)' : 'rgba(157, 214, 255, 0.98)';
    const shadow = isProject
      ? '0 0 6px 2px rgba(255, 205, 118, 0.9), 0 0 18px 5px rgba(255, 176, 82, 0.56), 0 0 42px 10px rgba(255, 141, 41, 0.28)'
      : '0 0 6px 2px rgba(157, 214, 255, 0.9), 0 0 18px 5px rgba(74, 169, 255, 0.56), 0 0 42px 10px rgba(36, 112, 255, 0.28)';

    this.districtHoverPopupBackground = background;
    this.districtHoverPopupBorder = border;
    this.districtHoverPopupTextColor = '#ffffff';
    this.districtHoverPopupShadow = shadow;
  }

  /** Set the mouse cursor on the Cesium canvas. Pass empty string to clear. */
  private setCanvasCursor(cursor: string): void {
    try {
      const canvas = (this.viewer && this.viewer.scene && this.viewer.scene.canvas) as HTMLCanvasElement | undefined;
      const container = (this.viewer as any)?.container as HTMLElement | undefined;
      if (canvas) canvas.style.cursor = cursor || '';
      if (container) container.style.cursor = cursor || '';
    } catch {
      // ignore
    }
  }
  private setBloomIntensityForSelection(isSelected: boolean): void {
    try {
      const bloom: any = this.viewer?.scene?.postProcessStages?.bloom;
      if (!bloom) return;

      if (isSelected) {
          bloom.uniforms.sigma = 6.0;
          bloom.uniforms.contrast = 192;
          bloom.uniforms.brightness = -0.2;
      } else {
          bloom.uniforms.sigma = 3.78;
          bloom.uniforms.contrast = 128;
          bloom.uniforms.brightness = -0.3;
      }

      this.viewer.scene.requestRender();
    } catch {
      // ignore
    }
  }

  private setCesiumContainerGlow(active: boolean): void {
    const el = document.getElementById('cesiumContainer');
    if (!el) return;
    if (active) el.classList.add('district-selected');
    else el.classList.remove('district-selected');
  }

 /** Lock in full neon glow for the selected district. */
  private selectDistrictNeonGlow(districtId: string): void {
    if (this.selectedGlowDistrictId && this.selectedGlowDistrictId !== districtId) {
      this.setDistrictGlowState(this.selectedGlowDistrictId, false, false);
    }
    this.selectedGlowDistrictId = districtId;
    this.setDistrictGlowState(districtId, true, true);
    this.setBloomIntensityForSelection(true);
    this.setCesiumContainerGlow(true);
  }
 
  /** Remove neon from the currently selected district. */
private clearDistrictNeonGlow(): void {
  if (this.selectedGlowDistrictId) {
    this.setDistrictGlowState(this.selectedGlowDistrictId, false, false);
    this.selectedGlowDistrictId = null;
  }
  this._bloomHaloEntities.forEach((halos) => {
    halos.forEach(h => { try { this.viewer.entities.remove(h); } catch (_) {} });
  });
  this._bloomHaloEntities.clear();
  this.setBloomIntensityForSelection(false);
  this.setCesiumContainerGlow(false);
}

  private updateDistrictHoverPopupPosition(position: { x: number; y: number }): void {
    const canvas = this.viewer.scene.canvas;
    const tooltipWidth = 250;
    const estimatedLines = Math.min(3, Math.max(1, Math.ceil(String(this.districtHoverPopupText || '').length / 24)));
    const tooltipHeight = 28 + (estimatedLines * 20);
    const offsetX = 14;
    const offsetY = -10;

    const nextX = Math.max(8, Math.min(position.x + offsetX, canvas.clientWidth - tooltipWidth));
    const nextY = Math.max(8, Math.min(position.y + offsetY, canvas.clientHeight - tooltipHeight));
    this.districtHoverPopupX = nextX;
    this.districtHoverPopupY = nextY;
  }

  private getDistrictDisplayName(districtId: string): string {
    const meta = this.tunisiaDistrictsMapping[districtId];
    if (!meta) return districtId;
    return this.currentLanguage === 'ar'
      ? meta.name_ar
      : (this.currentLanguage === 'fr' ? meta.name_fr : meta.name_en);
  }

  // ════════════════════════════════════════════════════════════════════════════
  // UNIFIED ZOOM — single function used by every click path
  // ════════════════════════════════════════════════════════════════════════════

  /**
   * Primary zoom: fly to a Cesium entity using HeadingPitchRange offset.
   * This is the same call used by selectProject() — now shared everywhere.
   */
  private zoomToEntityWithHeight(entity: Cesium.Entity, height: number): void {
    this.viewer.selectedEntity = entity;
    this.highlightSelectedPin(entity);   
    this.viewer.flyTo(entity, {
      duration : this.ZOOM_DURATION,
      offset   : new Cesium.HeadingPitchRange(
        this.ZOOM_HEADING,
        this.ZOOM_PITCH,
        height
      ),
    });
  }

  private zoomToEntity(entity: Cesium.Entity): void {
    this.zoomToEntityWithHeight(entity, this.ZOOM_HEIGHT);
  }

  /**
   * Secondary zoom: fly to lon/lat coordinates when no entity is available.
   * Uses the same height/pitch/duration constants.
   */
  private zoomToCoordinatesWithHeight(lon: number, lat: number, height: number): void {
    this.viewer.camera.flyTo({
      destination   : Cesium.Cartesian3.fromDegrees(lon, lat, height),
      orientation   : { heading: this.ZOOM_HEADING, pitch: this.ZOOM_PITCH, roll: 0 },
      duration      : this.ZOOM_DURATION,
      easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT,
    });
  }

  private zoomToCoordinates(lon: number, lat: number): void {
    this.zoomToCoordinatesWithHeight(lon, lat, this.ZOOM_HEIGHT);
  }

  private focusTunisiaUpright(duration: number = this.tunisiaUprightDuration): void {
    if (!this.viewer) return;
    this.viewer.camera.flyTo({
      destination: this.getTunisiaUprightRectangle(),
      orientation: {
        heading: 0,
        pitch: -Cesium.Math.PI_OVER_TWO,
        roll: 0,
      },
      duration,
      easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT,
      complete: () => {
        this.enableTunisiaStandingLock();
        this.enforceTunisiaStandingOrientation();
        this.setDistrictPinsVisible(this.isTunisiaActiveView() && this.hasCompletedTunisiaIntroSpin);
      },
    });
  }

  private disableMouseWheelZoom(): void {
    if (!this.viewer?.scene?.screenSpaceCameraController) return;
    const controller = this.viewer.scene.screenSpaceCameraController;
    controller.zoomEventTypes = [];
  }

  private enableMouseWheelZoom(): void {
    if (!this.viewer?.scene?.screenSpaceCameraController) return;
    const controller = this.viewer.scene.screenSpaceCameraController;
    controller.zoomEventTypes = [
      Cesium.CameraEventType.WHEEL,
    ];
  }

private setMapZoomInteractionFromFullscreenState(): void {
  // Always disable wheel zoom - zoom only via buttons
  this.disableMouseWheelZoom();
}

  private isTunisiaActiveView(): boolean {
    const activeCountry = this.selectedCountry || this.defaultMapCountry || this.focusCountryName;
    return this.normalizeSelectionName(activeCountry) === 'tunisia';
  }

  private enableTunisiaStandingLock(): void {
    if (!this.viewer || this.cameraChangedListener) return;
    const controller = this.viewer.scene.screenSpaceCameraController;
    controller.enableTilt = false;
    controller.enableLook = false;
    this.viewer.camera.percentageChanged = 0.001;

    this.cameraChangedListener = () => {
      this.enforceTunisiaStandingOrientation();
    };
    this.viewer.camera.changed.addEventListener(this.cameraChangedListener);
  }

  private enforceTunisiaStandingOrientation(): void {
    if (!this.viewer || !this.isTunisiaActiveView() || this.isApplyingTunisiaOrientationLock) return;

    const camera = this.viewer.camera;
    const minimumOverviewHeight = this.tunisiaMinOverviewHeight;
    const currentHeight = camera.positionCartographic?.height ?? minimumOverviewHeight;
    const clampedHeight = Math.max(minimumOverviewHeight, currentHeight);
    const targetHeading = 0;
    const targetPitch = -Cesium.Math.PI_OVER_TWO;
    const targetRoll = 0;

    const headingDelta = Math.abs(Cesium.Math.negativePiToPi(camera.heading - targetHeading));
    const pitchDelta = Math.abs(camera.pitch - targetPitch);
    const rollDelta = Math.abs(camera.roll - targetRoll);
    if (headingDelta < 0.0005 && pitchDelta < 0.0005 && rollDelta < 0.0005) return;

    this.isApplyingTunisiaOrientationLock = true;
    try {
      camera.setView({
        destination: Cesium.Cartesian3.fromRadians(
          camera.positionCartographic.longitude,
          camera.positionCartographic.latitude,
          clampedHeight,
        ),
        orientation: {
          heading: targetHeading,
          pitch: targetPitch,
          roll: targetRoll,
        },
      });
      this.viewer.scene.requestRender();
    } finally {
      this.isApplyingTunisiaOrientationLock = false;
    }
  }

  private getTunisiaUprightRectangle(): Cesium.Rectangle {
    return this.currentLanguage === 'ar'
      ? this.tunisiaArabicUprightRectangle
      : this.tunisiaNonArabicUprightRectangle;
  }

  /**
   * Zoom to a district — slightly higher altitude so the full district is visible,
   * but uses the same pitch/duration for visual consistency.
   */
  private zoomToDistrict(districtId: string): void {
    if (this.isTunisiaActiveView()) {
      this.focusTunisiaUpright(0.9);
      return;
    }

    // Try the pin entity first (exact centroid coordinates)
    const pinEntity =
      this.districtLabelEntities.find(e => {
        const now = Cesium.JulianDate.now();
        const p   = e.properties;
        if (!p) return false;
        const id  = p['districtId']?.getValue ? p['districtId'].getValue(now) : p['districtId'];
        return id === districtId;
      }) ?? this.districtEntities.get(districtId);

    if (pinEntity) {
      this.viewer.selectedEntity = pinEntity;
      const pos = pinEntity.position?.getValue(Cesium.JulianDate.now());
      if (pos) {
        const carto  = Cesium.Cartographic.fromCartesian(pos);
        const lon    = Cesium.Math.toDegrees(carto.longitude);
        const lat    = Cesium.Math.toDegrees(carto.latitude);
        // Districts: use overview height so we don't zoom in too closely
        const height = this.DISTRICT_OVERVIEW_ZOOM_HEIGHT;
        this.viewer.camera.flyTo({
          destination   : Cesium.Cartesian3.fromDegrees(lon, lat, height),
          orientation   : { heading: this.ZOOM_HEADING, pitch: this.ZOOM_PITCH, roll: 0 },
          duration      : this.ZOOM_DURATION,
          easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT,
        });
        this.positionSelectionFrameAtEntity(pinEntity);
        return;
      }
      // Fallback when position isn't available yet
      this.viewer.flyTo(pinEntity, {
        duration: this.ZOOM_DURATION,
        offset: new Cesium.HeadingPitchRange(
          this.ZOOM_HEADING, this.ZOOM_PITCH, this.DISTRICT_OVERVIEW_ZOOM_HEIGHT
        ),
      });
      this.positionSelectionFrameAtEntity(pinEntity);
      return;
    }

    // Derive center from pre-computed geometry bbox
    const geom = this.tunisiaDistrictGeometries[districtId];
    if (geom) {
      try {
        const [minLon, minLat, maxLon, maxLat] = turf.bbox({ type: 'Feature', properties: {}, geometry: geom });
        this.zoomToCoordinatesWithHeight((minLon + maxLon) / 2, (minLat + maxLat) / 2, this.DISTRICT_OVERVIEW_ZOOM_HEIGHT);
      } catch { /* ignore */ }
    }
  }

  /**
   * Zoom to a governorate — uses standard ZOOM_HEIGHT (same as project).
   */
  private zoomToGovernorate(govName: string, districtId?: string, memberIndex?: number): void {
    // Keep governorate navigation behavior consistent with project navigation:
    // always fly to the selected governorate pin/feature, even in Tunisia view.

    // Step 1 — exact pin by district/member index
    const strictPin = this.getGovernoratePinByDistrictIndex(districtId, memberIndex);
    if (strictPin) {
      this.viewer.selectedEntity = strictPin;
      this.viewer.flyTo(strictPin, {
        duration: this.ZOOM_DURATION,
        offset: new Cesium.HeadingPitchRange(this.ZOOM_HEADING, this.ZOOM_PITCH, this.GOVERNORATE_ZOOM_HEIGHT)
      });
      this.positionSelectionFrameAtEntity(strictPin);
      return;
    }

    // Step 2 — name-based entity lookup
    const namedEntity = this.getGovernorateEntityByName(govName);
    if (namedEntity) {
      this.viewer.selectedEntity = namedEntity;
      this.viewer.flyTo(namedEntity, {
        duration: this.ZOOM_DURATION,
        offset: new Cesium.HeadingPitchRange(this.ZOOM_HEADING, this.ZOOM_PITCH, this.GOVERNORATE_ZOOM_HEIGHT)
      });
      this.positionSelectionFrameAtEntity(namedEntity);
      return;
    }

    // Step 3 — GeoJSON feature centroid
    const feature = this.getMatchedGovernorateFeature(districtId, govName, memberIndex);
    if (feature?.geometry) {
      try {
        const [lon, lat] = turf.centroid(feature).geometry.coordinates;
        this.viewer.selectedEntity = undefined;
        this.viewer.camera.flyTo({
          destination: Cesium.Cartesian3.fromDegrees(lon, lat, this.GOVERNORATE_ZOOM_HEIGHT),
          orientation: { heading: this.ZOOM_HEADING, pitch: this.ZOOM_PITCH, roll: 0 },
          duration: this.ZOOM_DURATION,
          easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT,
        });
        this.positionSelectionFrameAtCartesian(Cesium.Cartesian3.fromDegrees(lon, lat));
        return;
      } catch { /* ignore */ }
    }

    // Step 4 — render district (skip its camera) then retry
    if (districtId) {
      this.showDistrict(districtId, true).then(() => {
        const retryPin = this.getGovernoratePinByDistrictIndex(districtId, memberIndex)
                      ?? this.getGovernorateEntityByName(govName);
        if (retryPin) {
          this.viewer.flyTo(retryPin, {
            duration: this.ZOOM_DURATION,
            offset: new Cesium.HeadingPitchRange(this.ZOOM_HEADING, this.ZOOM_PITCH, this.GOVERNORATE_ZOOM_HEIGHT)
          });
          this.positionSelectionFrameAtEntity(retryPin);
          return;
        }
        const retryFeature = this.getMatchedGovernorateFeature(districtId, govName, memberIndex);
        if (retryFeature?.geometry) {
          try {
            const [lon, lat] = turf.centroid(retryFeature).geometry.coordinates;
            this.viewer.camera.flyTo({
              destination: Cesium.Cartesian3.fromDegrees(lon, lat, this.GOVERNORATE_ZOOM_HEIGHT),
              orientation: { heading: this.ZOOM_HEADING, pitch: this.ZOOM_PITCH, roll: 0 },
              duration: this.ZOOM_DURATION,
              easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT,
            });
            this.positionSelectionFrameAtCartesian(Cesium.Cartesian3.fromDegrees(lon, lat));
          } catch { /* ignore */ }
        }
      }).catch(() => {});
    }
  }

  // ════════════════════════════════════════════════════════════════════════════
  // SINGLE SOURCE OF TRUTH: setSidebarProjects
  // Every place that changes what projects are shown MUST call this.
  // It updates sidebarProjects, renders pins, and triggers change detection.
  // ════════════════════════════════════════════════════════════════════════════
  private setSidebarProjects(projects: LocationDTO[], contextKey: string): void {
    const unique = this.deduplicateProjects(projects || []);
    this.sidebarProjectsBase = unique;
    this.sidebarProjects = [...unique];
    this.applySidebarSearchFilters();

    // Sync map pins to match exactly what the sidebar shows
    this.clearZoneProjects();
    if (unique.length > 0) {
      this.currentZoneProjectZoneId = contextKey;
      this.renderZoneProjectPins(unique as any[]);
    }

    this.scheduleChangeDetection();
  }

  // ════════════════════════════════════════════════════════════════════════════
  // STRICT PROJECT FILTERING
  // ════════════════════════════════════════════════════════════════════════════

  private getStrictGovernorateProjects(
    governorateLocation: LocationDTO | null,
    governorateName: string
  ): LocationDTO[] {
    // Primary: backend location id
    let byId: LocationDTO[] = [];
    let byLocationId: LocationDTO[] = [];
    let byGovField: LocationDTO[] = [];

    if (governorateLocation?.id != null) {
      byId = this.allProjects.filter(
        p => String((p as any)?.parentId) === String(governorateLocation.id)
      );
      if (byId.length > 0) return byId;
    }

    // Secondary: match location ids by name
    const normalizedTarget = this.normalizeSelectionName(governorateName);
    if (!normalizedTarget) return [];

    const matchingLocationIds = new Set<string>(
      this.allLocations
        .filter(loc => {
          if (loc.category !== 'GOVERNORATE') return false;
          const n = this.normalizeSelectionName(loc.name || '');
          return n === normalizedTarget || n.includes(normalizedTarget) || normalizedTarget.includes(n);
        })
        .map(loc => String(loc.id))
    );

    if (matchingLocationIds.size > 0) {
      byLocationId = this.allProjects.filter(
        p => (p as any)?.parentId != null && matchingLocationIds.has(String((p as any).parentId))
      );
      if (byLocationId.length > 0) return byLocationId;
    }

    // Tertiary: project's own governorateName field
    byGovField = this.allProjects.filter(p => {
      const pGov = this.normalizeSelectionName(String((p as any)?.governorateName || ''));
      return pGov && (
        pGov === normalizedTarget ||
        pGov.includes(normalizedTarget) ||
        normalizedTarget.includes(pGov)
      );
    });
    if (byGovField.length > 0) return byGovField;

    // Fallback: try matching by the parent location's name (in case parentId points to a different intermediate)
    const fallback = this.allProjects.filter(p => {
      const parentId = (p as any)?.parentId;
      if (parentId == null) return false;
      const parent = this.findLocationById(parentId);
      if (!parent || !parent.name) return false;
      const parentNorm = this.normalizeSelectionName(String(parent.name));
      return parentNorm === normalizedTarget || parentNorm.includes(normalizedTarget) || normalizedTarget.includes(parentNorm);
    });
    if (fallback.length > 0) return fallback;

    // Diagnostic: log when no projects were found so maintainers can inspect data for governorates like "Beja"
    // (This is non-intrusive and can be removed after debugging.)
    console.warn(`[getStrictGovernorateProjects] governorate="${governorateName}" normalized="${normalizedTarget}" results: byId=${byId.length} byLocationId=${byLocationId.length} byGovField=${byGovField.length} fallback=${fallback.length}`);
    return [];
  }

  private getStrictDistrictProjects(districtId: string): LocationDTO[] {
    const meta = this.tunisiaDistrictsMapping[districtId];
    if (!meta) return [];

    const normalizedMembers = meta.members.map(m => this.normalizeSelectionName(m));

    const governorateIds = new Set<string>(
      this.allLocations
        .filter(loc => {
          if (loc.category !== 'GOVERNORATE') return false;
          const n = this.normalizeSelectionName(loc.name || '');
          return normalizedMembers.some(m => m && (n === m || n.includes(m) || m.includes(n)));
        })
        .map(loc => String(loc.id))
    );

    const districtIds = new Set<string>(
      this.allLocations
        .filter(loc => {
          if (loc.category !== 'DISTRICT') return false;
          const n = this.normalizeSelectionName(loc.name || '');
          return [meta.name_en, meta.name_ar, meta.name_fr]
            .filter(Boolean)
            .map(s => this.normalizeSelectionName(s))
            .some(dn => dn && (n === dn || n.includes(dn) || dn.includes(n)));
        })
        .map(loc => String(loc.id))
    );

    const allParentIds = new Set([...governorateIds, ...districtIds]);
    if (allParentIds.size === 0) return [];

    return this.allProjects.filter(
      p => (p as any)?.parentId != null && allParentIds.has(String((p as any).parentId))
    );
  }

  // ════════════════════════════════════════════════════════════════════════════
  // PROJECT COUNT helper (for template: "X projects")
  // ════════════════════════════════════════════════════════════════════════════
  get sidebarProjectCount(): number {
    return this.sidebarProjects.length;
  }

  // ════════════════════════════════════════════════════════════════════════════
  // HIERARCHY HELPERS (unchanged logic, wired to setSidebarProjects)
  // ════════════════════════════════════════════════════════════════════════════

  isHierarchySidebarActive(): boolean {
    return !!this.hierarchyState.view && !!this.hierarchyState.selectedDistrict;
  }

  getHierarchyDistrict(): SidebarHierarchyDistrictNode | null {
    return this.hierarchyState.selectedDistrict;
  }

  getHierarchyGovernorate(): SidebarHierarchyGovernorateNode | null {
    const district = this.hierarchyState.selectedDistrict;
    if (!district || district.governorates.length === 0) return null;
    const index = Math.max(0, Math.min(this.hierarchyState.selectedGovernorateIndex, district.governorates.length - 1));
    return district.governorates[index] || null;
  }

  getHierarchyProject(): SidebarHierarchyProjectNode | null {
    const governorate = this.getHierarchyGovernorate();
    if (!governorate || governorate.projects.length === 0) return null;
    const index = Math.max(0, Math.min(this.hierarchyState.selectedProjectIndex, governorate.projects.length - 1));
    return governorate.projects[index] || null;
  }

  canNavigateGovernoratePrevious(): boolean { return this.hierarchyState.selectedGovernorateIndex > 0; }
  canNavigateGovernorateNext(): boolean {
    const d = this.hierarchyState.selectedDistrict;
    return !!d && this.hierarchyState.selectedGovernorateIndex < d.governorates.length - 1;
  }
  canNavigateProjectPrevious(): boolean { return this.hierarchyState.selectedProjectIndex > 0; }
  canNavigateProjectNext(): boolean {
    const g = this.getHierarchyGovernorate();
    return !!g && this.hierarchyState.selectedProjectIndex < g.projects.length - 1;
  }

  getHierarchyMotionClass(): string {
    if (this.hierarchyTransition === 'slide-out') return 'hierarchy-slide-out';
    if (this.hierarchyTransition === 'slide-in') return 'hierarchy-slide-in';
    return '';
  }

  getHierarchyExploreLabel(): string {
    if (this.hierarchyState.view === 'governorate') return this.getHierarchyGovernorate()?.displayName || '';
    if (this.hierarchyState.view === 'project') return this.getHierarchyProject()?.project?.name || '';
    return '';
  }

  getHierarchyExploreCurrentPosition(): number {
    if (this.hierarchyState.view === 'governorate') return this.hierarchyState.selectedGovernorateIndex + 1;
    if (this.hierarchyState.view === 'project') return this.hierarchyState.selectedProjectIndex + 1;
    return 0;
  }

  getHierarchyExploreTotal(): number {
    if (this.hierarchyState.view === 'governorate') return this.getHierarchyDistrict()?.governorates?.length || 0;
    if (this.hierarchyState.view === 'project') return this.getHierarchyGovernorate()?.projects?.length || 0;
    return 0;
  }

  canNavigateHierarchyPrevious(): boolean {
    if (this.hierarchyState.view === 'governorate') return this.canNavigateGovernoratePrevious();
    if (this.hierarchyState.view === 'project') return this.canNavigateProjectPrevious();
    return false;
  }

  canNavigateHierarchyNext(): boolean {
    if (this.hierarchyState.view === 'governorate') return this.canNavigateGovernorateNext();
    if (this.hierarchyState.view === 'project') return this.canNavigateProjectNext();
    return false;
  }

  navigateHierarchyPrevious(): void {
    if (this.hierarchyState.view === 'governorate') { this.navigateGovernorate(-1).catch(() => {}); return; }
    if (this.hierarchyState.view === 'project') this.navigateProject(-1);
  }

  navigateHierarchyNext(): void {
    if (this.hierarchyState.view === 'governorate') { this.navigateGovernorate(1).catch(() => {}); return; }
    if (this.hierarchyState.view === 'project') this.navigateProject(1);
  }

  onHierarchyTopBack(): void {
    if (this.hierarchyState.view === 'project') { this.backToGovernorateHierarchy(); return; }
    if (this.hierarchyState.view === 'governorate') { this.backToDistrictHierarchy(); return; }
    this.backToList();
  }

  async selectHierarchyGovernorate(index: number): Promise<void> {
    const district = this.hierarchyState.selectedDistrict;
    if (!district || index < 0 || index >= district.governorates.length) return;

    this.hierarchyState.selectedGovernorateIndex = index;
    this.hierarchyState.selectedProjectIndex = 0;
    this.applyHierarchyView('governorate');

    const governorate = district.governorates[index];
    // Unified zoom
    this.zoomToGovernorate(governorate.name, governorate.districtId, governorate.index);
    this.showGovernorateHierarchyProjectsOnMap(governorate);
    this.hydrateGovernorateNode(governorate);

    this.syncLegacySelectionFromHierarchyView('governorate');
    this.scheduleChangeDetection();
  }

  async navigateGovernorate(delta: number): Promise<void> {
    await this.selectHierarchyGovernorate(this.hierarchyState.selectedGovernorateIndex + delta);
  }

  selectHierarchyProject(index: number, zoomHeight: number = this.ZOOM_HEIGHT): void {
    const governorate = this.getHierarchyGovernorate();
    if (!governorate || index < 0 || index >= governorate.projects.length) return;

    this.hierarchyState.selectedProjectIndex = index;
    this.applyHierarchyView('project');

    const projectNode = governorate.projects[index];
    this.showGovernorateHierarchyProjectsOnMap(governorate);
    this.focusProjectOnMap(projectNode.project, zoomHeight);
    this.hydrateProjectNode(projectNode);
    this.syncLegacySelectionFromHierarchyView('project');
    this.scheduleChangeDetection();
  }

  navigateProject(delta: number): void {
    this.selectHierarchyProject(
      this.hierarchyState.selectedProjectIndex + delta,
      this.EXPLORE_PROJECT_ZOOM_HEIGHT
    );
  }

  backToGovernorateHierarchy(): void {
    this.applyHierarchyView('governorate');
    this.syncLegacySelectionFromHierarchyView('governorate');
    this.scheduleChangeDetection();
  }

  backToDistrictHierarchy(): void {
    this.applyHierarchyView('district');
    const district = this.hierarchyState.selectedDistrict;
    if (district) {
      this.zoomToDistrict(district.id);
      this.showDistrict(district.id, true).catch(() => {});
      // Do not show all district projects - only show projects when a governorate is selected
      this.clearProjectsFromMap();
    }
    this.syncLegacySelectionFromHierarchyView('district');
    this.scheduleChangeDetection();
  }

  private clearHierarchyState(): void {
    this.hierarchyState.view = null;
    this.hierarchyState.selectedDistrict = null;
    this.hierarchyState.selectedGovernorateIndex = 0;
    this.hierarchyState.selectedProjectIndex = 0;
    this.hierarchyTransition = 'idle';
    this.clearHierarchyTransitionTimer();
  }

  private clearHierarchyTransitionTimer(): void {
    if (!this.hierarchyTransitionTimer) return;
    clearTimeout(this.hierarchyTransitionTimer);
    this.hierarchyTransitionTimer = null;
  }

  private applyHierarchyView(nextView: SidebarHierarchyView): void {
    const token = ++this.hierarchyTransitionToken;
    this.clearHierarchyTransitionTimer();

    if (!this.hierarchyState.view) {
      this.hierarchyState.view = nextView;
      this.hierarchyTransition = 'slide-in';
      this.hierarchyTransitionTimer = setTimeout(() => {
        if (token !== this.hierarchyTransitionToken) return;
        this.hierarchyTransition = 'idle';
        this.hierarchyTransitionTimer = null;
        this.scheduleChangeDetection();
      }, this.hierarchyTransitionMs);
      return;
    }

    if (this.hierarchyState.view === nextView && this.hierarchyTransition === 'idle') return;

    this.hierarchyTransition = 'slide-out';
    this.scheduleChangeDetection();

    this.hierarchyTransitionTimer = setTimeout(() => {
      if (token !== this.hierarchyTransitionToken) return;
      this.hierarchyState.view = nextView;
      this.hierarchyTransition = 'slide-in';
      this.scheduleChangeDetection();
      this.hierarchyTransitionTimer = setTimeout(() => {
        if (token !== this.hierarchyTransitionToken) return;
        this.hierarchyTransition = 'idle';
        this.hierarchyTransitionTimer = null;
        this.scheduleChangeDetection();
      }, this.hierarchyTransitionMs);
    }, this.hierarchyTransitionMs);
  }

  // ════════════════════════════════════════════════════════════════════════════
  // CONTENT HELPERS
  // ════════════════════════════════════════════════════════════════════════════

  private getContentType(entry: any): string {
    return String(entry?.content?.type || entry?.type || '').toUpperCase();
  }

  private getContentValue(entry: any): string {
    const value = entry?.content?.value ?? entry?.value;
    if (value == null) return '';
    return String(value).trim();
  }

  private getImageFromContents(contents: any[] | undefined | null): string | null {
    if (!Array.isArray(contents)) return null;
    const imageEntry = contents.find(e => this.getContentType(e) === 'IMAGE' && this.getContentValue(e));
    return imageEntry ? this.getContentValue(imageEntry) : null;
  }

  private getDescriptionFromContents(contents: any[] | undefined | null): string {
    if (!Array.isArray(contents)) return '';
    const textEntry = contents.find(e => this.getContentType(e) === 'TEXT' && this.getContentValue(e));
    return textEntry ? this.getContentValue(textEntry) : '';
  }

 private extractLocationImage(location: LocationDTO | null | undefined): string | null {
  if (!location) return null;
  // Try contents first
  const fromContents = this.getLocationImage(location);
  if (fromContents) return fromContents;
  // Fallback: direct imageUrl field some backends set
  return (location as any)?.imageUrl || null;
}

  private extractLocationDescription(location: LocationDTO | null | undefined): string {
    return this.getDescriptionFromContents(location?.contents as any[]) || String((location as any)?.description || '').trim();
  }

  private findLocationById(id: number | string | null | undefined): LocationDTO | null {
    if (id == null) return null;
    return this.allLocations.find(l => String(l.id) === String(id)) || null;
  }

  private findDistrictLocationByMeta(meta: any): LocationDTO | null {
    const districtNames = [meta?.name_en, meta?.name_fr, meta?.name_ar]
      .filter(Boolean)
      .map((n: string) => this.normalizeSelectionName(String(n)));

    const matches = this.allLocations.filter(loc => {
      if (loc.category !== 'DISTRICT') return false;
      const locationName = this.normalizeSelectionName(loc.name || '');
      return districtNames.some(n => n === locationName || n.includes(locationName) || locationName.includes(n));
    });

    if (matches.length === 0) return null;

    matches.sort((a, b) => {
      const score = (loc: LocationDTO) => ((Array.isArray(loc.contents) && loc.contents.length > 0) ? 2 : 0) + (loc.id ? 1 : 0);
      const byScore = score(b) - score(a);
      if (byScore !== 0) return byScore;
      return Number(b.id || 0) - Number(a.id || 0);
    });

    return matches[0];
  }

  private findGovernorateLocationByNames(names: string[]): LocationDTO | null {
    const normalizedNames = names.filter(Boolean).map(n => this.normalizeSelectionName(String(n)));
    if (normalizedNames.length === 0) return null;

    const matches = this.allLocations.filter(loc => {
      if (loc.category !== 'GOVERNORATE') return false;
      const locationName = this.normalizeSelectionName(loc.name || '');
      return normalizedNames.some(n => n === locationName || n.includes(locationName) || locationName.includes(n));
    });

    if (matches.length === 0) return null;

    matches.sort((a, b) => {
      const score = (loc: LocationDTO) => ((Array.isArray(loc.contents) && loc.contents.length > 0) ? 2 : 0) + (loc.id ? 1 : 0);
      const byScore = score(b) - score(a);
      if (byScore !== 0) return byScore;
      return Number(b.id || 0) - Number(a.id || 0);
    });

    return matches[0];
  }

  private getDistrictAndGovernorateIndexByName(governorateName: string): { districtId: string; index: number } | null {
    const target = this.normalizeSelectionName(governorateName);
    if (!target) return null;
    for (const [districtId, meta] of Object.entries(this.tunisiaDistrictsMapping)) {
      for (let index = 0; index < meta.members.length; index++) {
        const nameEn = meta.members[index];
        const nameAr = meta.members_ar?.[index];
        const matches = [nameEn, nameAr]
          .map(n => this.normalizeSelectionName(n || ''))
          .some(n => !!n && (n === target || n.includes(target) || target.includes(n)));
        if (matches) return { districtId, index };
      }
    }
    return null;
  }

  private getProjectDescription(project: LocationDTO): string {
    return this.getDescriptionFromContents(project?.contents as any[]) || String((project as any)?.description || '').trim();
  }

  private formatCoordinateText(lon: number, lat: number, precision: number = 5): string {
    return `${lat.toFixed(precision)}, ${lon.toFixed(precision)}`;
  }

  private resolveCoordinateTextFromGeometry(geometry: any, precision: number = 5): string | null {
    if (!geometry) return null;

    try {
      if (geometry.type === 'Point' && Array.isArray(geometry.coordinates) && geometry.coordinates.length >= 2) {
        const lon = Number(geometry.coordinates[0]);
        const lat = Number(geometry.coordinates[1]);
        if (Number.isFinite(lon) && Number.isFinite(lat)) {
          return this.formatCoordinateText(lon, lat, precision);
        }
      }

      const centroid = turf.centroid({ type: 'Feature', properties: {}, geometry } as any);
      const coords = centroid?.geometry?.coordinates;
      if (Array.isArray(coords) && coords.length >= 2) {
        const lon = Number(coords[0]);
        const lat = Number(coords[1]);
        if (Number.isFinite(lon) && Number.isFinite(lat)) {
          return this.formatCoordinateText(lon, lat, precision);
        }
      }
    } catch {
      // Ignore malformed geometry and keep sidebar rendering stable.
    }

    return null;
  }

  private getGovernorateFeatureByName(governorateName: string): any | null {
    const target = this.normalizeSelectionName(governorateName || '');
    if (!target || !Array.isArray(this.countryBorders)) return null;

    return this.countryBorders.find((feature: any) => {
      const names = [
        feature?.properties?.NAME_1,
        feature?.properties?.NAME,
        feature?.properties?.ADMIN,
        feature?.properties?.NAME_1_AR,
        feature?.properties?.NAME_1_FR,
      ]
        .filter(Boolean)
        .map((name: any) => this.normalizeSelectionName(String(name)));

      return names.some((name: string) => name === target || name.includes(target) || target.includes(name));
    }) || null;
  }

  getDistrictCoordinateText(districtId: string): string | null {
    const districtCenter = this.districtLabelData.get(districtId);
    if (districtCenter && Number.isFinite(districtCenter.lon) && Number.isFinite(districtCenter.lat)) {
      return this.formatCoordinateText(districtCenter.lon, districtCenter.lat, 5);
    }

    return this.resolveCoordinateTextFromGeometry(this.tunisiaDistrictGeometries[districtId], 5);
  }

  getGovernorateCoordinateText(governorate: any): string | null {
    if (!governorate) return null;

    const districtIdValue = governorate?.districtId;
    const districtId = districtIdValue != null && String(districtIdValue).trim() !== ''
      ? String(districtIdValue)
      : undefined;
    const memberIndexRaw = Number(governorate?.index ?? governorate?.memberIndex);
    const memberIndex = Number.isFinite(memberIndexRaw) ? memberIndexRaw : undefined;
    const governorateName = String(governorate?.name || governorate?.displayName || governorate?.location?.name || '');

    const feature = this.getMatchedGovernorateFeature(districtId, governorateName, memberIndex)
      || this.getGovernorateFeatureByName(governorateName);

    if (feature?.geometry) {
      return this.resolveCoordinateTextFromGeometry(feature.geometry, 5);
    }

    return this.resolveCoordinateTextFromGeometry(governorate?.location?.geometry, 5);
  }

  getProjectCoordinateText(project: LocationDTO | null | undefined): string | null {
    if (!project?.geometry) return null;
    return this.resolveCoordinateTextFromGeometry(project.geometry, 6);
  }

  getLocationCoordinateText(location: LocationDTO | null | undefined): string | null {
    if (!location) return null;
    if (location.geometry) return this.resolveCoordinateTextFromGeometry(location.geometry, 5);
    if (location.category === 'GOVERNORATE') {
      return this.getGovernorateCoordinateText({ name: location.name });
    }
    return null;
  }

  getProjectResolvedLocationText(project: LocationDTO | null | undefined): string {
    if (!project) return '';

    const projectData: any = project;
    const governorateName = String(projectData?.governorateName || '').trim();
    const districtName = String(projectData?.districtName || '').trim();
    if (governorateName) {
      return districtName ? `${governorateName}, ${districtName}` : governorateName;
    }

    const parentId = projectData?.parentId;
    if (parentId != null) {
      const parentLocation = this.findLocationById(parentId);
      if (parentLocation?.name) {
        const mapping = this.getDistrictAndGovernorateIndexByName(parentLocation.name);
        if (mapping) {
          const districtMeta = this.tunisiaDistrictsMapping[mapping.districtId];
          const districtLabel = districtMeta
            ? (this.currentLanguage === 'ar'
                ? districtMeta.name_ar
                : this.currentLanguage === 'fr'
                  ? districtMeta.name_fr
                  : districtMeta.name_en)
            : '';
          const governorateLabel = this.getGovernorateDisplayName(mapping.districtId, mapping.index) || parentLocation.name;
          return districtLabel ? `${governorateLabel}, ${districtLabel}` : governorateLabel;
        }

        return parentLocation.name;
      }
    }

    const fallbackName = String(projectData?.locationName || projectData?.zoneName || '').trim();
    return fallbackName;
  }

  getProjectSidebarDisplayName(project: LocationDTO | null | undefined): string {
    if (!project) return '';

    const projectData: any = project;
    const candidates = [
      projectData?.name,
      projectData?.displayName,
      projectData?.projectName,
      projectData?.locationName,
      projectData?.zoneName,
      this.getProjectResolvedLocationText(project),
    ];

    for (const candidate of candidates) {
      const text = String(candidate || '').trim();
      if (text) return text;
    }

    return this.i18nService.translate('sidebar.unknown_project');
  }

  private getGovernorateProjects(location: LocationDTO | null, governorateName: string): SidebarHierarchyProjectNode[] {
    const byId = (location?.id != null)
      ? this.allProjects.filter(p => String((p as any)?.parentId) === String(location.id))
      : [];

    const strict = this.getStrictGovernorateProjects(location, governorateName);
    const merged = this.deduplicateProjects([...(byId || []), ...(strict || [])]);

    if (merged.length === 0) {
      console.warn(`[getGovernorateProjects] No projects matched governorate="${governorateName}" locationId="${location?.id ?? 'none'}"`);
    }

    return merged.map(project => ({
      project,
      image: this.getImageFromContents(project?.contents as any[]),
      description: this.getProjectDescription(project),
    }));
  }

  private deduplicateProjects(projects: LocationDTO[]): LocationDTO[] {
    const deduped = new Map<string, LocationDTO>();
    projects.forEach(project => {
      const projectId = project?.id;
      if (projectId != null) { deduped.set(`id:${projectId}`, project); return; }
      const coordinates = Array.isArray(project?.geometry?.coordinates)
        ? `${project.geometry.coordinates[0]}:${project.geometry.coordinates[1]}`
        : 'no-geometry';
      deduped.set(`name:${this.normalizeSelectionName(project?.name || '')}:${coordinates}`, project);
    });
    return Array.from(deduped.values());
  }

  // ════════════════════════════════════════════════════════════════════════════
  // PROJECT DISPLAY (all routes go through setSidebarProjects)
  // ════════════════════════════════════════════════════════════════════════════

  private showGovernorateHierarchyProjectsOnMap(governorate: SidebarHierarchyGovernorateNode): void {
    const directProjects = governorate.projects.map(n => n.project);
    if (directProjects.length > 0) {
      this.setSidebarProjects(directProjects, `hierarchy_governorate_${governorate.districtId}_${governorate.index}`);
      return;
    }
    const strict = this.getStrictGovernorateProjects(governorate.location, governorate.name);
    this.setSidebarProjects(strict, `hierarchy_governorate_${governorate.districtId}_${governorate.index}`);
  }

  private showDistrictHierarchyProjectsOnMap(district: SidebarHierarchyDistrictNode): void {
    const nodeProjects = district.governorates.flatMap(gov => gov.projects.map(n => n.project));
    if (nodeProjects.length > 0) {
      this.setSidebarProjects(nodeProjects, `hierarchy_district_${district.id}`);
      return;
    }
    const strict = this.getStrictDistrictProjects(district.id);
    this.setSidebarProjects(strict, `hierarchy_district_${district.id}`);
  }

  // Keep old name as alias so nothing breaks
  private showProjectPinsForHierarchy(projects: LocationDTO[], contextKey: string): void {
    this.setSidebarProjects(projects, contextKey);
  }

  // ════════════════════════════════════════════════════════════════════════════
  // HIERARCHY DATA LOADING
  // ════════════════════════════════════════════════════════════════════════════

  private async ensureHierarchyDataReady(): Promise<void> {
    if (!this.currentMap?.id) return;
    const hasGovernorates = this.allLocations.some(l => l.category === 'GOVERNORATE');
    if (!this.allLocations.length || !hasGovernorates) {
      try {
        const locations = await this.locationsService.getLocationsByMap(this.currentMap.id).toPromise();
        if (Array.isArray(locations)) this.allLocations = locations;
      } catch { /* keep existing */ }
    }
    if (!this.allProjects.length) {
      try {
        const locations = await this.locationsService.getLocationsByMapWithGeometry(this.currentMap.id, true).toPromise();
        this.allProjects = (locations || []).filter((l: any) => l.category === 'PROJECT');
      } catch { /* keep existing */ }
    }
  }

  private async buildDistrictHierarchy(districtId: string): Promise<SidebarHierarchyDistrictNode | null> {
    await this.ensureHierarchyDataReady();
    const meta = this.tunisiaDistrictsMapping[districtId];
    if (!meta) return null;
    const districtLocation = this.findDistrictLocationByMeta(meta);
    const districtName = this.currentLanguage === 'ar' ? meta.name_ar : this.currentLanguage === 'fr' ? meta.name_fr : meta.name_en;
    const governorates: SidebarHierarchyGovernorateNode[] = meta.members.map((memberName: string, index: number) => {
      const memberAr = meta.members_ar?.[index] || memberName;
      const displayName = this.currentLanguage === 'ar' ? memberAr : memberName;
      const governorateLocation = this.findGovernorateLocationByNames([
        memberName, memberAr,
        (this.allGovernorates.find((gov: any) => gov.districtId === districtId && Number(gov.index) === index)?.displayName || ''),
      ]);
      return {
        districtId, index, name: memberName, displayName,
        location: governorateLocation,
        image: this.extractLocationImage(governorateLocation),
        description: this.extractLocationDescription(governorateLocation),
        projects: this.getGovernorateProjects(governorateLocation, memberName),
      };
    });
    return {
      id: districtId, name: meta.name_en, displayName: districtName,
      location: districtLocation,
      image: this.extractLocationImage(districtLocation),
      description: this.extractLocationDescription(districtLocation),
      governorates,
    };
  }

  private syncLegacySelectionFromHierarchy(): void {
    if (!this.hierarchyState.view) return;
    this.syncLegacySelectionFromHierarchyView(this.hierarchyState.view);
  }

  private syncLegacySelectionFromHierarchyView(view: SidebarHierarchyView): void {
    const district = this.getHierarchyDistrict();
    if (!district) return;

    if (view === 'district') {
      this.selectedProject = null;
      this.selectedDistrict = {
        id: district.id,
        meta: this.tunisiaDistrictsMapping[district.id] || { name_en: district.name, name_ar: district.name, name_fr: district.name },
      };
      this.selectedLocation = district.location || {
        id: 0, name: district.name, category: 'DISTRICT', type: LocationType.ZONE,
        mapId: this.currentMap?.id || 0, contents: [], geometry: null,
      } as any;
      this.selectedName = district.displayName;
      this.selectedContents = (this.selectedLocation?.contents || []) as any;
      return;
    }

    const governorate = this.getHierarchyGovernorate();
    if (view === 'governorate' && governorate) {
      this.selectedProject = null;
      this.selectedDistrict = null;
      this.selectedLocation = governorate.location || {
        id: 0, name: governorate.name, category: 'GOVERNORATE', type: LocationType.ZONE,
        mapId: this.currentMap?.id || 0, contents: [], geometry: null,
      } as any;
      this.selectedName = governorate.displayName;
      this.selectedContents = (this.selectedLocation?.contents || []) as any;
      return;
    }

    const project = this.getHierarchyProject();
    if (view === 'project' && project) {
      this.selectedDistrict = null;
      this.selectedLocation = null;
      this.selectedProject = project.project;
      this.selectedName = project.project.name;
      this.selectedContents = (project.project.contents || []) as any;
    }
  }

  // ════════════════════════════════════════════════════════════════════════════
  // focusProjectOnMap — uses unified zoom
  // ════════════════════════════════════════════════════════════════════════════
  private focusProjectOnMap(project: LocationDTO, zoomHeight: number = this.ZOOM_HEIGHT): void {
    if (!project) return;
    const entity = this.viewer.entities.getById(`project-pin-${project.id}`);
    if (entity) {
      this.highlightSelectedPin(entity);
      this.zoomToEntityWithHeight(entity, zoomHeight);
      this.positionSelectionFrameAtEntity(entity);
      return;
    }
    if (project.geometry?.type === 'Point' && Array.isArray(project.geometry.coordinates)) {
      const lon = Number(project.geometry.coordinates[0]);
      const lat = Number(project.geometry.coordinates[1]);
      if (!isNaN(lon) && !isNaN(lat)) {
        this.zoomToCoordinatesWithHeight(lon, lat, zoomHeight);
        this.positionSelectionFrameAtCartesian(Cesium.Cartesian3.fromDegrees(lon, lat));
      }
    }
  }

  private hydrateDistrictNode(district: SidebarHierarchyDistrictNode): void {
    const districtId = district.location?.id;
    if (!districtId) return;
    this.locationsService.getLocationDetails(districtId).subscribe({
      next: (fullLocation: LocationDTO) => {
        district.location = { ...(district.location as any), ...(fullLocation as any) };
        district.image = this.extractLocationImage(district.location) || district.image;
        district.description = this.extractLocationDescription(district.location) || district.description;
        // Do not show all district projects when viewing a district
        // Projects only show when a specific governorate is selected
        if (this.hierarchyState.view === 'district' && this.hierarchyState.selectedDistrict?.id === district.id) {
          this.clearProjectsFromMap();
        }
        this.syncLegacySelectionFromHierarchy();
        this.scheduleChangeDetection();
      },
      error: () => {}
    });
  }
// Drop-in cached wrapper — replaces direct locationsService.getLocationDetails() calls
private getLocationDetailsCached(id: number): Observable<LocationDTO> {
  const cached = this._locationDetailsCache.get(id);
  if (cached && this.isCacheValid(cached.ts)) {
    return of(cached.data);
  }
  return this.locationsService.getLocationDetails(id).pipe(
    tap(detail => {
      this._locationDetailsCache.set(id, { data: detail, ts: Date.now() });
    })
  );
}
  private hydrateGovernorateNode(governorate: SidebarHierarchyGovernorateNode): void {
    const governorateId = governorate.location?.id;
    if (!governorateId) return;
this.getLocationDetailsCached(governorateId).subscribe({
      next: (fullLocation: LocationDTO) => {
        governorate.location = { ...(governorate.location as any), ...(fullLocation as any) };
        governorate.image = this.extractLocationImage(governorate.location) || governorate.image;
        governorate.description = this.extractLocationDescription(governorate.location) || governorate.description;
        const refreshedProjects = this.getGovernorateProjects(governorate.location, governorate.name);
        if (refreshedProjects.length > 0) governorate.projects = refreshedProjects;
        const currentGovernorate = this.getHierarchyGovernorate();
        const isCurrent = !!currentGovernorate
          && currentGovernorate.districtId === governorate.districtId
          && Number(currentGovernorate.index) === Number(governorate.index);
        if (isCurrent) this.showGovernorateHierarchyProjectsOnMap(governorate);
        this.syncLegacySelectionFromHierarchy();
        this.scheduleChangeDetection();
      },
      error: () => {}
    });
  }

  private hydrateProjectNode(projectNode: SidebarHierarchyProjectNode): void {
    const projectId = projectNode.project?.id;
    if (!projectId) return;
    this.locationsService.getLocationDetails(projectId).subscribe({
      next: (fullProject: LocationDTO) => {
        projectNode.project = { ...(projectNode.project as any), ...(fullProject as any) };
        projectNode.image = this.getImageFromContents(projectNode.project.contents as any[]) || projectNode.image;
        projectNode.description = this.getProjectDescription(projectNode.project) || projectNode.description;
        this.syncLegacySelectionFromHierarchy();
        this.scheduleChangeDetection();
      },
      error: () => {}
    });
  }

  private async openDistrictHierarchy(
    districtId: string,
    options: { governorateIndex?: number; projectId?: string | number; targetView?: SidebarHierarchyView; } = {}
  ): Promise<boolean> {
    const district = await this.buildDistrictHierarchy(districtId);
    if (!district) return false;
    this.hierarchyState.selectedDistrict = district;
    this.hierarchyState.selectedGovernorateIndex = Math.max(
      0, Math.min(options.governorateIndex ?? 0, Math.max(0, district.governorates.length - 1))
    );
    this.hierarchyState.selectedProjectIndex = 0;
    if (options.projectId != null) {
      const governorate = this.getHierarchyGovernorate();
      if (governorate) {
        const projectIndex = governorate.projects.findIndex(n => String(n.project.id) === String(options.projectId));
        if (projectIndex >= 0) this.hierarchyState.selectedProjectIndex = projectIndex;
      }
    }
    this.showRightSidebar = true;
    this.showLocationsList = false;
    this.applyHierarchyView(options.targetView || 'district');
    this.hydrateDistrictNode(district);
    const currentGovernorate = this.getHierarchyGovernorate();
    if (currentGovernorate) this.hydrateGovernorateNode(currentGovernorate);
    // Only show projects when a governorate is selected, not when viewing a district
    if ((options.targetView || 'district') === 'governorate' && currentGovernorate) {
      this.showGovernorateHierarchyProjectsOnMap(currentGovernorate);
    } else {
      this.clearProjectsFromMap();
    }
    this.syncLegacySelectionFromHierarchyView(options.targetView || 'district');
    this.scheduleChangeDetection();
    return true;
  }

  private async openGovernorateHierarchyByName(
    governorateName: string, districtId?: string, districtMemberIndex?: number
  ): Promise<boolean> {
    let resolvedDistrictId = districtId;
    let resolvedGovernorateIndex = typeof districtMemberIndex === 'number' ? districtMemberIndex : -1;
    if (!resolvedDistrictId || resolvedGovernorateIndex < 0) {
      const mapping = this.getDistrictAndGovernorateIndexByName(governorateName);
      if (mapping) {
        resolvedDistrictId = mapping.districtId;
        if (resolvedGovernorateIndex < 0) resolvedGovernorateIndex = mapping.index;
      }
    }
    if (!resolvedDistrictId) return false;
    const opened = await this.openDistrictHierarchy(resolvedDistrictId, {
      governorateIndex: Math.max(0, resolvedGovernorateIndex),
      targetView: 'governorate',
    });
    if (!opened) return false;
    const governorate = this.getHierarchyGovernorate();
    if (governorate) {
      // zoom already fired before await — no duplicate zoom here
      this.showGovernorateHierarchyProjectsOnMap(governorate);
    }
    this.syncLegacySelectionFromHierarchy();
    this.scheduleChangeDetection();
    return true;
  }

  private async openProjectHierarchyFromProject(project: LocationDTO): Promise<boolean> {
    if (!project) return false;
    await this.ensureHierarchyDataReady();
    let mapping: { districtId: string; index: number } | null = null;
    const parentId = (project as any)?.parentId;
    const governorateLocation = this.findLocationById(parentId);
    if (governorateLocation?.name) mapping = this.getDistrictAndGovernorateIndexByName(governorateLocation.name);
    if (!mapping) {
      const governorateName = String((project as any)?.governorateName || '');
      mapping = this.getDistrictAndGovernorateIndexByName(governorateName);
    }
    if (!mapping) return false;
    const opened = await this.openDistrictHierarchy(mapping.districtId, {
      governorateIndex: mapping.index, projectId: project.id, targetView: 'project',
    });
    if (!opened) return false;
    const selectedGovernorate = this.getHierarchyGovernorate();
    if (selectedGovernorate) this.showGovernorateHierarchyProjectsOnMap(selectedGovernorate);
    const selectedProjectNode = this.getHierarchyProject();
    if (selectedProjectNode) this.hydrateProjectNode(selectedProjectNode);
    // Unified zoom to project
    this.focusProjectOnMap(project);
    this.syncLegacySelectionFromHierarchy();
    this.scheduleChangeDetection();
    return true;
  }

  // ════════════════════════════════════════════════════════════════════════════
  // ENTITY LOOKUP HELPERS (unchanged)
  // ════════════════════════════════════════════════════════════════════════════

  private animateCameraToCartesian(position: Cesium.Cartesian3, height: number, duration: number = 0.9): void {
    const carto = Cesium.Cartographic.fromCartesian(position);
    this.viewer.camera.flyTo({
      destination: Cesium.Cartesian3.fromDegrees(
        Cesium.Math.toDegrees(carto.longitude), Cesium.Math.toDegrees(carto.latitude), height
      ),
      orientation: { heading: 0, pitch: Cesium.Math.toRadians(-40), roll: 0 },
      duration,
      easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT,
    });
  }

  private getGovernorateEntityByName(governorateName: string): Cesium.Entity | null {
    const target = this.normalizeSelectionName(governorateName);
    const isPinEntity = (e: Cesium.Entity | null | undefined): e is Cesium.Entity =>
      !!e && !!e.billboard && !!e.position;

    const direct = this.governorateEntities.get(governorateName);
    if (isPinEntity(direct)) return direct;

    let nonPinMatch: Cesium.Entity | null = null;
    for (const [key, entity] of this.governorateEntities.entries()) {
      const keyNorm = this.normalizeSelectionName(key);
      const keyMatches = keyNorm === target || keyNorm.includes(target) || target.includes(keyNorm);
      const now = Cesium.JulianDate.now();
      const props = entity.properties && typeof entity.properties.getValue === 'function'
        ? entity.properties.getValue(now) : (entity.properties as any);
      const names = [props?.name_en, props?.name_fr, props?.name_ar]
        .filter(Boolean).map((n: any) => this.normalizeSelectionName(String(n)));
      const propMatches = names.some((n: string) => n === target || n.includes(target) || target.includes(n));
      if (!keyMatches && !propMatches) continue;
      if (isPinEntity(entity)) return entity;
      if (!nonPinMatch) nonPinMatch = entity;
    }
    if (isPinEntity(nonPinMatch)) return nonPinMatch;

    for (const entity of this.displayedDistrictEntities) {
      if (!isPinEntity(entity)) continue;
      const now = Cesium.JulianDate.now();
      const props = entity.properties && typeof entity.properties.getValue === 'function'
        ? entity.properties.getValue(now) : (entity.properties as any);
      if (!props?.isGovernorate) continue;
      const candidateNames = [props?.name_en, props?.name_fr, props?.name_ar]
        .filter(Boolean).map((n: any) => this.normalizeSelectionName(String(n)));
      if (candidateNames.some((n: string) => n === target || n.includes(target) || target.includes(n))) return entity;
    }
    return null;
  }

  private getGovernorateLabelEntityIdFromFeature(feature: any): string | null {
    const baseName = feature?.properties?.NAME_1 || feature?.properties?.NAME || feature?.properties?.ADMIN;
    if (!baseName) return null;
    return `ADM1_label_${baseName}`;
  }

  private getGovernoratePinByDistrictIndex(districtId?: string, memberIndex?: number): Cesium.Entity | null {
    if (!districtId || typeof memberIndex !== 'number') return null;
    for (const entity of this.displayedDistrictEntities) {
      if (!entity?.billboard || !entity?.position) continue;
      const now = Cesium.JulianDate.now();
      const props = entity.properties && typeof entity.properties.getValue === 'function'
        ? entity.properties.getValue(now) : (entity.properties as any);
      if (!props?.isGovernorate) continue;
      if (String(props?.districtId) === String(districtId) && Number(props?.memberIndex) === Number(memberIndex)) return entity;
    }
    return null;
  }

  private getMatchedGovernorateFeature(districtId: string | undefined, governorateName: string, districtMemberIndex?: number): any | null {
    if (!districtId) return null;
    const matched = this.tunisiaDistrictMatchedFeatures[districtId] || [];
    if (!matched.length) return null;

    const targetNorms = new Set<string>();
    const governorateTarget = this.normalizeSelectionName(governorateName);
    if (governorateTarget) targetNorms.add(governorateTarget);

    const districtMeta = this.tunisiaDistrictsMapping[districtId];
    if (districtMeta && typeof districtMemberIndex === 'number') {
      const memberName = districtMeta.members?.[districtMemberIndex];
      const memberNameAr = districtMeta.members_ar?.[districtMemberIndex];
      if (memberName) targetNorms.add(this.normalizeSelectionName(memberName));
      if (memberNameAr) targetNorms.add(this.normalizeSelectionName(memberNameAr));
    }

    const byName = matched.find((feature: any) => {
      const featureNames = [
        feature?.properties?.NAME_1, feature?.properties?.NAME,
        feature?.properties?.ADMIN, feature?.properties?.NAME_1_AR, feature?.properties?.NAME_1_FR,
      ].filter(Boolean).map((n: any) => this.normalizeSelectionName(String(n)));
      return featureNames.some((fn: string) => {
        for (const t of targetNorms) {
          if (fn === t || fn.includes(t) || t.includes(fn)) return true;
        }
        return false;
      });
    });

    if (byName) return byName;
    if (typeof districtMemberIndex === 'number' && matched[districtMemberIndex]) return matched[districtMemberIndex];
    return null;
  }

  private getDistrictSelectionKey(districtId: string): string { return `district:${districtId}`; }
  private getGovernorateSelectionKey(governorateName: string): string { return `governorate:${this.normalizeSelectionName(governorateName)}`; }

  private getSelectionKeyFromEntity(entity: Cesium.Entity | undefined): string | null {
    if (!entity?.properties) return null;
    const now = Cesium.JulianDate.now();
    const props: any = entity.properties;
    const getProp = (key: string) => { const v = props[key]; return v?.getValue ? v.getValue(now) : v; };
    const districtId = getProp('districtId');
    const labelType  = getProp('labelType');
    if (districtId && labelType === 'district') return this.getDistrictSelectionKey(String(districtId));
    const location = getProp('location');
    if (location?.category === 'GOVERNORATE' && location?.name) return this.getGovernorateSelectionKey(String(location.name));
    const isGovernorate = getProp('isGovernorate');
    if (isGovernorate) {
      const govProps = getProp('govProps');
      const nameEn   = getProp('name_en');
      const name     = nameEn || govProps?.NAME_1 || govProps?.NAME || govProps?.ADMIN;
      if (name) return this.getGovernorateSelectionKey(String(name));
    }
    return null;
  }

  private bindViewerSelectionSync(): void {
    if (!this.viewer) return;
    this.selectedEntityChangedListener = (entity: Cesium.Entity | undefined) => {
      this.activeSidebarItemKey = this.getSelectionKeyFromEntity(entity);
      if (entity?.properties) {
        const now = Cesium.JulianDate.now();
        const props: any = entity.properties;
        const getProp = (key: string) => { const v = props[key]; return v?.getValue ? v.getValue(now) : v; };
        const districtId = getProp('districtId');
        if (districtId && getProp('labelType') === 'district') {
          this.syncNavIndexToEntity('district', districtId);
        } else if (getProp('isGovernorate')) {
          const nameEn = getProp('name_en') || getProp('govProps')?.NAME_1 || '';
          if (nameEn) {
            const nameNorm = this.normalizeSelectionName(String(nameEn));
            const idx = this.unifiedNavList.findIndex(
              i => i.type === 'governorate' && this.normalizeSelectionName(String(i.data?.name || i.label)) === nameNorm
            );
            if (idx >= 0) this.unifiedNavIndex = idx;
          }
        } else {
          const loc = getProp('location');
          if (loc?.id) this.syncNavIndexToEntity('location', loc.id);
          const projData = getProp('projectData');
          if (projData?.id) {
            this.syncNavIndexToEntity('project', projData.id);
          }
        }
      }
      this.scheduleChangeDetection();
    };
    this.viewer.selectedEntityChanged.addEventListener(this.selectedEntityChangedListener);
  }

  /**
   * Called when the user clicks a project pin on the map.
   * Updates sidebar selection to match — same handler as sidebar click.
   */
  private setSelectedProjectFromMapClick(projectData: LocationDTO): void {
    // Find the full project from allProjects (may have richer data)
    const fullProject = this.allProjects.find(p => String(p.id) === String(projectData.id)) || projectData;
    this.selectedProject  = fullProject;
    this.selectedLocation = null;
    this.selectedDistrict = null;
    this.selectedName     = fullProject.name;
    this.selectedContents = (fullProject as any).contents || [];
    this.showLocationsList = false;
    this.showRightSidebar  = true;
    // Highlight the project on the map with shadow/glow effect
    this.highlightProjectEntity(fullProject.id);
    // Highlight in sidebar — sidebarProjects stays unchanged (scoped to district/governorate)
    // so the project is already in the list; just update selectedProject for highlighting
    this.scheduleChangeDetection();
  }

  isDistrictCardActive(district: any): boolean {
    return !!district?.id && this.activeSidebarItemKey === this.getDistrictSelectionKey(String(district.id));
  }
  isGovernorateCardActive(governorate: any): boolean {
    const n = governorate?.name || governorate?.displayName;
    return !!n && this.activeSidebarItemKey === this.getGovernorateSelectionKey(String(n));
  }
  isLocationCardActive(location: any): boolean {
    return !!location?.id && this.selectedLocation?.id === location.id;
  }
  isProjectCardActive(project: any): boolean {
    return !!project?.id && this.selectedProject?.id === project.id;
  }

  private getCachedDiamondPin(color: string, size: number = 56): string {
    const cacheKey = `diamond_${color}_${size}`;
    if (this.pinIconCache.has(cacheKey)) return this.pinIconCache.get(cacheKey)!;
    const pinIcon = this.createDiamondPinIcon(color, size);
    this.pinIconCache.set(cacheKey, pinIcon);
    return pinIcon;
  }

  private createDiamondScaleByDistance(): Cesium.NearFarScalar {
    return new Cesium.NearFarScalar(1.0e3, 1.0, 1.0e6, 0.3);
  }

  toggleSector(sector: SectorDTO | any) {
    const id = Number(sector.id ?? sector.sectorId ?? sector.id);
    if (this.openSectorIds.has(id)) this.openSectorIds.delete(id);
    else this.openSectorIds.add(id);
  }

  isSectorOpen(sector: SectorDTO | any): boolean {
    const id = Number(sector.id ?? sector.sectorId ?? sector.id);
    return this.openSectorIds.has(id);
  }

  selectSector(sector: any) { this.selectedSector = sector; }

  // ════════════════════════════════════════════════════════════════════════════
  // UNIFIED NAVIGATION
  // ════════════════════════════════════════════════════════════════════════════

  buildUnifiedNavList(): void {
    this.unifiedNavList = [];
    Object.keys(this.tunisiaDistrictsMapping).forEach(id => {
      const meta = this.tunisiaDistrictsMapping[id];
      this.unifiedNavList.push({
        type: 'district', id,
        label: this.currentLanguage === 'ar' ? meta.name_ar : this.currentLanguage === 'fr' ? meta.name_fr : meta.name_en,
        data: { id, ...meta }
      });
    });
    Object.keys(this.tunisiaDistrictsMapping).forEach(districtId => {
      const meta = this.tunisiaDistrictsMapping[districtId];
      meta.members.forEach((memberName: string, idx: number) => {
        this.unifiedNavList.push({
          type: 'governorate', id: `gov_${districtId}_${idx}`,
          label: this.currentLanguage === 'ar' && meta.members_ar ? meta.members_ar[idx] : memberName,
          data: { districtId, index: idx, name: memberName, displayName: memberName,
                  name_ar: meta.members_ar ? meta.members_ar[idx] : memberName }
        });
      });
    });
    this.allLocations.forEach(loc => {
      this.unifiedNavList.push({ type: 'location', id: loc.id!, label: loc.name, data: loc });
    });
    this.allProjects.forEach(proj => {
      this.unifiedNavList.push({ type: 'project', id: proj.id!, label: proj.name, data: proj });
    });
  }

  navigateToUnifiedItem(item: any): void {
    this.animateSidebarForMapSelection(
      { type: `explore_${item?.type || 'item'}`, data: item?.data || item },
      async () => {
        this.showRightSidebar = true;
        this.showLocationsList = false;

        switch (item.type) {
          case 'district':
            this.selectedDistrict = { id: item.id, meta: item.data };
            this.selectedProject  = null;
            this.selectedName     = item.label;
            this.selectedContents = [];
            this.selectedLocation = {
              id: 0, name: item.label, category: 'DISTRICT',
              type: LocationType.ZONE, mapId: this.currentMap?.id || 0, contents: [], geometry: null
            };
            this.activeSidebarItemKey = this.getDistrictSelectionKey(String(item.id));
            this.cdr.detectChanges();
            this.zoomToDistrict(item.id);
            this.showDistrict(item.id, true);
            this.getDistrictAsLocation().then(loc => {
              if (loc?.id && loc.id > 0) {
                this.selectedLocation = loc;
                this.selectedContents = loc.contents || [];
                this.cdr.detectChanges();
              }
            }).catch(() => {});
            break;

          case 'governorate':
            this.selectedDistrict = null;
            this.selectedProject  = null;
            this.selectedName     = item.label;
            this.selectedContents = [];
            this.selectedLocation = {
              id: 0, name: item.data.name, category: 'GOVERNORATE',
              type: LocationType.ZONE, mapId: this.currentMap?.id || 0, contents: [], geometry: null
            };
            this.activeSidebarItemKey = this.getGovernorateSelectionKey(item.data.name);
            this.cdr.detectChanges();
            this.zoomToGovernorate(item.data.name, item.data.districtId, item.data.index);
            this.getGovernorateAsLocation(item.data.name).then(loc => {
              this.selectedLocation = loc;
              this.selectedContents = loc.contents || [];
              this.cdr.detectChanges();
            }).catch(() => {});
            break;

          case 'location':
            this.selectedDistrict = null;
            this.selectedProject  = null;
            this.selectedLocation = item.data;
            this.selectedName     = item.data.name;
            this.selectedContents = item.data.contents || [];
            this.activeSidebarItemKey = null;
            this.cdr.detectChanges();
            this.selectLocationFromList(item.data);
            break;

          case 'project':
            this.selectedDistrict = null;
            this.selectedProject  = item.data;
            this.selectedLocation = null;
            this.selectedName     = item.data.name;
            this.selectedContents = item.data.contents || [];
            this.activeSidebarItemKey = null;
            this.cdr.detectChanges();
            this.selectProject(item.data, this.EXPLORE_PROJECT_ZOOM_HEIGHT);
            break;
        }
      }
    ).catch(() => {});
  }

  syncNavIndexToEntity(type: string, id: string | number): void {
    if (this.unifiedNavList.length === 0) return;
    const idx = this.unifiedNavList.findIndex(item => item.type === type && String(item.id) === String(id));
    if (idx >= 0) this.unifiedNavIndex = idx;
  }

  private getExploreScopeType(): 'district' | 'governorate' | 'project' | null {
    if (!this.showLocationsList) {
      if (this.selectedProject) return 'project';
      if (this.selectedDistrict) return 'district';
      if (this.selectedLocation?.category === 'GOVERNORATE') return 'governorate';
      if (this.selectedLocation?.category === 'DISTRICT') return 'district';
    }
    if (this.selectedRightSidebarTab === 'DISTRICTS') return 'district';
    if (this.selectedRightSidebarTab === 'GOVERNORATES') return 'governorate';
    if (this.selectedRightSidebarTab === 'SECTORS') return 'project';
    return null;
  }

  private getScopedExploreItems(): Array<{ type: 'district' | 'governorate' | 'location' | 'project'; id: string | number; label: string; data: any }> {
    const scopeType = this.getExploreScopeType();
    if (!scopeType) return this.unifiedNavList;
    const scopedItems = this.unifiedNavList.filter(item => item.type === scopeType);
    if (scopeType !== 'project') return scopedItems;
    return this.getCountryScopedProjectItems(scopedItems);
  }

  private getCountryScopedParentIds(): Set<string> {
    const scopedParentIds = new Set<string>();
    if (!this.countryLocationId || this.allLocations.length === 0) return scopedParentIds;
    const childrenByParent = new Map<string, string[]>();
    this.allLocations.forEach((location: any) => {
      const id = location?.id; const parentId = location?.parentId;
      if (id == null || parentId == null) return;
      const parentKey = String(parentId);
      const existing = childrenByParent.get(parentKey) || [];
      existing.push(String(id));
      childrenByParent.set(parentKey, existing);
    });
    const queue: string[] = [String(this.countryLocationId)];
    while (queue.length > 0) {
      const parentKey = queue.shift()!;
      const children = childrenByParent.get(parentKey) || [];
      children.forEach(childId => {
        if (scopedParentIds.has(childId)) return;
        scopedParentIds.add(childId);
        queue.push(childId);
      });
    }
    return scopedParentIds;
  }

  private getCountryScopedProjectItems(
    projectItems: Array<{ type: 'district' | 'governorate' | 'location' | 'project'; id: string | number; label: string; data: any }>
  ): Array<{ type: 'district' | 'governorate' | 'location' | 'project'; id: string | number; label: string; data: any }> {
    if (projectItems.length === 0) return projectItems;
    // Use sidebarProjects as the filter source (single source of truth)
    if (this.sidebarProjects.length > 0) {
      const visibleIds = new Set(this.sidebarProjects.map(p => String(p.id)));
      const filtered = projectItems.filter(item => visibleIds.has(String(item.id)));
      if (filtered.length > 0) return filtered;
    }
    const countryScopedParentIds = this.getCountryScopedParentIds();
    if (countryScopedParentIds.size > 0) {
      const filteredByCountry = projectItems.filter(item => {
        const parentId = item.data?.parentId;
        return parentId != null && countryScopedParentIds.has(String(parentId));
      });
      if (filteredByCountry.length > 0) return filteredByCountry;
    }
    const selectedParentId = (this.selectedProject as any)?.parentId;
    if (selectedParentId != null) {
      const filteredByParent = projectItems.filter(item => String(item.data?.parentId) === String(selectedParentId));
      if (filteredByParent.length > 0) return filteredByParent;
    }
    return projectItems;
  }

  private getScopedExploreIndex(scopedItems: Array<{ type: 'district' | 'governorate' | 'location' | 'project'; id: string | number; label: string; data: any }>): number {
    if (scopedItems.length === 0) return -1;
    const scopeType = scopedItems[0].type;
    if (scopeType === 'district') {
      const selectedDistrictId = this.selectedDistrict?.id;
      if (selectedDistrictId != null) return scopedItems.findIndex(item => String(item.id) === String(selectedDistrictId));
      if (this.activeSidebarItemKey?.startsWith('district:')) {
        const keyDistrictId = this.activeSidebarItemKey.split(':')[1];
        return scopedItems.findIndex(item => String(item.id) === String(keyDistrictId));
      }
    }
    if (scopeType === 'governorate') {
      const selectedGovernorate = this.selectedLocation?.category === 'GOVERNORATE' ? this.selectedLocation.name : null;
      if (selectedGovernorate) {
        const target = this.normalizeSelectionName(selectedGovernorate);
        return scopedItems.findIndex(item => this.normalizeSelectionName(String(item.data?.name || item.label)) === target);
      }
      if (this.activeSidebarItemKey?.startsWith('governorate:')) {
        const keyName = this.activeSidebarItemKey.replace('governorate:', '');
        return scopedItems.findIndex(item => this.normalizeSelectionName(String(item.data?.name || item.label)) === keyName);
      }
    }
    if (scopeType === 'project') {
      const selectedProjectId = this.selectedProject?.id;
      if (selectedProjectId != null) return scopedItems.findIndex(item => String(item.id) === String(selectedProjectId));
    }
    if (this.unifiedNavIndex >= 0 && this.unifiedNavIndex < this.unifiedNavList.length) {
      const currentItem = this.unifiedNavList[this.unifiedNavIndex];
      const fallbackIdx = scopedItems.findIndex(item => item.type === currentItem.type && String(item.id) === String(currentItem.id));
      if (fallbackIdx >= 0) return fallbackIdx;
    }
    return -1;
  }

  getExploreCurrentLabel(): string {
    const scopedItems = this.getScopedExploreItems();
    if (scopedItems.length === 0) return '';
    const currentIdx = this.getScopedExploreIndex(scopedItems);
    return scopedItems[currentIdx >= 0 ? currentIdx : 0].label;
  }

  getExploreCurrentPosition(): number {
    const scopedItems = this.getScopedExploreItems();
    if (scopedItems.length === 0) return 0;
    const currentIdx = this.getScopedExploreIndex(scopedItems);
    return currentIdx >= 0 ? currentIdx + 1 : 1;
  }

  getExploreTotal(): number { return this.getScopedExploreItems().length; }

  navigateNext(): void {
    if (this.unifiedNavList.length === 0) this.buildUnifiedNavList();
    const scopedItems = this.getScopedExploreItems();
    if (scopedItems.length === 0) return;
    const currentIdx = this.getScopedExploreIndex(scopedItems);
    const nextIdx = currentIdx < 0 ? 0 : Math.min(currentIdx + 1, scopedItems.length - 1);
    if (nextIdx !== currentIdx) {
      const targetItem = scopedItems[nextIdx];
      this.syncNavIndexToEntity(targetItem.type, targetItem.id);
      this.navigateToUnifiedItem(targetItem);
    }
  }

  navigatePrevious(): void {
    if (this.unifiedNavList.length === 0) this.buildUnifiedNavList();
    const scopedItems = this.getScopedExploreItems();
    if (scopedItems.length === 0) return;
    const currentIdx = this.getScopedExploreIndex(scopedItems);
    if (currentIdx <= 0) return;
    const prevIdx = Math.max(currentIdx - 1, 0);
    const targetItem = scopedItems[prevIdx];
    this.syncNavIndexToEntity(targetItem.type, targetItem.id);
    this.navigateToUnifiedItem(targetItem);
  }

  shouldDisableNext(): boolean {
    const scopedItems = this.getScopedExploreItems();
    if (scopedItems.length === 0) return true;
    const currentIdx = this.getScopedExploreIndex(scopedItems);
    if (currentIdx < 0) return scopedItems.length <= 1;
    return currentIdx >= scopedItems.length - 1;
  }

  shouldDisablePrevious(): boolean {
    const scopedItems = this.getScopedExploreItems();
    if (scopedItems.length === 0) return true;
    const currentIdx = this.getScopedExploreIndex(scopedItems);
    return currentIdx <= 0;
  }

  // ════════════════════════════════════════════════════════════════════════════
  // LIFECYCLE
  // ════════════════════════════════════════════════════════════════════════════

async ngAfterViewInit() {
  (window as any).CESIUM_BASE_URL = 'assets/cesium';

 // Cesium.Ion.defaultAccessToken =
    //'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIwODlkMTlhOC1mYmU2LTRlYTYtOWY4ZS00MTc2OTdkYmVjNmEiLCJpZCI6Mzg4NjUwLCJpYXQiOjE3NzA2MTk0Mzh9.Isci4dDCXVWaugLjCP8lEl_Jgo39YKiSqa3jVC9P6BY';

  // =========================
  // CESIUM INIT (create viewer outside Angular to avoid CD churn)
  // =========================
  this.ngZone.runOutsideAngular(() => {

    this.viewer = new Cesium.Viewer('cesiumContainer', {
      
      terrainProvider: new Cesium.EllipsoidTerrainProvider(),
      animation: false,
      timeline: false,
      baseLayerPicker: !this.landingMode,
      geocoder: !this.landingMode,
      homeButton: !this.landingMode,
      navigationHelpButton: !this.landingMode,
      sceneModePicker: !this.landingMode,
      fullscreenButton: !this.landingMode,
      infoBox: false,
      selectionIndicator: true,
      creditContainer: document.createElement('div'),
      creditViewport: document.createElement('div'),
      requestRenderMode: true,
      targetFrameRate: 30,
      contextOptions: { webgl: { alpha: true } }
    });
    try {
  const color = Cesium.Color.fromCssColorString('#08044C');
  this.viewer.scene.backgroundColor = color;
  this.viewer.scene.globe.baseColor = color;
  this.viewer.scene.skyAtmosphere.show = false;
  if (this.viewer.scene.skyBox) this.viewer.scene.skyBox.show = false;
} catch (e) {}
    // In ngAfterViewInit, after this.viewer is initialized:
this.bindMapScrollPassthrough();
  // apply initial pointer-events according to input (desktop default true)
  this.updatePointerEvents();

    // Request render mode + conservative render time change to avoid continuous frames
    try {
      this.viewer.scene.requestRenderMode = true;
      this.viewer.scene.maximumRenderTimeChange = 0.1; // seconds; small change threshold
      this.viewer.resolutionScale = window.devicePixelRatio || 1;
      this.viewer.scene.globe.maximumScreenSpaceError = 1;
    } catch (e) {
      // Ignore scene tuning failures in non-browser or early-init contexts
    }

    // Configure camera controller: allow scroll zoom, disable other interactions
    try {
      const controller = this.viewer?.scene?.screenSpaceCameraController;
      if (controller) {
        controller.enableZoom = true;      // allow scroll wheel zoom
        controller.enableRotate = false;   // disable rotate (right-drag)
        controller.enableTilt = false;     // disable tilt (two-finger drag / ctrl-drag)
        controller.enableTranslate = false; // disable translate/pan
        controller.enableLook = false;     // disable look interactions
      }
    } catch (e) {
      // ignore controller configuration errors
    }
  });

  // Sync UI and app state on the Angular side
  this.syncMapFullscreenState();

  // Make container visible
  // Set dark background immediately — no white flash
const containerEl = document.getElementById('cesiumContainer');
if (containerEl) {
  containerEl.style.backgroundColor = '#08044C';
  containerEl.style.opacity = '1';
  containerEl.style.transition = 'none';
}

  this.hideCesiumBranding();
  this.forceRemoveCesiumInfoBox();
  this.bindInfoBoxRemovalObserver();

  // Detach Angular change detection for the heavy map component; we'll trigger
  // change detection manually via `this.changeDetectionSubject` when needed.
  try { this.cdr.detach(); } catch (e) {}

  this.i18nService.currentLanguage$
    .pipe(takeUntil(this.destroy$))
    .subscribe(lang => {
      this.currentLanguage = lang;
      this.updateDistrictLabels();
      this.buildUnifiedNavList();
      this.scheduleChangeDetection();
    });

  // =========================
  // DATA STREAMS (FIXED)
  // =========================
  const geojson$ = this.http.get('assets/tunisia_adm1.geojson');
  const activeMap$ = this.mapService.getActiveMap$().pipe(
    distinctUntilChanged((previous, current) => (previous?.id ?? null) === (current?.id ?? null)),
    takeUntil(this.destroy$)
  );

  // =========================
  // GEOJSON ONLY (NO MAP LOGIC)
  // =========================
  geojson$.subscribe((geojson: any) => {
    this.countryBorders = geojson.features || [];

    const prevColor = this.defaultMapBorderColor;
    const prevOpacity = this.defaultMapFillOpacity;
    const prevWidth = this.defaultMapBorderWidth;

    this.defaultMapBorderColor = '#FFFFFF';
    this.defaultMapFillOpacity = 0.0;
    this.defaultMapBorderWidth = 2;

    this.displayCountryBorders(this.focusCountryName, true);

    this.defaultMapBorderColor = prevColor;
    this.defaultMapFillOpacity = prevOpacity;
    this.defaultMapBorderWidth = prevWidth;
  });

  // =========================
  // SINGLE MAP SUBSCRIPTION (FIX)
  // =========================
  activeMap$.subscribe(map => {
    this.currentMap = map;
    this.sidebarConfigService.setActiveMapId(map?.id ?? null);

    if (map?.id) {
      this.applyResolvedSidebarConfig();
      this.cdr.markForCheck();

      this.loadDefaultMap();   // internally handles locations
      this.loadSectors();
      this.loadProjects();
    }
  });

  // =========================
  // ROUTES + EVENTS
  // =========================
  this.route.queryParams
    .pipe(takeUntil(this.destroy$))
    .subscribe(params => {
      if (params['focus']) this.pendingFocus = params['focus'];
    });

  this.initializeMapClickPipeline();
  this.enableClick();
  this.bindViewerSelectionSync();

  // =========================
  // LOCATION EVENTS (already optimized, kept as-is)
  // =========================
  this.locationsService.locationCreated$
    .pipe(takeUntil(this.destroy$))
    .subscribe((newLocation: LocationDTO) => {
      this.invalidateLocationCache(newLocation?.id ?? 0);
      this.invalidateProjectsCache();

      if (newLocation?.id != null) {
        this.locationsService.getLocationDetails(newLocation.id).subscribe({
          next: (full) => {
            const idx = this.allLocations.findIndex(l => l.id === full.id);
            if (idx >= 0) this.allLocations[idx] = full;
            else this.allLocations.push(full);

            this.refreshHierarchyNodeIfActive(full);
            this.scheduleChangeDetection();
          }
        });
      }

      if (newLocation?.category === 'PROJECT') {
        this.loadProjects();
      }

      const sidebarCategories = ['DISTRICT', 'GOVERNORATE', 'COUNTRY'];
      if (sidebarCategories.includes(newLocation?.category ?? '')) {
        this.loadLocations();
      }
    });

  this.locationsService.locationUpdated$
    .pipe(takeUntil(this.destroy$))
    .subscribe((updatedLocation: LocationDTO) => {
      this.invalidateLocationCache(updatedLocation?.id ?? 0);

      const idx = this.allLocations.findIndex(l => l.id === updatedLocation.id);
      if (idx >= 0) this.allLocations[idx] = updatedLocation;

      if (updatedLocation?.category === 'PROJECT') {
        this.invalidateProjectsCache();
        this.loadProjects();
      }

      const sidebarCategories = ['DISTRICT', 'GOVERNORATE', 'COUNTRY'];
      if (sidebarCategories.includes(updatedLocation?.category ?? '')) {
        this.loadLocations();
      }

      this.refreshHierarchyNodeIfActive(updatedLocation);
      this.scheduleChangeDetection();
    });

  this.locationsService.locationDeleted$
    .pipe(takeUntil(this.destroy$))
    .subscribe((deletedId: number) => {
      this.invalidateProjectsCache();

      if (deletedId != null) {
        this.allLocations = this.allLocations.filter(l => l.id !== deletedId);
      }

      this.loadProjects();
      this.scheduleChangeDetection();
    });

  // =========================
  // GLOBAL ERROR HANDLING
  // =========================
  window.addEventListener('unhandledrejection', (event: any) => {
    if (
      event.reason?.message?.includes('virtualearth.net') ||
      event.reason?.message?.includes('BingMaps') ||
      event.reason?.message?.includes('Imagery/Metadata')
    ) {
      event.preventDefault();
    }
  });

  // =========================
  // THEME / INTRO ANIMATION (UNCHANGED LOGIC)
  // =========================
  try {
    await this.applyNightTheme();
    if (!this.transparentBackground && this.showBlueBackdrop) this.applySoftBlueBackdrop();
  } catch (err) {
    console.warn('Night imagery failed', err);
  }

  // Intro spin - rotation always runs if runIntroNightTheme=true, UI visibility controlled by showIntroUI
  if (this.runIntroNightTheme) {
    try {
      // Show UI (progress bar, title) only if showIntroUI is true
      this.showIntroOverlay = this.showIntroUI;
      this.introPhase = 'rotating';
      this.introProgress = 0;
      this.setIntroGlobeView();

      let cameraController: any = this.viewer.scene.screenSpaceCameraController;
      const prev = cameraController.enableRotate;
      cameraController.enableRotate = false;

      try {
        // Track progress during rotation: show up to 66% during spin
        const rotationCount = 3; // number of full rotations
        const perRotationSeconds = 2; // seconds per rotation
        const rotationDuration = rotationCount * perRotationSeconds * 1000; // total ms
        const startTime = Date.now();
        const progressInterval = setInterval(() => {
          const elapsed = Date.now() - startTime;
          const rotationProgress = Math.min((elapsed / rotationDuration) * 0.66, 0.66); // 0 to 0.66
          this.introProgress = rotationProgress * 100;
          this.cdr.markForCheck();
          if (elapsed >= rotationDuration) {
            clearInterval(progressInterval);
          }
        }, 50);

        // Perform 3 rotations in total 6s (3 * 2s)
        await this.rotateGlobe(rotationCount, rotationCount * perRotationSeconds);
        clearInterval(progressInterval);
        
        // Tunisia appears: jump to 100%
        this.introProgress = 100;
        this.cdr.markForCheck();
      } finally {
        cameraController.enableRotate = prev;
      }

      this.hasCompletedTunisiaIntroSpin = true;
      
      // Wait a moment then hide overlay
      await new Promise(resolve => setTimeout(resolve, 800));
      this.showIntroOverlay = false;
      this.introPhase = 'complete';
      this.cdr.markForCheck();
      
      this.focusTunisiaUpright(1.2);
    } catch (err) {
      console.warn('Intro rotation failed', err);
      this.hasCompletedTunisiaIntroSpin = true;
      this.showIntroOverlay = false;
      this.introPhase = 'complete';
    }
  } else {
    this.hasCompletedTunisiaIntroSpin = true;
    this.focusTunisiaUpright(1.2);
  }
}

  private initializeMapClickPipeline(): void {
    this.mapClickSubject
      .pipe(
        debounceTime(120),
        takeUntil(this.destroy$)
      )
      .subscribe((event) => {
        if (this.isHandlingMapClick) return;
        this.isHandlingMapClick = true;
        this.handleClick(event)
          .catch((error) => {
            console.warn('Map click handling failed', error);
          })
          .finally(() => {
            this.isHandlingMapClick = false;
          });
      });
  }

  private applyCanvasBloom(): void {
    if (!this.viewer?.scene?.postProcessStages) return;

    try {
      const bloom: any = this.viewer.scene.postProcessStages.bloom;
      if (bloom) {
        bloom.enabled = true;
        bloom.uniforms.glowOnly = false;
        bloom.uniforms.contrast = 128;
        bloom.uniforms.brightness = -0.3;
        bloom.uniforms.delta = 1.0;
        bloom.uniforms.sigma = 3.78;
        bloom.uniforms.stepSize = 1.0;
      }
    } catch (e) {
      console.warn('Bloom stage not available', e);
    }

    try {
      this.viewer.scene.postProcessStages.fxaa.enabled = true;
    } catch {
      // ignore
    }

    this.viewer.scene.requestRender();
  }

  /** Update pointer-events style on the Cesium container based on input. */
  private updatePointerEvents(): void {
    try {
      const el = document.getElementById('cesiumContainer') as HTMLElement | null;
      if (!el) return;
      el.style.pointerEvents = this._allowPointerEvents ? 'auto' : 'none';
    } catch (e) {
      // ignore DOM access failures in non-browser tests
    }
  }

  private applySoftBlueBackdrop(): void {
    if (!this.viewer) return;
    if (this.transparentBackground) return; // preserve globe visibility for transparent embeds
    if (!this.showBlueBackdrop) return; // skip if backdrop disabled
    // Use a solid color backdrop that 'engulfs' the globe. Disable Cesium sky elements
    // so the scene background color is visible consistently.
    try {
      const color = Cesium.Color.fromCssColorString(this.globeBackdropImageUrl);
      this.viewer.scene.backgroundColor = color;
      this.viewer.scene.globe.baseColor = color;
      this.viewer.scene.skyAtmosphere.show = false;
      if (this.viewer.scene.skyBox) {
        this.viewer.scene.skyBox.show = false;
      }
      if ((this.viewer.scene as any).skyBoxCube) {
        (this.viewer.scene as any).skyBoxCube.show = false;
      }
    } catch {
      // ignore if Cesium color conversion fails
    }

    try {
      const container = document.getElementById('cesiumContainer');
      if (!container) return;
      container.style.setProperty('background-image', 'none', 'important');
      container.style.setProperty('background-color', this.globeBackdropImageUrl, 'important');
    } catch {
      // ignore
    }
  }

  private bindSoftBlueBackdropLock(): void {
    if (!this.viewer) return;

    if (this.backdropPreRenderListener) {
      this.viewer.scene.preRender.removeEventListener(this.backdropPreRenderListener);
      this.backdropPreRenderListener = undefined;
    }

    this.backdropPreRenderListener = () => {
      try {
        const color = Cesium.Color.fromCssColorString(this.globeBackdropImageUrl);
        this.viewer.scene.backgroundColor = color;
        this.viewer.scene.globe.baseColor = color;
        this.viewer.scene.skyAtmosphere.show = false;
        if (this.viewer.scene.skyBox) {
          this.viewer.scene.skyBox.show = false;
        }
        if ((this.viewer.scene as any).skyBoxCube) {
          (this.viewer.scene as any).skyBoxCube.show = false;
        }
      } catch {
        // ignore
      }
    };

    this.viewer.scene.preRender.addEventListener(this.backdropPreRenderListener);
  }

  private hideCesiumBranding(): void {
    if (!this.viewer) return;

    try {
      const widget = (this.viewer as any)?._cesiumWidget;
      if (widget?._creditContainer) {
        widget._creditContainer.style.display = 'none';
      }
      if (widget?._creditViewport) {
        widget._creditViewport.style.display = 'none';
      }
    } catch {
      // ignore
    }

    const selectors = [
      '.cesium-credit-container',
      '.cesium-credit-logoContainer',
      '.cesium-credit-expand-link',
      '.cesium-credit-textContainer',
      '.cesium-widget-credits',
      '.cesium-credit-lightbox-overlay',
      '.cesium-credit-lightbox',
    ];

    selectors.forEach((selector) => {
      document.querySelectorAll(selector).forEach((node) => {
        const el = node as HTMLElement;
        el.style.setProperty('display', 'none', 'important');
        el.style.setProperty('visibility', 'hidden', 'important');
        el.style.setProperty('opacity', '0', 'important');
        el.style.setProperty('pointer-events', 'none', 'important');
      });
    });
  }

  private forceRemoveCesiumInfoBox(): void {
    const selectors = [
      '.cesium-viewer-infoBoxContainer',
      '.cesium-infoBox',
      '.cesium-infoBox-visible',
      '.cesium-infoBox-camera',
      '.cesium-infoBox-title',
      '.cesium-infoBox-iframe',
      '.cesium-infoBox-close',
    ];

    selectors.forEach((selector) => {
      document.querySelectorAll(selector).forEach((node) => {
        const el = node as HTMLElement;
        el.style.setProperty('display', 'none', 'important');
        el.style.setProperty('visibility', 'hidden', 'important');
        el.style.setProperty('opacity', '0', 'important');
        el.style.setProperty('pointer-events', 'none', 'important');

        if (el.classList.contains('cesium-viewer-infoBoxContainer') || el.classList.contains('cesium-infoBox')) {
          try { el.remove(); } catch { /* ignore */ }
        }
      });
    });
  }

  private bindInfoBoxRemovalObserver(): void {
    if (this.infoBoxObserver) {
      this.infoBoxObserver.disconnect();
      this.infoBoxObserver = null;
    }

    if (typeof MutationObserver === 'undefined') return;
    const container = document.getElementById('cesiumContainer');
    if (!container) return;

    this.infoBoxObserver = new MutationObserver(() => {
      this.forceRemoveCesiumInfoBox();
    });

    this.infoBoxObserver.observe(container, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ['class'],
    });
  }

  /** Add night imagery to the globe only; do not change the scene background. */
private async applyNightTheme(): Promise<void> {
  if (!this.viewer) return;
  try {
    this.viewer.imageryLayers.removeAll();

    // Earth at Night - Imagerie satellite avec effet NASA Black Marble
    // Globe bleu foncé avec lumières des villes visibles
    const satProvider = new Cesium.UrlTemplateImageryProvider({
      url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
      credit: new Cesium.Credit('Esri'),
      minimumLevel: 0,
      maximumLevel: 19,
    });
    const layer = this.viewer.imageryLayers.addImageryProvider(satProvider);

    // BACKUP VERSION - Filtres qui fonctionnent bien en globe dézoomé:
    // brightness: 0.30, contrast: 1.4, saturation: 0.25, hue: 0.0
    
    // Filtres optimisés pour Earth at Night (bleu foncé, pays voisins visibles)
    layer.alpha = 1.0;
    layer.brightness = 0.30;   // Plus clair pour voir pays voisins et Méditerranée
    layer.contrast = 1.4;      // Contraste pour distinguer les zones
    layer.saturation = 0.25;   // Désaturé pour réduire le doré/sable
    layer.hue = 0.0;           // Pas de teinte - couleurs naturelles sombres

    // Désactiver l'éclairage du globe pour garder des couleurs constantes à tous les niveaux de zoom
    // L'éclairage cause des changements de couleur (marron) en zoomant
    this.viewer.scene.globe.enableLighting = false;
    this.viewer.scene.globe.dynamicAtmosphereLighting = false;
    
    // Optimisations supplémentaires pour les performances
    this.viewer.scene.globe.tileCacheSize = 200; // Augmenter le cache de tiles (défaut: 100)
    this.viewer.scene.globe.preloadSiblings = false; // Ne pas précharger les tiles voisines
    this.viewer.scene.globe.preloadAncestors = false; // Ne pas précharger les tiles parentes

    if (!this.transparentBackground) this.applySoftBlueBackdrop();
    this.viewer.scene.requestRender();
  } catch (err) {
    console.warn('applyNightTheme error', err);
  }
}
  /** Rotate the camera around the globe a given number of full rotations over duration (s).
   *  Uses incremental `camera.rotateRight()` to preserve pitch/roll and avoid tilting.
   */
  private setIntroGlobeView(): void {
    if (!this.viewer) return;

    try {
      // A high-altitude oblique view so the whole globe is visible during spin.
      // Use the previous working framing: pulled back but still large like the screenshot.
      this.viewer.camera.setView({
  destination: Cesium.Cartesian3.fromDegrees(9.0, 15.0, 18500000.0),
        orientation: { heading: 0.0, pitch: -Cesium.Math.PI_OVER_TWO, roll: 0.0 },
      });
      this.viewer.scene.requestRender();
    } catch {
      // ignore
    }
  }

  private rotateGlobe(rotations: number = 1, durationSeconds: number = 4): Promise<void> {
    return new Promise(resolve => {
      if (!this.viewer || rotations === 0 || durationSeconds <= 0) { resolve(); return; }
      const camera = this.viewer.camera;
      const totalAngle = Cesium.Math.TWO_PI * rotations;
      const totalDurationMs = durationSeconds * 1000;
      const startTime = performance.now();
      let lastProgress = 0;

      const tick = (now: number) => {
        const progress = Math.min(1, (now - startTime) / totalDurationMs);
        const deltaProgress = progress - lastProgress;
        lastProgress = progress;

        const deltaAngle = totalAngle * deltaProgress;
        // Rotate horizontally around the globe without changing pitch/roll
        camera.rotateRight(deltaAngle);

        this.viewer.scene.requestRender();

        if (progress >= 1) {
          resolve();
          return;
        }

        window.requestAnimationFrame(tick);
      };

      window.requestAnimationFrame(tick);
    });
  }

  async saveTunisiaDistricts(): Promise<void> {
    if (!this.currentMap || !this.currentMap.id) return;
    try {
      const countryDto: any = { name: 'Tunisia', mapId: this.currentMap.id };
      const countryResp: any = await this.locationsService.createOrGetCountry(countryDto).toPromise();
      const countryId = countryResp?.id;
      if (!countryId) return;
      const districtKeys = Object.keys(this.tunisiaDistrictGeometries);
      const bulkRequests: any[] = [];
      for (const id of districtKeys) {
        const meta = this.tunisiaDistrictsMapping[id];
        const geom = this.tunisiaDistrictGeometries[id];
        if (!geom) continue;
        const districtIndex = Object.keys(this.tunisiaDistrictsMapping).indexOf(id);
        const totalDistricts = Object.keys(this.tunisiaDistrictsMapping).length || 5;
        const hue = Math.round((districtIndex / totalDistricts) * 360);
        const fillHex = this.getDistrictOverrideHex(id) || this.hslToHex(hue, 50, 20);
        bulkRequests.push({
          location: { mapId: this.currentMap.id, name: meta.name_en, category: 'DISTRICT', type: 'ZONE', geometry: { type: geom.type, coordinates: geom.coordinates } },
          contents: [{ content: { name_en: meta.name_en, name_ar: meta.name_ar, name_fr: meta.name_fr || meta.name_en, color: fillHex } }]
        });
      }
      if (bulkRequests.length > 0) {
        try { await this.locationsService.bulkCreateDistricts(countryId, bulkRequests).toPromise(); }
        catch (err) { console.error('Bulk save failed', err); }
      }
    } catch (err) { console.error('Error saving Tunisia districts', err); }
  }

  private tunisiaDistrictsMapping: { [id: string]: { name_ar: string; name_en: string; name_fr: string; members: string[]; members_ar?: string[] } } = {
    district1: { name_ar: 'الإقليم الأول', name_en: 'First District', name_fr: 'Premier District', members: ['Bizerte', 'Beja', 'Jendouba', 'Le Kef'], members_ar: ['بنزرت', 'باجة', 'جندوبة', 'الكاف'] },
    district2: { name_ar: 'الإقليم الثاني', name_en: 'Second District', name_fr: 'Deuxième District', members: ['Tunis', 'Ariana', 'Manubah', 'Ben Arous (Tunis Sud)', 'Nabeul', 'Zaghouan'], members_ar: ['تونس', 'أريانة', 'منوبة', 'بن عروس', 'نابل', 'زغوان'] },
    district3: { name_ar: 'الإقليم الثالث', name_en: 'Third District', name_fr: 'Troisième District', members: ['Sousse', 'Kasserine', 'Siliana', 'Kairouan', 'Monastir', 'Mahdia'], members_ar: ['سوسة', 'القصرين', 'سليانة', 'القيروان', 'المنستير', 'المهدية'] },
    district4: { name_ar: 'الإقليم الرابع', name_en: 'Fourth District', name_fr: 'Quatrième District', members: ['Tozeur', 'Sfax Governorate', 'Gafsa', 'Sidi Bou Zid'], members_ar: ['توزر', 'صفاقس', 'قفصة', 'سيدي بوزيد'] },
    district5: { name_ar: 'الإقليم الخامس', name_en: 'Fifth District', name_fr: 'Cinquième District', members: ['Gabes', 'Medenine', 'Tataouine', 'Kebili'], members_ar: ['قابس', 'مدنين', 'تطاوين', 'قبلي'] },
  };

  private tunisiaDistrictGeometries: { [id: string]: any } = {};

  private hslToHex(h: number, s: number, l: number): string {
    s /= 100; l /= 100;
    const k = (n: number) => (n + h / 30) % 12;
    const a = s * Math.min(l, 1 - l);
    const f = (n: number) => { const color = l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1))); return Math.round(255 * color).toString(16).padStart(2, '0'); };
    return `#${f(0)}${f(8)}${f(4)}`;
  }

  private async displayTunisiaDistricts() {
    try {
      const geojson: any = await this.http.get('assets/tunisia_adm1.geojson').toPromise();
      const features = geojson.features || [];
      const normalize = (s: string) => (s || '').normalize ? s.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase() : (s || '').toLowerCase();
      const districtKeys = Object.keys(this.tunisiaDistrictsMapping);
      const totalDistricts = districtKeys.length || 5;

      for (const id of districtKeys) {
        const meta = this.tunisiaDistrictsMapping[id];
        const matched = features.filter((f: any) => {
          const name = (f.properties && (f.properties.NAME_1 || f.properties.NAME || f.properties.ADMIN)) || '';
          const nname = normalize(name);
          return meta.members.some(m => normalize(m) === nname || nname.includes(normalize(m)) || normalize(m).includes(nname));
        });
        if (!matched.length) continue;
        this.tunisiaDistrictMatchedFeatures[id] = matched;
        let merged: any = null;
        for (const f of matched) {
          const featureCopy = JSON.parse(JSON.stringify(f));
          if (!merged) { merged = featureCopy; }
          else {
            try { merged = turf.union(merged, featureCopy); }
            catch (e) {
              if (merged.geometry.type === 'Polygon') merged.geometry = { type: 'MultiPolygon', coordinates: [merged.geometry.coordinates] };
              if (featureCopy.geometry.type === 'Polygon') featureCopy.geometry = { type: 'MultiPolygon', coordinates: [featureCopy.geometry.coordinates] };
              merged.geometry.coordinates = merged.geometry.coordinates.concat(featureCopy.geometry.coordinates || []);
            }
          }
        }
        if (!merged) continue;
        this.tunisiaDistrictGeometries[id] = merged.geometry;
        // Use pointOnFeature to ensure the pin is inside the polygon (centroid can be outside for irregular shapes)
        const ctr = turf.pointOnFeature(merged);
        const [lon, lat] = ctr.geometry.coordinates;
        const districtIndex = districtKeys.indexOf(id);
        const hue = Math.round((districtIndex / totalDistricts) * 360);
        const fillHex = this.getDistrictOverrideHex(id) || this.hslToHex(hue, 50, 20);
        const fillAlpha = this.defaultMapFillOpacity != null ? this.defaultMapFillOpacity : 0.28;
        const districtFeature = { type: 'FeatureCollection', features: [{ type: 'Feature', properties: { id, name_en: meta.name_en, name_ar: meta.name_ar }, geometry: merged.geometry }] };
        const ds = await Cesium.GeoJsonDataSource.load(districtFeature, { clampToGround: false });
        this.viewer.dataSources.add(ds);
        ds.entities.values.forEach((ent: any) => {
          if (ent.polygon) {
            ent.polygon.material = Cesium.Color.fromCssColorString(fillHex).withAlpha(fillAlpha);
            ent.polygon.outline = true;
            ent.polygon.outlineColor = this.districtBaseOutlineColor;
            ent.polygon.outlineWidth = this.districtBaseOutlineWidth;
          }
          const baseProps = ent.properties?.getValue
            ? ent.properties.getValue(Cesium.JulianDate.now())
            : {};
          ent.properties = new Cesium.PropertyBag({
            ...baseProps,
            districtId: id,
            labelType: 'districtPolygon',
            districtMeta: meta,
            isDistrictPolygon: true,
          });
          this.registerDistrictPolygonEntity(id, ent);
          this.displayedCountryBorders.push(ent);
        });
        this.districtLabelData.set(id, { lon, lat, meta });
        // District pin labels intentionally omitted to keep map clean (no pin icons).
        // If a pin entity is required later for programmatic zoom, the district
        // centroid is used via `tunisiaDistrictGeometries` fallback.
      }

      if (Object.keys(this.tunisiaDistrictGeometries).length > 0) {
        const mapId = this.currentMap?.id;
        if (mapId != null) {
          this.locationsService.getLocationsByMap(mapId).subscribe({
            next: (locations) => {
              const existingDistricts = locations.filter(loc => loc.category === 'DISTRICT');
              if (existingDistricts.length === 0) this.saveTunisiaDistricts();
            },
            error: () => {}
          });
        } else {
          console.warn('displayTunisiaDistricts: currentMap is not set, skipping existence check');
        }
      }

      this.displayTunisiaGovernorateOutlines(features);

      if (this.hasCompletedTunisiaIntroSpin) {
        this.focusTunisiaUpright(2);
      }
      this.applyRightSidebarFilter();
      this.showRightSidebar = false;
      this.showLocationsList = true;
      this.buildUnifiedNavList();
      this.updateDistrictLabels();
      this.setDistrictPinsVisible(this.hasCompletedTunisiaIntroSpin && this.isTunisiaActiveView());
    } catch (err) { console.error('Error rendering Tunisia districts', err); }
  }

  private clearTunisiaGovernorateOutlines(): void {
    this.tunisiaGovernorateOutlineEntities.forEach(entity => {
      try { this.viewer.entities.remove(entity); } catch (e) {}
    });
    this.displayedCountryBorders = this.displayedCountryBorders.filter(
      entity => !this.tunisiaGovernorateOutlineEntities.includes(entity)
    );
    this.tunisiaGovernorateOutlineEntities = [];
  }

  private displayTunisiaGovernorateOutlines(features: any[]): void {
    this.clearTunisiaGovernorateOutlines();
    if (!Array.isArray(features) || features.length === 0) return;

    features.forEach((feature: any) => {
      const props = feature?.properties || {};
      const baseName = props.NAME_1 || props.NAME || props.ADMIN;
      if (!baseName) return;

      const mapping = this.getDistrictAndGovernorateIndexByName(String(baseName));
      const districtMeta = mapping ? this.tunisiaDistrictsMapping[mapping.districtId] : null;
      const memberAr = mapping && districtMeta?.members_ar
        ? districtMeta.members_ar[mapping.index]
        : undefined;
      const nameAr = props.NAME_1_AR || memberAr || baseName;
      const nameFr = props.NAME_1_FR || baseName;
      const commonProps = {
        isGovernorate: true,
        govProps: props,
        districtId: mapping?.districtId || null,
        memberIndex: mapping ? mapping.index : null,
        name_en: baseName,
        name_ar: nameAr,
        name_fr: nameFr,
      };

      let registered = false;

      const addOutline = (positions: number[]): void => {
        if (positions.length < 6) return;
        const outlineEntity = this.viewer.entities.add({
          polygon: {
            hierarchy: Cesium.Cartesian3.fromDegreesArray(positions),
          
            height: 0,
            outline: true,
          outlineColor: Cesium.Color.WHITE.withAlpha(0.55),
 outlineWidth: 1.5,
   fill: true,    
  material: Cesium.Color.TRANSPARENT,  
          },
          properties: commonProps,
        });

        this.tunisiaGovernorateOutlineEntities.push(outlineEntity);
        this.displayedCountryBorders.push(outlineEntity);

        if (!registered) {
          this.governorateEntities.set(String(baseName), outlineEntity);
          this.governorateEntities.set(String(nameAr), outlineEntity);
          this.governorateEntities.set(String(nameFr), outlineEntity);
          registered = true;
        }
      };

      if (feature.geometry?.type === 'Polygon') {
        feature.geometry.coordinates.forEach((ring: any) => {
          const positions: number[] = [];
          ring.forEach((coord: any) => positions.push(coord[0], coord[1]));
          addOutline(positions);
        });
      } else if (feature.geometry?.type === 'MultiPolygon') {
        feature.geometry.coordinates.forEach((polygon: any) => {
          polygon.forEach((ring: any) => {
            const positions: number[] = [];
            ring.forEach((coord: any) => positions.push(coord[0], coord[1]));
            addOutline(positions);
          });
        });
      }
    });
  }

  private clearDisplayedDistricts(): void {
    this.displayedDistrictEntities.forEach(entity => { try { this.viewer.entities.remove(entity); } catch (e) {} });
    this.displayedDistrictEntities = [];
    this.displayedDistrictDataSources.forEach(ds => { try { this.viewer.dataSources.remove(ds); } catch (e) {} });
    this.displayedDistrictDataSources = [];
    this.governorateEntities.clear();
    this.compactDistrictPolygonRegistry();
    // Hide governorate pins and clear projects when no district is selected
    this.setGovernoratePinsVisible(false);
    this.clearProjectsFromMap();
  }

  private async showDistrict(districtId: string, skipFlyToDistrict: boolean = false): Promise<void> {
    this.clearDisplayedDistricts();
    const matched = this.tunisiaDistrictMatchedFeatures[districtId];
    if (!matched || !matched.length) return;
    const meta = this.tunisiaDistrictsMapping[districtId];
    if (!meta) return;
    const normalize = (s: string) => (s || '').normalize ? s.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase() : (s || '').toLowerCase();

    for (const govFeature of matched) {
      try {
        const gCtr = turf.centroid(govFeature);
        const [glon, glat] = gCtr.geometry.coordinates;
        const baseName = (govFeature.properties && (govFeature.properties.NAME_1 || govFeature.properties.NAME || govFeature.properties.ADMIN)) || '';
        const govId = `ADM1_${baseName}`;
        let govNameEn = baseName;
        let govNameAr = govFeature.properties?.NAME_1_AR || baseName;
        const govNameFr = govFeature.properties?.NAME_1_FR || baseName;
        let governorateMemberIndex = -1;
        try {
          const baseNormalized = normalize(baseName);
          const idx = meta.members.findIndex((m: any) => {
            const mNorm = normalize(m);
            return mNorm === baseNormalized || baseNormalized.includes(mNorm) || mNorm.includes(baseNormalized);
          });
          governorateMemberIndex = idx;
          if (idx >= 0 && meta.members_ar && meta.members_ar[idx]) govNameAr = meta.members_ar[idx];
        } catch (e) {}

        if (govFeature.geometry.type === 'Polygon') {
          const positions: number[] = [];
          govFeature.geometry.coordinates.forEach((ring: any) => { ring.forEach((coord: any) => positions.push(coord[0], coord[1])); });
          const outlineEnt = this.viewer.entities.add({ id: govId, polygon: { hierarchy: Cesium.Cartesian3.fromDegreesArray(positions), height: 0, outline: true,outlineColor: Cesium.Color.WHITE.withAlpha(0.55),
  outlineWidth: 1.5,
   fill: true,
  material: Cesium.Color.TRANSPARENT, }, properties: { isGovernorate: true, govProps: govFeature.properties, districtId, memberIndex: governorateMemberIndex, name_en: govNameEn, name_ar: govNameAr, name_fr: govNameFr } });
          this.governorateEntities.set(baseName, outlineEnt);
          outlineEnt.show = false; // Initially hide governorate outlines
          this.displayedDistrictEntities.push(outlineEnt);
        } else if (govFeature.geometry.type === 'MultiPolygon') {
          let firstRingRegistered = false;
          govFeature.geometry.coordinates.forEach((polygon: any) => {
            polygon.forEach((ring: any) => {
              const positions: number[] = [];
              ring.forEach((coord: any) => positions.push(coord[0], coord[1]));
              const entityOptions: any = { polygon: { hierarchy: Cesium.Cartesian3.fromDegreesArray(positions), material: Cesium.Color.TRANSPARENT, height: 0, outline: true, outlineColor: Cesium.Color.fromCssColorString('#FFFFFF').withAlpha(0.65), outlineWidth: 1, fill: false }, properties: { isGovernorate: true, govProps: govFeature.properties, districtId, memberIndex: governorateMemberIndex, name_en: govNameEn, name_ar: govNameAr, name_fr: govNameFr } };
              if (!firstRingRegistered) entityOptions.id = govId;
              const outlineEnt = this.viewer.entities.add(entityOptions);
              if (!firstRingRegistered) { this.governorateEntities.set(baseName, outlineEnt); firstRingRegistered = true; }
              outlineEnt.show = false; // Initially hide governorate outlines
              this.displayedDistrictEntities.push(outlineEnt);
            });
          });
        }

        const labelText = this.currentLanguage === 'ar' ? govNameAr : (this.currentLanguage === 'fr' ? govNameFr : govNameEn);
        const labelId = `ADM1_label_${govNameEn}`;
        const govPinImage = this.getCachedDiamondPin(this.governoratePinColor, this.diamondPinSourceSize);

        // Create a governorate pin (billboard) but omit the textual label to reduce map clutter.
        const govLabel = this.viewer.entities.add({
          id: labelId,
          position: Cesium.Cartesian3.fromDegrees(glon, glat),
          billboard: { image: govPinImage, verticalOrigin: Cesium.VerticalOrigin.CENTER, horizontalOrigin: Cesium.HorizontalOrigin.CENTER, disableDepthTestDistance: Number.POSITIVE_INFINITY, scale: this.governoratePinScale, scaleByDistance: this.createDiamondScaleByDistance(), width: this.diamondPinBillboardBaseSize, height: this.diamondPinBillboardBaseSize },
          // Intentionally do NOT add a `label` property here — governorate name labels are removed per user request.
          properties: { isGovernorate: true, name_en: govNameEn, name_ar: govNameAr, name_fr: govNameFr, districtId, memberIndex: governorateMemberIndex }
        });

        this.governorateEntities.set(govNameEn, govLabel);
        this.governorateEntities.set(baseName, govLabel);
        if (govNameFr) this.governorateEntities.set(govNameFr, govLabel);
        if (govNameAr) this.governorateEntities.set(govNameAr, govLabel);
        if (governorateMemberIndex >= 0) {
          const memberNameEn = meta.members?.[governorateMemberIndex];
          const memberNameAr = meta.members_ar?.[governorateMemberIndex];
          if (memberNameEn) this.governorateEntities.set(memberNameEn, govLabel);
          if (memberNameAr) this.governorateEntities.set(memberNameAr, govLabel);
        }
        // Initially hide governorate pins - they will be shown after district selection
        govLabel.show = false;
        this.displayedDistrictEntities.push(govLabel);
      } catch (e) { console.warn('Failed to render governorate', e); }
    }

    // Render merged district polygon
    let merged: any = null;
    for (const f of matched) {
      const featureCopy = JSON.parse(JSON.stringify(f));
      if (!merged) merged = featureCopy;
      else {
        try { merged = turf.union(merged, featureCopy); }
        catch (e) {
          if (merged.geometry.type === 'Polygon') merged.geometry = { type: 'MultiPolygon', coordinates: [merged.geometry.coordinates] };
          if (featureCopy.geometry.type === 'Polygon') featureCopy.geometry = { type: 'MultiPolygon', coordinates: [featureCopy.geometry.coordinates] };
          merged.geometry.coordinates = merged.geometry.coordinates.concat(featureCopy.geometry.coordinates || []);
        }
      }
    }
    if (merged) {
      const districtFeature = { type: 'FeatureCollection', features: [{ type: 'Feature', properties: { id: districtId, name_en: meta.name_en, name_ar: meta.name_ar }, geometry: merged.geometry }] };
      try {
        const ds = await Cesium.GeoJsonDataSource.load(districtFeature, { clampToGround: false });
        this.viewer.dataSources.add(ds);
        this.displayedDistrictDataSources.push(ds);
        const districtIndex = Object.keys(this.tunisiaDistrictsMapping).indexOf(districtId);
        const totalDistricts = Object.keys(this.tunisiaDistrictsMapping).length || 5;
        const hue = Math.round((districtIndex / totalDistricts) * 360);
        const fillHex = this.getDistrictOverrideHex(districtId) || this.hslToHex(hue, 50, 20);
        const fillAlpha = this.defaultMapFillOpacity != null ? this.defaultMapFillOpacity : 0.28;
        ds.entities.values.forEach((ent: any) => {
          if (ent.polygon) {
            ent.polygon.material = Cesium.Color.fromCssColorString(fillHex).withAlpha(fillAlpha);
            ent.polygon.outline = true;
            ent.polygon.outlineColor = this.districtBaseOutlineColor;
            ent.polygon.outlineWidth = this.districtBaseOutlineWidth;
          }
          const baseProps = ent.properties?.getValue
            ? ent.properties.getValue(Cesium.JulianDate.now())
            : {};
          ent.properties = new Cesium.PropertyBag({
            ...baseProps,
            districtId,
            labelType: 'districtPolygon',
            districtMeta: meta,
            isDistrictPolygon: true,
          });
          this.registerDistrictPolygonEntity(districtId, ent);
        });
      } catch (e) { console.error('Failed to render merged district polygon', e); }
    }

    // skipFlyToDistrict — ALWAYS true when called from a click handler
    // Only skip=false when called from backToDistrictHierarchy (no prior zoom)
    if (!skipFlyToDistrict && this.displayedDistrictEntities.length > 0) {
      try {
        const districtPin = this.districtLabelEntities.find(entity => {
          const now = Cesium.JulianDate.now(); const props = entity.properties; if (!props) return false;
          const entityDistrictId = props['districtId']?.getValue ? props['districtId'].getValue(now) : props['districtId'];
          return entityDistrictId === districtId;
        });
        if (districtPin) {
          this.viewer.flyTo(districtPin, {
            duration: this.ZOOM_DURATION,
            offset: new Cesium.HeadingPitchRange(this.ZOOM_HEADING, this.ZOOM_PITCH, this.DISTRICT_OVERVIEW_ZOOM_HEIGHT)
          });
        }
        else if (this.displayedDistrictEntities.length > 0) { this.viewer.flyTo(this.displayedDistrictEntities, { duration: 1.5 }); }
      } catch (e) { console.warn('Failed to fly to district:', e); }
    }
    this.refreshDistrictPinsWithProjectIndicators();
    // Show governorate pins for the selected district, hide others
    this.setGovernoratePinsVisible(true);
  }

  private updateDistrictLabels() {
    this.districtLabelEntities.forEach(entity => { try { this.viewer.entities.remove(entity); } catch (err) {} });
    this.districtLabelEntities = [];
    this.districtLabelData.forEach((data, id) => {
      const { lon, lat, meta } = data;
      const districtIndex = Object.keys(this.tunisiaDistrictsMapping).indexOf(id);
      const totalDistricts = Object.keys(this.tunisiaDistrictsMapping).length || 5;
      const hue = Math.round((districtIndex / totalDistricts) * 360);
      const fillColor = this.getDistrictOverrideHex(id) || this.hslToHex(hue, 70, 50);
      const districtPin = this.viewer.entities.add({
        id: `district-pin-${id}`,
        position: Cesium.Cartesian3.fromDegrees(lon, lat),
        billboard: {
          image: this.getCachedDiamondPin(this.districtPinColor, this.diamondPinSourceSize),
          verticalOrigin: Cesium.VerticalOrigin.CENTER,
          horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
          disableDepthTestDistance: Number.POSITIVE_INFINITY,
          scale: this.governoratePinScale,
          scaleByDistance: this.createDiamondScaleByDistance(),
          width: this.diamondPinBillboardBaseSize,
          height: this.diamondPinBillboardBaseSize,
        },
        properties: {
          districtId: id,
          labelType: 'district',
          districtMeta: meta,
          isDistrictPin: true,
          name_en: meta?.name_en,
          name_ar: meta?.name_ar,
          name_fr: meta?.name_fr,
          fillColor,
        },
      });
      this.districtLabelEntities.push(districtPin);
      this.districtEntities.set(id, districtPin);
    });
    this.setDistrictPinsVisible(this.hasCompletedTunisiaIntroSpin && this.isTunisiaActiveView());
  }

  toggleZonePoints(zoneId: number) {
    if (this.zoneChildEntities.has(zoneId)) {
      const ents = this.zoneChildEntities.get(zoneId) || [];
      ents.forEach(e => { try { this.viewer.entities.remove(e); } catch (err) {} });
      this.zoneChildEntities.delete(zoneId);
      return;
    }
    const children = this.zoneChildMap.get(zoneId) || [];
    if (!children || children.length === 0) return;
    const created: Cesium.Entity[] = [];
    const pinImage = this.getCachedDiamondPin(this.projectDotColor, this.diamondPinSourceSize);
    children.forEach((loc: any) => {
      let [lon, lat] = loc.geometry.coordinates; lon = Number(lon); lat = Number(lat);
      const ent = this.viewer.entities.add({ name: loc.name, 
        position: Cesium.Cartesian3.fromDegrees(lon, lat), 
        billboard: { image: pinImage, 
          verticalOrigin: Cesium.VerticalOrigin.CENTER, 
          horizontalOrigin: Cesium.HorizontalOrigin.CENTER, 
          disableDepthTestDistance: Number.POSITIVE_INFINITY, 
          scale: this.projectPinScale,
          scaleByDistance: this.createDiamondScaleByDistance(), 
          distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0.0, 5.0e6), 
          width: this.diamondPinBillboardBaseSize, height: this.diamondPinBillboardBaseSize }, 
          label: { 
   
           
      
             pixelOffset: new Cesium.Cartesian2(0, 34), 
             verticalOrigin: Cesium.VerticalOrigin.TOP,   disableDepthTestDistance: Number.POSITIVE_INFINITY }, properties: { contents: loc.contents || [], location: loc, parentZoneId: zoneId } });
      created.push(ent);
    });
    this.zoneChildEntities.set(zoneId, created);
  }

  enableClick() {
    if (this.mapEventHandler) {
      this.mapEventHandler.destroy();
      this.mapEventHandler = null;
    }

    this.mapEventHandler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);
    this.mapEventHandler.setInputAction((event: any) => {
      this.mapClickSubject.next(event);
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

    // When pointer is pressed on a pin, show a closed-hand cursor. Do NOT
    // trigger for districts or generic map clicks — the hand should appear
    // only when interacting with a billboard (pin).
    this.mapEventHandler.setInputAction((event: any) => {
      try {
        const picked = this.viewer.scene.pick(event.position);
        if (!picked || !picked.id) return;
        const now = Cesium.JulianDate.now();
        const props: any = picked.id.properties;
        const getProp = (key: string) => { if (!props) return undefined; const v = props[key]; return v?.getValue ? v.getValue(now) : v; };
        const isBillboard = !!(picked && picked.id && (picked.id as any).billboard);
        const isGovernorate = !!getProp('isGovernorate');
        const locationObj = getProp('location');
        const locationIsProject = !!(getProp('type') === 'project' || (locationObj && String(locationObj.category).toUpperCase() === 'PROJECT'));
        // Only show grabbing hand while pressing governorate or project billboards
        if (isBillboard && (isGovernorate || locationIsProject)) {
          this.setCanvasCursor('pointer');
          // remember grabbed project entity so we can keep showing grabbing while over it
          if (locationIsProject) this.grabbedProjectEntityId = picked.id;
        }
      } catch {}
    }, Cesium.ScreenSpaceEventType.LEFT_DOWN);

    // On release, always clear the cursor (hand only during active press).
this.mapEventHandler.setInputAction((event: any) => {
      try {
        // On release: revert to grab if still over a governorate or project diamond pin.
        // Districts and everything else: default cursor (no pointer/grab).
        const picked = this.viewer.scene.pick(event.position);
        let stillOverDiamondPin = false;
        if (picked && picked.id && (picked.id as any).billboard && picked.id.properties) {
          const now = Cesium.JulianDate.now();
          const props: any = picked.id.properties;
          const getProp = (key: string) => { const v = props[key]; return v?.getValue ? v.getValue(now) : v; };
          const isGovernorate = !!getProp('isGovernorate');
          const locationObj = getProp('location');
          const locationIsProject = !!(getProp('type') === 'project' || (locationObj && String(locationObj.category).toUpperCase() === 'PROJECT'));
          // Only the two diamond pin types keep grab cursor after release
          if (isGovernorate || locationIsProject) stillOverDiamondPin = true;
        }
        if (stillOverDiamondPin) {
          this.setCanvasCursor('grab');
        } else {
          this.setCanvasCursor('');
          this.grabbedProjectEntityId = null;
        }
      } catch { this.setCanvasCursor(''); }
    }, Cesium.ScreenSpaceEventType.LEFT_UP);

    this.mapEventHandler.setInputAction((movement: any) => {
      this.handleDistrictHover(movement);
    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
  }

  displayCountryBorders(countryName: string = '', skipFlyTo: boolean = false) {
    this.displayedCountryBorders.forEach(e => this.viewer.entities.remove(e));
    this.displayedCountryBorders = [];
    this.countryLabelEntities.forEach(e => this.viewer.entities.remove(e));
    this.countryLabelEntities = [];
    this.hoveredDistrictId = null;
    this.districtHoverPopupVisible = false;
    this.districtHoverPopupText = '';
    this.districtPolygonEntities.clear();
    this.setDistrictPinsVisible(false);
    this.tunisiaGovernorateOutlineEntities = [];
    this.governorateEntities.clear();

    const targetCountry = (countryName || this.defaultMapCountry || this.selectedCountry || this.focusCountryName).trim();
    const normalizedTarget = this.normalizeSelectionName(targetCountry);

    let features = this.countryBorders.filter((feature: any) => {
      const props = feature?.properties || {};
      const gid0 = String(props.GID_0 || '').toUpperCase();
      const featureCountry = this.normalizeSelectionName(String(props.COUNTRY || props.ADMIN || props.name || ''));
      if (normalizedTarget === 'tunisia') return gid0 === 'TUN' || featureCountry === 'tunisia';
      return featureCountry === normalizedTarget;
    });

    if (normalizedTarget === 'tunisia') { this.displayTunisiaDistricts(); return; }

    features.forEach(feature => {
      const gid0 = feature.properties?.GID_0;
      const countryLabel = feature.properties?.COUNTRY || feature.properties?.name || feature.properties?.ADMIN || 'Unknown';
      let centroid: { lon: number; lat: number } | null = null;
      if (feature.geometry.type === 'Polygon') {
        feature.geometry.coordinates.forEach((ring: any) => {
          const positions: number[] = [];
          ring.forEach((coord: any) => positions.push(coord[0], coord[1]));
          if (!centroid && ring.length > 0) centroid = this.calculateCentroid(ring);
          const entity = this.viewer.entities.add({
            polygon: {
              hierarchy: Cesium.Cartesian3.fromDegreesArray(positions),
              material: Cesium.Color.fromCssColorString('#00FFFF').withAlpha(0.001),
              height: 0,
              outline: true,
              outlineColor: Cesium.Color.fromCssColorString('#00FFFF'),
              outlineWidth: 3,
              fill: true,
            },
            properties: {
              type: 'countryBorder',
              countryName: countryLabel,
              gid0,
            },
          });
          this.displayedCountryBorders.push(entity);
        });
      } else if (feature.geometry.type === 'MultiPolygon') {
        let largestPolygon: any = null; let largestArea = 0;
        feature.geometry.coordinates.forEach((polygon: any) => {
          polygon.forEach((ring: any) => {
            const positions: number[] = [];
            ring.forEach((coord: any) => positions.push(coord[0], coord[1]));
            if (ring.length > 0) { const lons = ring.map((c: any) => c[0]); const lats = ring.map((c: any) => c[1]); const area = (Math.max(...lons) - Math.min(...lons)) * (Math.max(...lats) - Math.min(...lats)); if (area > largestArea) { largestArea = area; largestPolygon = ring; } }
            const entity = this.viewer.entities.add({
              polygon: {
                hierarchy: Cesium.Cartesian3.fromDegreesArray(positions),
                material: Cesium.Color.fromCssColorString('#00FFFF').withAlpha(0.001),
                height: 0,
                outline: true,
                outlineColor: Cesium.Color.fromCssColorString('#00FFFF'),
                outlineWidth: 3,
                fill: true,
              },
              properties: {
                type: 'countryBorder',
                countryName: countryLabel,
                gid0,
              },
            });
            this.displayedCountryBorders.push(entity);
          });
        });
        if (largestPolygon) centroid = this.calculateCentroid(largestPolygon);
      }
      if (centroid && countryLabel) {
        const labelEntity = this.viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(centroid.lon, centroid.lat), properties: { gid0, countryName: countryLabel, isCountryLabel: true } });
        this.countryLabelEntities.push(labelEntity);
      }
    });
    if (!skipFlyTo && this.displayedCountryBorders.length > 0) {
      this.viewer.flyTo(this.displayedCountryBorders, {
        duration: 2,
        offset: new Cesium.HeadingPitchRange(this.ZOOM_HEADING, this.ZOOM_PITCH, this.DISTRICT_OVERVIEW_ZOOM_HEIGHT)
      });
    }
  }

  private calculateCentroid(ring: any[]): { lon: number; lat: number } {
    let lonSum = 0, latSum = 0, count = 0;
    ring.forEach((coord: any) => { if (coord && coord.length >= 2) { lonSum += coord[0]; latSum += coord[1]; count++; } });
    return { lon: count > 0 ? lonSum / count : 0, lat: count > 0 ? latSum / count : 0 };
  }

  updateCountryLabels(): void {
    if (!this.viewer || this.countryLabelEntities.length === 0) return;
    this.countryLabelEntities.forEach(labelEntity => {
      const props = (labelEntity.properties && typeof labelEntity.properties.getValue === 'function') ? labelEntity.properties.getValue(Cesium.JulianDate.now()) : labelEntity.properties || {};
      if (props) {
        if (props.gid0 && labelEntity.label) { labelEntity.label.text = new Cesium.ConstantProperty(props.countryName || 'Unknown'); }
        if (props.districtId && labelEntity.label) {
          const meta = this.tunisiaDistrictsMapping[props.districtId];
          if (meta) { const labelLang = props.labelLang || 'ar'; labelEntity.label.text = new Cesium.ConstantProperty(labelLang === 'ar' ? '\u202B' + meta.name_ar + '\u202C' : meta.name_en); }
        }
      }
    });
    try {
      this.displayedCountryBorders.forEach(ent => {
        const props = (ent.properties && typeof ent.properties.getValue === 'function') ? ent.properties.getValue(Cesium.JulianDate.now()) : ent.properties || {};
        if (props && props.isGovernorate) {
          const nameEn = props.name_en || (props.govProps && (props.govProps.NAME_1 || props.govProps.NAME)) || '';
          const nameAr = props.name_ar || (props.govProps && props.govProps.NAME_1_AR) || nameEn;
          const nameFr = props.name_fr || (props.govProps && props.govProps.NAME_1_FR) || nameEn;
          const newLabel = this.currentLanguage === 'ar' ? nameAr : (this.currentLanguage === 'fr' ? nameFr : nameEn);
          if (ent.label) {
            if (this.currentLanguage === 'ar') { ent.label.text = new Cesium.ConstantProperty('\\u202B' + newLabel + '\\u202C'); ent.label.font = new Cesium.ConstantProperty('bold 13px "Amiri", "Noto Naskh Arabic", sans-serif'); }
            else { ent.label.text = new Cesium.ConstantProperty(newLabel); ent.label.font = new Cesium.ConstantProperty('bold 13px sans-serif'); }
          }
        }
      });
    } catch (e) {}
  }

  changeLanguage(language: SupportedLanguage): void { this.i18nService.setLanguage(language); }

  loadDefaultMap() {
    this.mapService.getDefaultMap().subscribe({
      next: (response: any) => {
        if (response) {
          const resolvedCountry = response.countryName || this.defaultMapCountry || this.selectedCountry || this.focusCountryName;
          this.selectedCountry = resolvedCountry;
          this.defaultMapCountry = resolvedCountry;
          if (response.borderColor) this.defaultMapBorderColor = response.borderColor;
          if (response.fillOpacity != null) this.defaultMapFillOpacity = response.fillOpacity;
          if (response.borderWidth != null) this.defaultMapBorderWidth = response.borderWidth;
          this.displayCountryBorders(resolvedCountry, true);
          if (response.camera) {
            try {
              const cam = typeof response.camera === 'string' ? JSON.parse(response.camera) : response.camera;
              if (cam && cam.longitude != null && cam.latitude != null) this.defaultCameraPosition = cam;
            } catch (e) {}
          }
          if (this.displayedCountryBorders.length > 0) {
            const isTunisiaDefault = this.normalizeSelectionName(resolvedCountry) === 'tunisia';
            if (isTunisiaDefault) {
              if (this.hasCompletedTunisiaIntroSpin) {
                this.focusTunisiaUpright(1.2);
              }
            } else {
              this.viewer.flyTo(this.displayedCountryBorders, {
                duration: 1.2,
                offset: new Cesium.HeadingPitchRange(this.ZOOM_HEADING, this.ZOOM_PITCH, this.DISTRICT_OVERVIEW_ZOOM_HEIGHT)
              });
            }
          }
          this.isDefaultMap = true;
          if (this.currentMap && this.currentMap.id) {
            this.locationsService.getCountryLocations(this.currentMap.id).subscribe((countries: any) => {
              const found = countries.find((c: any) => c.name === this.selectedCountry || (c.geometry && c.geometry.properties && c.geometry.properties.name === this.selectedCountry));
              if (found) {
                this.countryLocationId = found.id;
                this.locationsService.getCountryWithLocations(found.id).subscribe((countryData: any) => {
                  if (countryData && countryData.children) this.allLocations = countryData.children;
                  this.loadLocations();
                }, () => this.loadLocations());
              } else this.loadLocations();
            }, () => this.loadLocations());
          } else this.loadLocations();
        }
      },
      error: () => {}
    });
  }

  // ════════════════════════════════════════════════════════════════════════════
  // CLICK HANDLER — uses unified zoom throughout
  // ════════════════════════════════════════════════════════════════════════════

   async handleClick(event: any) {
    let cartesian = this.viewer.scene.pickPosition(event.position);
    if (!cartesian) {
      const pickRay = this.viewer.camera.getPickRay(event.position);
      if (pickRay) cartesian = this.viewer.scene.globe.pick(pickRay, this.viewer.scene);
    }

    let lon: number | null = null;
    let lat: number | null = null;
    if (cartesian) {
      const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
      lon = Cesium.Math.toDegrees(cartographic.longitude);
      lat = Cesium.Math.toDegrees(cartographic.latitude);
    }

    if (this.drawingPoint) {
      if (lon == null || lat == null) return;
      this.createPoint(lon, lat);
      return;
    }
    if (this.drawingPolygon) {
      if (lon == null || lat == null) return;
      this.polygonPoints.push([lon, lat]);
      this.drawTempPolygon();
      return;
    }

    const picked = this.viewer.scene.pick(event.position);
    if (!Cesium.defined(picked) || !picked.id) {
      this.isMapFocused = false;
      this.resizeViewerAsync();
      this.clearHierarchyState();
      this.selectedName = null; this.selectedContents = [];
      this.selectedLocation = null; this.selectedProject = null;
      this.activeSidebarItemKey = null;
      this.selectedItem = null;
      this.isAnimating = false;
      this.showLocationsList = true;
      this.showRightSidebar = true;
      this.applyRightSidebarFilter();
      this.cdr.detectChanges();
      return;
    }

    const now = Cesium.JulianDate.now();
    const props = picked.id.properties;
    const getProp = (key: string) => { if (!props) return undefined; const val = props[key]; return val?.getValue ? val.getValue(now) : val; };
    const entityType = getProp('type');

    this.onMapClick();

    if (entityType === 'countryPin' || entityType === 'countryBorder' || !!getProp('isCountryLabel')) {
      const clickedCountryName = String(
        getProp('countryName') ||
        picked.id?.name ||
        this.selectedCountry ||
        this.defaultMapCountry ||
        this.focusCountryName ||
        ''
      ).trim();

      await this.animateSidebarForMapSelection(
        { type: 'country', name: clickedCountryName },
        async () => {
          this.clearHierarchyState();
          this.selectedDistrict = null;
          this.selectedProject = null;
          this.selectedLocation = null;
          this.selectedContents = [];
          this.selectedName = clickedCountryName || null;
          this.activeSidebarItemKey = null;
          this.showLocationsList = true;
          this.showRightSidebar = true;
          this.applyRightSidebarFilter();
        }
      );
      return;
    }

    // ── PROJECT PIN ──────────────────────────────────────────────────────────
    if (entityType === 'project') {
      const projectData: LocationDTO = getProp('projectData');
      if (projectData) {
        this.zoomToEntity(picked.id);
        await this.animateSidebarForMapSelection(
          { type: 'project', data: projectData },
          async () => {
            this.activeSidebarItemKey = null;
            this.syncNavIndexToEntity('project', projectData.id!);
            this.setSelectedProjectFromMapClick(projectData);

            const opened = await this.openProjectHierarchyFromProject(projectData);
            if (!opened) {
              this.clearHierarchyState();
              this.showLocationsList = false;
              this.showRightSidebar = true;
            }
          }
        );
      }
      return;
    }

    // ── DISTRICT PIN ─────────────────────────────────────────────────────────
    const districtIdRaw: string = getProp('districtId') || getProp('id');
    const districtId = districtIdRaw ? String(districtIdRaw) : '';
    const labelType: string = getProp('labelType');
    const isDistrictPolygon = !!getProp('isDistrictPolygon');
    if (districtId && (labelType === 'district' || labelType === 'districtPolygon' || isDistrictPolygon)) {
      this.activeSidebarItemKey = this.getDistrictSelectionKey(String(districtId));
      this.viewer.selectedEntity = picked.id;
      this.syncNavIndexToEntity('district', districtId);

      // ← CORRECT POSITION: districtId is declared above
      this.selectDistrictNeonGlow(districtId);

      // Unified zoom — fires immediately, showDistrict does NOT steal camera
      this.zoomToDistrict(districtId);
      this.showDistrict(districtId, true /* skipFlyToDistrict */).catch(() => {});

      await this.animateSidebarForMapSelection(
        { type: 'district', id: districtId },
        async () => {
          const opened = await this.openDistrictHierarchy(districtId, { targetView: 'district' });
          if (opened) {
            const district = this.getHierarchyDistrict();
            // Do not show all district projects - only show projects when governorate is selected
            if (district) this.clearProjectsFromMap();
          } else {
            this.selectedDistrict = { id: districtId, meta: this.tunisiaDistrictsMapping[districtId] };
            this.selectedProject = null;
            this.selectedLocation = {
              id: 0,
              name: this.tunisiaDistrictsMapping[districtId]?.name_en || districtId,
              category: 'DISTRICT',
              type: LocationType.ZONE,
              mapId: this.currentMap?.id || 0,
              geometry: this.tunisiaDistrictGeometries[districtId] || null,
              contents: []
            } as any;
            this.selectedName = this.tunisiaDistrictsMapping[districtId]?.name_en || districtId;
            this.selectedContents = [];
            this.showLocationsList = false;
            this.showRightSidebar = true;

            const scoped = this.getStrictDistrictProjects(districtId);
            this.setSidebarProjects(scoped, `district_fallback_${districtId}`);
          }
        }
      );
      return;
    }

    // ── GOVERNORATE PIN ──────────────────────────────────────────────────────
    const isGovernorate: boolean = getProp('isGovernorate');
    if (isGovernorate) {
      const govProps      = getProp('govProps');
      const nameEn: string = getProp('name_en');
      const selectedGovName = nameEn || (govProps && (govProps.NAME_1 || govProps.NAME || govProps.ADMIN)) || '';

      if (selectedGovName) {
        this.activeSidebarItemKey = this.getGovernorateSelectionKey(selectedGovName);
        this.viewer.selectedEntity = picked.id;
        const pickedDistrictId: string | undefined = getProp('districtId');
        const pickedMemberIndexRaw: any = getProp('memberIndex');
        const pickedMemberIndex = Number(pickedMemberIndexRaw);

        const navIdx = this.unifiedNavList.findIndex(i => i.type === 'governorate' && i.data.name === selectedGovName);
        if (navIdx >= 0) this.unifiedNavIndex = navIdx;

        // Unified zoom — fires immediately, does NOT wait for hierarchy
        this.zoomToGovernorate(
          selectedGovName,
          pickedDistrictId,
          isNaN(pickedMemberIndex) ? undefined : pickedMemberIndex
        );

        await this.animateSidebarForMapSelection(
          { type: 'governorate', name: selectedGovName },
          async () => {
            const opened = await this.openGovernorateHierarchyByName(
              selectedGovName,
              pickedDistrictId,
              isNaN(pickedMemberIndex) ? undefined : pickedMemberIndex
            );

            if (!opened) {
              this.showLocationsList = false;
              this.showRightSidebar  = true;
              this.selectedDistrict  = null;
              this.selectedName      = selectedGovName;
              this.selectedLocation  = {
                id: 0, name: selectedGovName, category: 'GOVERNORATE',
                type: LocationType.ZONE, mapId: this.currentMap?.id || 0, geometry: null, contents: [],
              } as any;
              this.selectedContents = [];
            }
          }
        );
      }
      return;
    }

    // ── ZONE / POINT / OTHER LOCATION ────────────────────────────────────────
    const location: LocationDTO = getProp('location');

    if (props && location) {
      if (location.id) this.syncNavIndexToEntity('location', location.id);

      const selectedEntity = picked.id;
      if (selectedEntity && location.geometry?.type) {
        try {
          this.viewer.flyTo(selectedEntity, { duration: this.ZOOM_DURATION, offset: new Cesium.HeadingPitchRange(0, this.ZOOM_PITCH, this.ZOOM_HEIGHT) })
            .then(() => { this.viewer.trackedEntity = selectedEntity; })
            .catch((e: any) => { console.warn('flyTo failed:', e); });
        } catch (e) {
          if (location.geometry.type === 'Point') {
            const [eLon, eLat] = location.geometry.coordinates;
            this.zoomToCoordinates(eLon, eLat);
          }
        }
      }

      await this.animateSidebarForMapSelection(
        { type: 'location', data: location },
        async () => {
          this.clearHierarchyState();
          let sidebarSelectionKey: string | null = null;
          if (location?.category === 'GOVERNORATE' && location?.name) sidebarSelectionKey = this.getGovernorateSelectionKey(location.name);
          else if (location?.category === 'DISTRICT' && location?.name) {
            const matchedDistrict = this.allDistricts.find((d: any) =>
              this.normalizeSelectionName(d.name_en) === this.normalizeSelectionName(location.name) ||
              this.normalizeSelectionName(d.name_fr) === this.normalizeSelectionName(location.name) ||
              this.normalizeSelectionName(d.name_ar) === this.normalizeSelectionName(location.name)
            );
            if (matchedDistrict?.id) sidebarSelectionKey = this.getDistrictSelectionKey(String(matchedDistrict.id));
          }

          this.activeSidebarItemKey = sidebarSelectionKey;
          const rawContents = props['contents'];
          const contents: LocationContentDTO[] = rawContents?.getValue ? rawContents.getValue(now) : rawContents;
          this.selectedName = picked.id?.name || location?.name || 'Location';
          this.selectedContents = contents || [];
          this.selectedLocation = location || null;
          this.selectedDistrict = null;
          this.selectedProject = null;
          this.showLocationsList = false;
          this.showRightSidebar = true;

          const locationType = location.geometry?.type;
          if (locationType === 'Polygon') {
            this.setFilter('ZONES');
            if (location.id != null) this.toggleZonePoints(Number(location.id));
          } else if (locationType === 'Point') {
            this.setFilter('POINTS');
          }
          this.currentLocationIndex = this.filteredLocations.findIndex(loc => loc.id === location.id);
        }
      );
      return;
    }

    const fallbackName = picked.id?.name || 'Map Item';
    await this.animateSidebarForMapSelection(
      { type: 'entity', name: fallbackName },
      async () => {
        this.clearHierarchyState();
        this.selectedDistrict = null;
        this.selectedProject = null;
        this.selectedName = fallbackName;
        this.selectedContents = [];
        this.selectedLocation = {
          id: 0,
          name: fallbackName,
          category: 'POINT',
          type: LocationType.POINT,
          mapId: this.currentMap?.id || 0,
          geometry: null,
          contents: []
        } as any;
        this.activeSidebarItemKey = null;
        this.showLocationsList = false;
        this.showRightSidebar = true;
      }
    );
  }


  clearSelection(preserveHiddenSidebar: boolean = false) {
    this.highlightSelectedPin(null); 
    this.sidebarAnimationToken++;
    if (!preserveHiddenSidebar) {
      this.sidebarForceHidden = false;
    }
    this.clearHierarchyState();
    this.clearZoneProjects();
    this.sidebarProjects = [];
    this.selectedItem = null;
    this.isAnimating = false;
    this.districtHoverPopupVisible = false;
    this.districtHoverPopupText = '';
    this.selectedName = null; this.selectedContents = []; this.selectedLocation = null;
    this.selectedDistrict = null; this.activeSidebarItemKey = null;
    if (this.currentZoneId !== null && this.zoneChildEntities.has(this.currentZoneId)) this.toggleZonePoints(this.currentZoneId);
    this.currentZoneChildPoints = []; this.currentZoneId = null;
    this.showLocationsList = true; this.showRightSidebar = false;
     this.isMapFocused = false;
     this.resizeViewerAsync();
       this.clearDistrictNeonGlow();
    this.viewer.selectedEntity = undefined;
    try { this.viewer.trackedEntity = undefined; } catch (e) {}
    this.hideSelectionFrame();
  }

  startPointDrawing() {
    this.drawingPoint = true; this.drawingPolygon = false; this.selectedName = null; this.selectedContents = []; this.viewer.selectedEntity = undefined;
    Swal.fire({ title: 'Point Mode Active', text: 'Click anywhere on the map to create a point', icon: 'info', toast: true, position: 'top-end', showConfirmButton: false, timer: 3000, background: '#1f2937', color: '#ffffff', iconColor: '#10b981' });
  }

  async createPoint(lon: number, lat: number) {
    if (!this.isPointInsideCountry(lon, lat)) {
      Swal.fire({ title: 'Invalid Location', text: 'You must draw the point inside the zone boundaries!', icon: 'warning', confirmButtonColor: '#f59e0b', background: '#1f2937', color: '#ffffff' });
      this.drawingPoint = false; return;
    }
    const { value: name } = await Swal.fire({ title: 'Enter Point Name', input: 'text', inputPlaceholder: 'Enter a name for this point', showCancelButton: true, confirmButtonText: 'Create Point', cancelButtonText: 'Cancel', confirmButtonColor: '#10b981', cancelButtonColor: '#6b7280', background: '#1f2937', color: '#ffffff', inputValidator: (value) => { if (!value) return 'Please enter a name!'; return null; } });
    if (!name) { this.drawingPoint = false; return; }
    if (!this.currentMap || !this.currentMap.id) { Swal.fire({ title: 'Error', text: 'No map selected.', icon: 'error', background: '#1f2937', color: '#ffffff' }); return; }
    const locationDTO: LocationDTO = { id: 0, name, category: 'USER_POINT', type: LocationType.POINT, mapId: this.currentMap.id, parentId: this.isDefaultMap ? this.countryLocationId : undefined, geometry: { type: 'Point', coordinates: [lon, lat] } };
    this.dialog.open(PolygonContentDialogComponent, { width: '600px', maxHeight: '80vh', panelClass: 'dark-dialog', backdropClass: 'dialog-backdrop', disableClose: false, data: { name, contents: [], isCreating: true } }).afterClosed().subscribe(result => {
      if (result !== null && result !== undefined) {
        const request: LocationRequest = { location: locationDTO, contents: result || [] };
        this.locationsService.createLocation(request).subscribe(saved => {
          Swal.fire({ title: 'Success!', text: `Point "${name}" saved successfully`, icon: 'success', confirmButtonColor: '#10b981', background: '#1f2937', color: '#ffffff' });
          try { const created: any = saved; if (created) { this.allLocations.push(created); this.renderLocationEntities(this.allLocations); this.selectLocationFromList(created); } else this.loadLocations(); } catch (e) { this.loadLocations(); }
        }, err => { Swal.fire({ title: 'Error!', text: `Failed to save point: ${err.error?.message || err.message || 'Unknown error'}`, icon: 'error', confirmButtonColor: '#ef4444', background: '#1f2937', color: '#ffffff' }); });
      }
      this.drawingPoint = false;
    });
  }

  startPolygonDrawing() {
    this.drawingPolygon = true; this.drawingPoint = false; this.polygonPoints = []; this.selectedName = null; this.selectedContents = []; this.viewer.selectedEntity = undefined;
    this.tempPolygonEntity && this.viewer.entities.remove(this.tempPolygonEntity);
    this.tempPointEntities.forEach(e => this.viewer.entities.remove(e)); this.tempPointEntities = [];
    Swal.fire({ title: 'Polygon Mode Active', text: 'Click at least 3 points. Then click "Finish Zone"', icon: 'info', toast: true, position: 'top-end', showConfirmButton: false, timer: 3000, background: '#1f2937', color: '#ffffff' });
  }

  drawTempPolygon() {
    if (this.tempPolygonEntity) this.viewer.entities.remove(this.tempPolygonEntity);
    this.tempPointEntities.forEach(e => this.viewer.entities.remove(e)); this.tempPointEntities = [];
    this.polygonPoints.forEach((p, i) => {
      const pointEntity = this.viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(p[0], p[1]), point: { pixelSize: 8, color: Cesium.Color.RED }, label: { text: (i + 1).toString(), font: '14px sans-serif', fillColor: Cesium.Color.WHITE } });
      this.tempPointEntities.push(pointEntity);
    });
    if (this.polygonPoints.length < 2) return;
    const flatCoords = this.polygonPoints.flat();
    this.tempPolygonEntity = this.viewer.entities.add({ polygon: { hierarchy: Cesium.Cartesian3.fromDegreesArray(flatCoords), material: Cesium.Color.fromCssColorString('#00FFFF').withAlpha(0.25), height: 0, outline: true, outlineColor: Cesium.Color.fromCssColorString('#00FFFF'), outlineWidth: 2 }, polyline: { positions: Cesium.Cartesian3.fromDegreesArray(flatCoords), material: Cesium.Color.fromCssColorString('#00FFFF').withAlpha(0.25), width: 2 } });
  }

  async finishPolygon() {
    if (this.polygonPoints.length < 3) { Swal.fire({ title: 'Not enough points', text: 'Polygon needs at least 3 points', icon: 'warning', confirmButtonColor: '#4CAF50' }); return; }
    const { value: polygonName } = await Swal.fire({ title: 'Enter Zone Name', input: 'text', inputPlaceholder: 'Enter a name for this zone', showCancelButton: true, confirmButtonText: 'Create Zone', cancelButtonText: 'Cancel', confirmButtonColor: '#10b981', cancelButtonColor: '#6b7280', background: '#1f2937', color: '#ffffff', inputValidator: (value) => { if (!value) return 'Please enter a name!'; return null; } });
    if (!polygonName) return;
    const first = this.polygonPoints[0]; const last = this.polygonPoints[this.polygonPoints.length - 1];
    const coordsCopy = [...this.polygonPoints];
    if (Math.abs(first[0] - last[0]) > 1e-6 || Math.abs(first[1] - last[1]) > 1e-6) coordsCopy.push([...first]);
    if (!this.currentMap || !this.currentMap.id) { Swal.fire({ title: 'Error', text: 'No map selected.', icon: 'error', background: '#1f2937', color: '#ffffff' }); return; }
    const locationDTO: LocationDTO = { id: 0, name: polygonName, category: 'USER_ZONE', type: LocationType.ZONE, mapId: this.currentMap.id, parentId: this.isDefaultMap ? this.countryLocationId : undefined, geometry: { type: 'Polygon', coordinates: [coordsCopy] } };
    this.dialog.open(PolygonContentDialogComponent, { width: '600px', maxHeight: '80vh', panelClass: 'dark-dialog', backdropClass: 'dialog-backdrop', disableClose: false, data: { name: polygonName, contents: [], isCreating: true } }).afterClosed().subscribe(result => {
      if (result !== null && result !== undefined) {
        const request: LocationRequest = { location: locationDTO, contents: result || [] };
        this.locationsService.createLocation(request).subscribe(saved => {
          Swal.fire({ title: 'Success!', text: `Zone "${polygonName}" saved successfully`, icon: 'success', confirmButtonColor: '#10b981', background: '#1f2937', color: '#ffffff' });
          this.drawingPolygon = false; this.polygonPoints = [];
          if (this.tempPolygonEntity) this.viewer.entities.remove(this.tempPolygonEntity); this.tempPolygonEntity = null;
          this.tempPointEntities.forEach(e => this.viewer.entities.remove(e)); this.tempPointEntities = [];
          try { const created: any = saved; if (created) { this.allLocations.push(created); this.renderLocationEntities(this.allLocations); this.selectLocationFromList(created); } else this.loadLocations(); } catch (e) { this.loadLocations(); }
        }, err => { Swal.fire({ title: 'Error!', text: `Failed to save zone: ${err.error?.message || err.message || 'Unknown error'}`, icon: 'error', confirmButtonColor: '#ef4444', background: '#1f2937', color: '#ffffff' }); });
      } else { this.drawingPolygon = true; }
    });
  }

  loadLocations() {
    if (!this.currentMap) return;
    if (this.isDefaultMap && this.defaultMapCountry) {
      this.mapService.getActiveMap().subscribe(activeMap => {
        if (activeMap && activeMap.id) {
          this.locationsService.getCountryLocations(activeMap.id).subscribe((countries: any) => {
            const foundCountry = countries.find((c: any) => c.name === this.defaultMapCountry || (c.geometry && c.geometry.properties && c.geometry.properties.name === this.defaultMapCountry));
            if (foundCountry && foundCountry.id) {
              this.locationsService.getLocationsByParent(foundCountry.id).subscribe((locations: LocationDTO[]) => {
                this.hydrateLocationsForSidebar(locations);
              }, err => console.error('Failed to fetch country child locations:', err));
            }
          }, err => console.error('Failed to fetch country locations:', err));
        }
      }, err => console.error('Failed to get active map:', err));
    } else {
      if (this.currentMap && this.currentMap.id) {
        this.locationsService.getLocationsByMap(this.currentMap.id).subscribe((locations: any) => {
          this.hydrateLocationsForSidebar(locations as LocationDTO[]);
        }, err => console.error('Failed to fetch map locations:', err));
      }
    }
  }

private hydrateLocationsForSidebar(locations: LocationDTO[]): void {
  const safeLocations = Array.isArray(locations) ? locations : [];

  // Skip fetch for IDs already in cache
  const idsNeedingHydration = safeLocations
    .filter(loc => {
      if (loc?.id == null) return false;
      if (Array.isArray(loc.contents) && loc.contents.length > 0) return false;
      const cached = this._locationDetailsCache.get(Number(loc.id));
      return !(cached && this.isCacheValid(cached.ts));  // ← NEW: skip if cached
    })
    .map(loc => Number(loc.id));

  if (idsNeedingHydration.length === 0) {
    // Merge cached data into locations before rendering
    this.allLocations = safeLocations.map(loc => {
      if (loc?.id == null) return loc;
      const cached = this._locationDetailsCache.get(Number(loc.id));
      return (cached && this.isCacheValid(cached.ts)) ? cached.data : loc;
    });
    this.renderLocationEntities(this.allLocations);
    return;
  }

  // Only fetch what's missing
  const detailCalls = idsNeedingHydration.map(id =>
    this.locationsService.getLocationDetails(id).pipe(
      tap(d => this._locationDetailsCache.set(id, { data: d, ts: Date.now() })),
      catchError(() => of(null))
    )
  );

  forkJoin(detailCalls).subscribe({
    next: (details) => {
      const detailById = new Map<number, LocationDTO>();
      details.forEach(d => { if (d?.id != null) detailById.set(Number(d.id), d); });

      this.allLocations = safeLocations.map(loc => {
        if (loc?.id == null) return loc;
        const cached = this._locationDetailsCache.get(Number(loc.id));
        return detailById.get(Number(loc.id))
          ?? (cached && this.isCacheValid(cached.ts) ? cached.data : loc);
      });
      this.renderLocationEntities(this.allLocations);
    },
    error: () => {
      this.allLocations = safeLocations;
      this.renderLocationEntities(safeLocations);
    }
  });
}

  renderLocationEntities(locations: any[]) {
    this.allLocations = locations;
    this.applyFilter();
    this.zoneChildMap.clear();
    this.zoneChildEntities.forEach((ents) => ents.forEach(e => this.viewer.entities.remove(e)));
    this.zoneChildEntities.clear();
    this.displayedLocationEntities.forEach(e => this.viewer.entities.remove(e));
    this.displayedLocationEntities = [];
    this.viewer.entities.values.forEach((entity: any) => {
      if (!this.displayedCountryBorders.includes(entity) && !this.districtLabelEntities.includes(entity) && !this.displayedDistrictEntities.includes(entity)) {
        this.viewer.entities.remove(entity);
      }
    });
    const zones  = locations.filter((l: any) => l.geometry?.type === 'Polygon');
    const points = locations.filter((l: any) => l.geometry?.type === 'Point');
    const zonePolygons: { id: number; flatCoords: number[]; ring: any[] }[] = [];

    zones.forEach((loc: any) => {
      if (!loc.geometry?.coordinates || !Array.isArray(loc.geometry.coordinates)) return;
      let ring = loc.geometry.coordinates[0];
      if (!Array.isArray(ring)) return;
      if (ring.length === 1 && Array.isArray(ring[0]) && Array.isArray(ring[0][0])) ring = ring[0];
      if (!Array.isArray(ring) || ring.length < 3) return;
      const flatCoords: number[] = []; const positions: Cesium.Cartesian3[] = [];
      let sumLon = 0, sumLat = 0; let hasInvalidCoord = false;
      for (let i = 0; i < ring.length; i++) {
        const coord = ring[i]; let cLon: number, cLat: number;
        if (Array.isArray(coord) && coord.length >= 2) { cLon = Number(coord[0]); cLat = Number(coord[1]); }
        else if (coord && typeof coord === 'object') { cLon = Number(coord.lon ?? coord.lng ?? coord.longitude); cLat = Number(coord.lat ?? coord.latitude); }
        else { hasInvalidCoord = true; break; }
        if (isNaN(cLon) || isNaN(cLat)) { hasInvalidCoord = true; break; }
        flatCoords.push(cLon, cLat); positions.push(Cesium.Cartesian3.fromDegrees(cLon, cLat)); sumLon += cLon; sumLat += cLat;
      }
      if (hasInvalidCoord || flatCoords.length < 6) return;
      const cx = sumLon / ring.length; const cy = sumLat / ring.length;
      const polyEnt = this.viewer.entities.add({ name: loc.name, position: positions[0], polygon: { hierarchy: Cesium.Cartesian3.fromDegreesArray(flatCoords), material: Cesium.Color.fromCssColorString('#00FFFF').withAlpha(0.25), height: 0, outline: true, outlineColor: Cesium.Color.fromCssColorString('#B026FF'), outlineWidth: 3, perPositionHeight: false, fill: true }, properties: { contents: loc.contents || [], location: loc, isZone: true } });
      this.displayedLocationEntities.push(polyEnt);
      const zonePinImage = this.getCachedDiamondPin(this.governoratePinColor, this.diamondPinSourceSize);
      const zonePinEntity = this.viewer.entities.add({ name: loc.name + '_pin', position: Cesium.Cartesian3.fromDegrees(cx, cy), billboard: { image: zonePinImage, verticalOrigin: Cesium.VerticalOrigin.CENTER, horizontalOrigin: Cesium.HorizontalOrigin.CENTER, disableDepthTestDistance: Number.POSITIVE_INFINITY, scale: this.zonePinScale, scaleByDistance: this.createDiamondScaleByDistance(), distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0.0, 5.0e6), width: this.diamondPinBillboardBaseSize, height: this.diamondPinBillboardBaseSize }, properties: { contents: loc.contents || [], location: loc, isZonePin: true, zoneId: loc.id } });
      this.displayedLocationEntities.push(zonePinEntity);
      zonePolygons.push({ id: loc.id, flatCoords, ring });
    });

    points.forEach((loc: any) => {
      let [pLon, pLat] = loc.geometry.coordinates; pLon = Number(pLon); pLat = Number(pLat);
      if (isNaN(pLon) || isNaN(pLat)) return;
      let foundZoneId: number | null = null;
      for (const zp of zonePolygons) { if (this.pointInPolygon(pLon, pLat, zp.ring)) { foundZoneId = zp.id; break; } }
      if (foundZoneId != null) { const arr = this.zoneChildMap.get(foundZoneId) || []; arr.push(loc); this.zoneChildMap.set(foundZoneId, arr); }
      else {
        const pinImage = this.getCachedDiamondPin(this.projectDotColor, this.diamondPinSourceSize);
        const entity = this.viewer.entities.add({ name: loc.name, position: Cesium.Cartesian3.fromDegrees(pLon, pLat), billboard: { image: pinImage, verticalOrigin: Cesium.VerticalOrigin.CENTER, horizontalOrigin: Cesium.HorizontalOrigin.CENTER, disableDepthTestDistance: Number.POSITIVE_INFINITY, scale: this.projectPinScale, scaleByDistance: this.createDiamondScaleByDistance(), distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0.0, 5.0e6), width: this.diamondPinBillboardBaseSize, height: this.diamondPinBillboardBaseSize }, properties: { contents: loc.contents || [], location: loc } });
        this.displayedLocationEntities.push(entity);
      }
    });
    this.buildUnifiedNavList();
  }

  focusByParam(focus: string) {
    if (!focus || !this.allLocations) return;
    const byId   = this.allLocations.find(l => String(l.id) === String(focus));
    const byName = this.allLocations.find(l => String(l.name).toLowerCase() === String(focus).toLowerCase());
    const target = byId || byName;
    if (target) this.selectLocationFromList(target);
  }

  editLocation() {
    if (!this.selectedLocation) return;
    if (this.selectedLocation.category === 'GOVERNORATE') { this.editGovernorate(); return; }

    const openEditDialog = (locationForEdit: LocationDTO) => {
      this.dialog.open(PolygonContentDialogComponent, {
        width: '600px',
        maxHeight: '80vh',
        panelClass: 'dark-dialog',
        backdropClass: 'dialog-backdrop',
        disableClose: false,
        data: {
          name: locationForEdit.name,
          contents: locationForEdit.contents || this.selectedContents || [],
          isCreating: false
        }
      }).afterClosed().subscribe(result => {
        if (result !== null && result !== undefined) {
          const normalizedContents = Array.isArray(result)
            ? result
            : ((result as any)?.contents || []);
          const request: LocationRequest = { location: locationForEdit, contents: normalizedContents };
          this.locationsService.updateLocation(locationForEdit.id!, request).subscribe(() => {
            Swal.fire({ title: 'Success!', text: `Updated successfully`, icon: 'success', confirmButtonColor: '#10b981' });
            this.clearSelection();
            this.loadLocations();
          }, err => {
            Swal.fire({ title: 'Error!', text: 'Failed to update location', icon: 'error', confirmButtonColor: '#f44336' });
          });
        }
      });
    };

    if (this.selectedLocation.id && this.selectedLocation.id > 0) {
      this.locationsService.getLocationDetails(this.selectedLocation.id).subscribe({
        next: (fullLocation: LocationDTO) => {
          this.selectedLocation = { ...(this.selectedLocation as any), ...(fullLocation as any) };
          this.selectedContents = fullLocation.contents || [];
          openEditDialog(this.selectedLocation);
        },
        error: () => openEditDialog(this.selectedLocation as LocationDTO)
      });
      return;
    }

    openEditDialog(this.selectedLocation);
  }

  deleteLocation() {
    if (!this.selectedLocation) return;
    Swal.fire({ title: 'Are you sure?', text: `Delete "${this.selectedLocation.name}"?`, icon: 'warning', showCancelButton: true, confirmButtonColor: '#f44336', cancelButtonColor: '#6b7280', confirmButtonText: 'Yes, delete it!' }).then((result) => {
      if (result.isConfirmed) {
        this.locationsService.deleteLocation(this.selectedLocation!.id!).subscribe(() => { this.clearSelection(); this.loadLocations(); }, err => { Swal.fire({ title: 'Error!', text: 'Failed to delete location', icon: 'error', confirmButtonColor: '#f44336' }); });
      }
    });
  }

  getFirstImage(): string | null {
    if (!this.selectedContents || this.selectedContents.length === 0) return null;
    const imageContent = this.selectedContents.find(c => c.content['type'] === 'IMAGE');
    return imageContent ? imageContent.content['value'] : null;
  }

  hasImage(): boolean { return this.getFirstImage() !== null; }

  applyFilter() {
    if (this.selectedFilter === 'ALL') this.filteredLocations = this.allLocations;
    else if (this.selectedFilter === 'ZONES') this.filteredLocations = this.allLocations.filter(loc => loc.geometry?.type === 'Polygon');
    else if (this.selectedFilter === 'POINTS') this.filteredLocations = this.allLocations.filter(loc => loc.geometry?.type === 'Point');
    this.currentLocationIndex = 0;
    this.cdr.detectChanges();
  }

  setFilter(filter: string) { this.selectedFilter = filter; this.applyFilter(); }

  selectLocationFromList(location: LocationDTO) {
    this.clearHierarchyState();
    this.showRightSidebar = true; this.showLocationsList = false; this.activeSidebarItemKey = null;
    this.selectedLocation = location; this.selectedName = location.name; this.selectedContents = location.contents || [];
    const locationType = location.geometry?.type;
    if (locationType === 'Polygon') { this.setFilter('ZONES'); this.loadZoneChildPoints(location.id!); }
    else if (locationType === 'Point') {
      this.setFilter('POINTS');
      if (this.currentZoneId !== null && this.zoneChildEntities.has(this.currentZoneId)) this.toggleZonePoints(this.currentZoneId);
      this.currentZoneChildPoints = []; this.currentZoneId = null;
    }
    this.currentLocationIndex = this.filteredLocations.findIndex(loc => loc.id === location.id);
    this.syncNavIndexToEntity('location', location.id!);
    this.cdr.detectChanges();
    const entity = this.displayedLocationEntities.find(e => e.name === location.name);
    if (entity) {
      const rawContents = entity.properties?.['contents'];
      const now = Cesium.JulianDate.now();
      const entityContents = rawContents?.getValue ? rawContents.getValue(now) : rawContents || [];
      if (entityContents.length > 0 && entityContents.length !== this.selectedContents.length) { this.selectedContents = entityContents; this.cdr.detectChanges(); }
      this.viewer.selectedEntity = entity;
      try {
        const targetEntity = location.geometry?.type === 'Polygon'
          ? (this.displayedLocationEntities.find(e => e.name === (location.name + '_pin')) || entity)
          : entity;
        // Unified zoom
        this.viewer.flyTo(targetEntity, {
          duration: this.ZOOM_DURATION,
          offset: new Cesium.HeadingPitchRange(0, this.ZOOM_PITCH, this.ZOOM_HEIGHT)
        }).then(() => { this.viewer.trackedEntity = targetEntity; }).catch(() => {});
      } catch (e) {}
    }
  }

  backToList() {
    this.highlightSelectedPin(null); 
    this.sidebarAnimationToken++;
    this.sidebarForceHidden = false;
    this.clearHierarchyState();
    this.sidebarProjects = [];
    this.selectedItem = null;
    this.isAnimating = false;
    this.districtHoverPopupVisible = false;
    this.districtHoverPopupText = '';
    // Keep the sidebar visible when returning to the list so tabs/search remain usable
    this.showRightSidebar = true;
    this.showLocationsList = true; this.selectedName = null; this.selectedContents = [];
    this.selectedLocation = null; this.activeSidebarItemKey = null;
    if (this.currentZoneId !== null && this.zoneChildEntities.has(this.currentZoneId)) this.toggleZonePoints(this.currentZoneId);
    this.currentZoneChildPoints = []; this.currentZoneId = null;
        this.clearDistrictNeonGlow();
    this.viewer.selectedEntity = undefined;
    this.cdr.detectChanges();
  }
getLocationImage(location: LocationDTO): string | null {
  if (!location?.contents || location.contents.length === 0) return null;
  for (const c of (location.contents as any[])) {
    // Handle both { content: { type, value } } and flat { type, value } shapes
    const inner = c?.content || c || {};
    const type = String(inner['type'] || inner['contentType'] || '').toUpperCase();
    if (type === 'IMAGE') {
      const val = inner['value'] || inner['url'] || inner['src'] || inner['data'] || null;
      if (val && String(val).trim()) return String(val).trim();
    }
  }
  return null;
}

  getDistrictImage(districtId: string): string | null {
    if (!this.currentMap?.id) return null;
    const districtMeta = this.tunisiaDistrictsMapping[districtId];
    if (!districtMeta) return null;

    const districtMatches = this.allLocations
      .filter(loc => loc.category === 'DISTRICT' && (loc.name === districtMeta.name_en || loc.name === districtMeta.name_ar || loc.name === districtMeta.name_fr))
      .sort((a, b) => {
        const score = (loc: LocationDTO) => ((Array.isArray(loc.contents) && loc.contents.length > 0) ? 2 : 0) + (loc.id ? 1 : 0);
        const byScore = score(b) - score(a);
        if (byScore !== 0) return byScore;
        return Number(b.id || 0) - Number(a.id || 0);
      });

    const districtLocation = districtMatches[0];
    if (districtLocation && districtLocation.contents) return this.getLocationImage(districtLocation);
    return null;
  }

  getGovernorateImage(govName: string): string | null {
    if (!this.currentMap?.id) return null;
    const normalize = (s: string) => s.toLowerCase().replace(/\s+/g, '').normalize('NFD').replace(/\p{Diacritic}/gu, '');
    const targetNorm = normalize(govName);
    const govMatches = this.allLocations.filter(loc => {
      if (loc.category !== 'GOVERNORATE') return false;
      const locNorm = normalize(loc.name);
      return locNorm === targetNorm || locNorm.includes(targetNorm) || targetNorm.includes(locNorm);
    });

    govMatches.sort((a, b) => {
      const score = (loc: LocationDTO) => ((Array.isArray(loc.contents) && loc.contents.length > 0) ? 2 : 0) + (loc.id ? 1 : 0);
      const byScore = score(b) - score(a);
      if (byScore !== 0) return byScore;
      return Number(b.id || 0) - Number(a.id || 0);
    });

    const govLocation = govMatches[0];
    if (govLocation && govLocation.contents) return this.getLocationImage(govLocation);
    return null;
  }

  loadZoneChildPoints(zoneId: number) {
    if (this.currentZoneId !== null && this.currentZoneId !== zoneId && this.zoneChildEntities.has(this.currentZoneId)) this.toggleZonePoints(this.currentZoneId);
    this.currentZoneId = zoneId;
    const cachedChildren = this.zoneChildMap.get(zoneId);
    if (cachedChildren) {
      this.currentZoneChildPoints = cachedChildren.filter(child => child.geometry?.type === 'Point');
      this.cdr.detectChanges();
      if (!this.zoneChildEntities.has(zoneId) && this.currentZoneChildPoints.length > 0) this.toggleZonePoints(zoneId);
      return;
    }
    this.locationsService.getLocationsByParent(zoneId).subscribe({
      next: (children: LocationDTO[]) => {
        this.zoneChildMap.set(zoneId, children);
        this.currentZoneChildPoints = children.filter(child => child.geometry?.type === 'Point');
        this.cdr.detectChanges();
        if (this.currentZoneChildPoints.length > 0) this.toggleZonePoints(zoneId);
      },
      error: () => { this.currentZoneChildPoints = []; }
    });
  }

  selectChildPoint(childPoint: LocationDTO) {
    this.selectedLocation = childPoint; this.selectedName = childPoint.name;
    let entity = this.displayedLocationEntities.find(e => e.name === childPoint.name);
    if (!entity) { for (const [, childEntities] of this.zoneChildEntities.entries()) { const found = childEntities.find(e => e.name === childPoint.name); if (found) { entity = found; break; } } }
    if (entity) {
      const rawContents = entity.properties?.['contents'];
      const now = Cesium.JulianDate.now();
      this.selectedContents = rawContents?.getValue ? rawContents.getValue(now) : rawContents || [];
      this.zoomToEntity(entity);
    }
  }

  flyToHome() {
    const activeCountry = this.selectedCountry || this.defaultMapCountry || this.focusCountryName;
    if (this.normalizeSelectionName(activeCountry) === 'tunisia') {
      this.focusTunisiaUpright(2);
      return;
    }
    if (this.defaultCameraPosition) {
      this.viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(this.defaultCameraPosition.longitude, this.defaultCameraPosition.latitude, this.defaultCameraPosition.height || 20000000), orientation: { heading: this.defaultCameraPosition.heading || 0, pitch: this.defaultCameraPosition.pitch || Cesium.Math.toRadians(-45), roll: this.defaultCameraPosition.roll || 0 }, duration: 2 });
    } else if (this.displayedCountryBorders.length > 0) {
      this.viewer.flyTo(this.displayedCountryBorders, {
        duration: 2,
        offset: new Cesium.HeadingPitchRange(this.ZOOM_HEADING, this.ZOOM_PITCH, this.DISTRICT_OVERVIEW_ZOOM_HEIGHT)
      });
    }
  }

  zoomIn() {
    if (!this.viewer) return;
    const camera = this.viewer.camera;
    const currentHeight = Math.max(
      camera.positionCartographic?.height ?? this.ZOOM_HEIGHT,
      this.MIN_BUTTON_ZOOM_HEIGHT
    );
    const targetHeight = Math.max(
      this.MIN_BUTTON_ZOOM_HEIGHT,
      currentHeight * this.BUTTON_ZOOM_IN_FACTOR
    );
    camera.zoomIn(currentHeight - targetHeight);
  }

  zoomOut() {
    if (!this.viewer) return;
    const camera = this.viewer.camera;
    const currentHeight = Math.max(
      camera.positionCartographic?.height ?? this.ZOOM_HEIGHT,
      this.MIN_BUTTON_ZOOM_HEIGHT
    );
    const targetHeight = Math.min(
      this.MAX_BUTTON_ZOOM_HEIGHT,
      currentHeight * this.BUTTON_ZOOM_OUT_FACTOR
    );
    camera.zoomOut(targetHeight - currentHeight);
  }

  clearAllCountryData() {
    if (!this.countryLocationId) { Swal.fire({ title: 'Error!', text: 'No country selected', icon: 'error', background: '#1f2937', color: '#ffffff' }); return; }
    Swal.fire({ title: 'Are you sure?', text: `This will delete all data for ${this.selectedCountry || 'this country'}.`, icon: 'warning', showCancelButton: true, confirmButtonColor: '#ef4444', cancelButtonColor: '#6b7280', confirmButtonText: 'Yes, clear all!', background: '#1f2937', color: '#ffffff' }).then((result) => {
      if (result.isConfirmed) {
        this.locationsService.clearAllCountryData(this.countryLocationId!).subscribe(() => { Swal.fire({ title: 'Cleared!', text: 'All data has been deleted.', icon: 'success', confirmButtonColor: '#10b981', background: '#1f2937', color: '#ffffff' }); this.clearSelection(); this.loadLocations(); }, err => { Swal.fire({ title: 'Error!', text: 'Failed to clear country data', icon: 'error', background: '#1f2937', color: '#ffffff' }); });
      }
    });
  }

  addStarField() {
    const starColor = Cesium.Color.fromCssColorString('#00FFFF').withAlpha(0.8);
    const pointCollection = this.viewer.scene.primitives.add(new Cesium.PointPrimitiveCollection());
    for (let i = 0; i < 200; i++) {
      pointCollection.add({ position: Cesium.Cartesian3.fromDegrees((Math.random() - 0.5) * 360, (Math.random() - 0.5) * 180, 15000000 + Math.random() * 5000000), pixelSize: Math.random() * 4 + 2, color: Math.random() > 0.7 ? Cesium.Color.WHITE : starColor, outlineColor: Cesium.Color.WHITE, outlineWidth: 1 });
    }
  }

  addGlobeRotation() { let rotationSpeed = 0.0001; this.viewer.clock.onTick.addEventListener(() => { if (this.viewer && this.viewer.camera) { this.viewer.camera.setView({ orientation: { heading: this.viewer.camera.heading + rotationSpeed, pitch: this.viewer.camera.pitch, roll: this.viewer.camera.roll } }); } }); }
  addEnhancedPulse(lon: number, lat: number, color: Cesium.Color, speed: number = 1.0, maxRadius: number = 300000) { const start = Cesium.JulianDate.now(); return this.viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(lon, lat), ellipse: { semiMajorAxis: new Cesium.CallbackProperty((time: any) => { const seconds = Cesium.JulianDate.secondsDifference(time, start); const cycle = (seconds * speed * 0.5) % 3.0; return 50000 + (cycle * maxRadius / 3.0); }, false), semiMinorAxis: new Cesium.CallbackProperty((time: any) => { const seconds = Cesium.JulianDate.secondsDifference(time, start); const cycle = (seconds * speed * 0.5) % 3.0; return 50000 + (cycle * maxRadius / 3.0); }, false), material: new Cesium.ColorMaterialProperty(new Cesium.CallbackProperty((time: any) => { const seconds = Cesium.JulianDate.secondsDifference(time, start); const cycle = (seconds * speed * 0.5) % 3.0; return color.withAlpha(Math.max(0.1, 0.6 - (cycle / 3.0) * 0.5)); }, false)), height: 100000, outline: true, outlineColor: new Cesium.CallbackProperty(() => color.withAlpha(0.8), false), outlineWidth: 2 } }); }
  addGlowingParticles() { return; }
  addDynamicLighting() { const start = Cesium.JulianDate.now(); this.viewer.scene.preRender.addEventListener(() => { const seconds = Cesium.JulianDate.secondsDifference(Cesium.JulianDate.now(), start); this.viewer.scene.globe.atmosphereLightIntensity = 25.0 + Math.sin(seconds * 0.5) * 5.0; if (this.viewer.scene.skyAtmosphere) this.viewer.scene.skyAtmosphere.hueShift = Math.sin(seconds * 0.2) * 0.05; }); }
  addPulse(lon: number, lat: number) { return this.addEnhancedPulse(lon, lat, Cesium.Color.CYAN, 1.0, 300000); }

  isPointInsideCountry(lon: number, lat: number): boolean {
    if (!this.currentMap || this.countryBorders.length === 0) return true;
    const countryName = this.currentMap.countryName || this.selectedCountry;
    if (!countryName) return true;
    let features = this.countryBorders.filter(f => f.properties && (f.properties.ADMIN === countryName || f.properties.name === countryName));
    if (features.length === 0 && this.normalizeSelectionName(countryName) === 'tunisia') {
      features = this.countryBorders.filter(f => String(f?.properties?.GID_0 || '').toUpperCase() === 'TUN');
    }
    if (features.length === 0) return true;
    for (const feature of features) {
      if (feature.geometry.type === 'Polygon') { for (const ring of feature.geometry.coordinates) { if (this.pointInPolygon(lon, lat, ring)) return true; } }
      else if (feature.geometry.type === 'MultiPolygon') { for (const polygon of feature.geometry.coordinates) { for (const ring of polygon) { if (this.pointInPolygon(lon, lat, ring)) return true; } } }
    }
    return false;
  }

  pointInPolygon(lon: number, lat: number, ring: number[][]): boolean {
    let inside = false;
    for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
      const xi = ring[i][0], yi = ring[i][1], xj = ring[j][0], yj = ring[j][1];
      const intersect = ((yi > lat) !== (yj > lat)) && (lon < (xj - xi) * (lat - yi) / (yj - yi) + xi);
      if (intersect) inside = !inside;
    }
    return inside;
  }

  startPulseEffect(entity: Cesium.Entity) { if (!entity.point) return; entity.point.color = new Cesium.CallbackProperty(() => Cesium.Color.fromCssColorString('#39FF14').withAlpha(0.5 + Math.abs(Math.sin(Date.now() * 0.003)) * 0.5), false); }

  getDistrictName(): string {
    if (!this.selectedDistrict) return '';
    const meta = this.selectedDistrict.meta;
    if (this.currentLanguage === 'ar') return meta.name_ar;
    if (this.currentLanguage === 'fr') return meta.name_fr;
    return meta.name_en;
  }

  getGovernorateDisplayName(districtId: string, index: number): string {
    const meta = this.tunisiaDistrictsMapping[districtId];
    if (!meta) return '';
    if (this.currentLanguage === 'ar' && meta.members_ar && meta.members_ar[index]) return meta.members_ar[index];
    return meta.members[index];
  }

  setRightSidebarTab(tab: string): void {
    this.selectedRightSidebarTab = tab;
    // Clear contextual projects when viewing pure Districts or Governorates lists
    if (tab === 'DISTRICTS' || tab === 'GOVERNORATES') {
      this.sidebarProjects = [];
      this.sidebarProjectsBase = [];
    }
    this.applyRightSidebarFilter();
    this.applySidebarSearchFilters();
    this.cdr.detectChanges();
  }

  applyRightSidebarFilter(): void {
    this.allDistricts = Object.keys(this.tunisiaDistrictsMapping).map(id => ({ id, ...this.tunisiaDistrictsMapping[id] }));
    this.allGovernorates = [];
    Object.keys(this.tunisiaDistrictsMapping).forEach(districtId => {
      const meta = this.tunisiaDistrictsMapping[districtId];
      meta.members.forEach((memberName: string, idx: number) => {
        this.allGovernorates.push({ districtId, index: idx, name: memberName, name_ar: meta.members_ar ? meta.members_ar[idx] : memberName, displayName: this.getGovernorateDisplayName(districtId, idx) });
      });
    });
    if (this.selectedRightSidebarTab === 'DISTRICTS') { this.filteredDistricts = this.allDistricts; this.filteredGovernorates = []; this.filteredSectors = []; }
    else if (this.selectedRightSidebarTab === 'GOVERNORATES') { this.filteredDistricts = []; this.filteredGovernorates = this.allGovernorates; this.filteredSectors = []; }
    else if (this.selectedRightSidebarTab === 'SECTORS') { this.filteredDistricts = []; this.filteredGovernorates = []; this.filteredSectors = this.allSectors; }
    else { this.filteredDistricts = this.allDistricts; this.filteredGovernorates = this.allGovernorates; this.filteredSectors = this.allSectors; }
    this.cdr.detectChanges();
  }

  private matchesSidebarQuery(value: string | null | undefined): boolean {
    if (!this.sidebarSearchTerm) return true;
    return String(value || '').toLowerCase().includes(this.sidebarSearchTerm);
  }

  private applySidebarSearchFilters(): void {
    if (!this.sidebarSearchTerm) {
      this.sidebarProjects = [...this.sidebarProjectsBase];
      return;
    }

    if (this.selectedRightSidebarTab === 'DISTRICTS' || this.selectedRightSidebarTab === 'ALL') {
      this.filteredDistricts = this.filteredDistricts.filter((d: any) =>
        this.matchesSidebarQuery(d?.name_en) ||
        this.matchesSidebarQuery(d?.name_fr) ||
        this.matchesSidebarQuery(d?.name_ar)
      );
    }

    if (this.selectedRightSidebarTab === 'GOVERNORATES' || this.selectedRightSidebarTab === 'ALL') {
      this.filteredGovernorates = this.filteredGovernorates.filter((g: any) =>
        this.matchesSidebarQuery(g?.displayName) ||
        this.matchesSidebarQuery(g?.name)
      );
    }

    if (this.selectedRightSidebarTab === 'SECTORS' || this.selectedRightSidebarTab === 'ALL') {
      this.filteredSectors = this.filteredSectors.filter((s: SectorDTO) =>
        this.matchesSidebarQuery(s?.nameEn) ||
        this.matchesSidebarQuery(s?.nameFr) ||
        this.matchesSidebarQuery(s?.nameAr)
      );
    }

    this.sidebarProjects = this.sidebarProjectsBase.filter((project: LocationDTO) => {
      const locationText = this.getProjectResolvedLocationText(project);
      return this.matchesSidebarQuery(project?.name) || this.matchesSidebarQuery(locationText);
    });
  }

  async selectDistrictFromList(district: any): Promise<void> {
    if (!district?.id) return;

    await this.animateSidebarForMapSelection(
      { type: 'district_list', id: district.id },
      async () => {
        this.selectDistrictNeonGlow(String(district.id));
        this.activeSidebarItemKey = this.getDistrictSelectionKey(String(district.id));
        this.syncNavIndexToEntity('district', district.id);

        // Unified zoom — fires first, showDistrict does NOT steal camera
        this.zoomToDistrict(district.id);
        this.showDistrict(district.id, true /* skipFlyToDistrict */).catch(() => {});

        const opened = await this.openDistrictHierarchy(String(district.id), { targetView: 'district' });

        if (!opened) {
          this.selectedDistrict = { id: district.id, meta: district };
          this.showLocationsList  = false;
          this.showRightSidebar   = true;
          this.selectedLocation   = {
            id: 0, name: district.name_en, category: 'DISTRICT', type: LocationType.ZONE,
            mapId: this.currentMap?.id || 0, parentId: this.countryLocationId || undefined,
            geometry: this.tunisiaDistrictGeometries[district.id] || null, contents: [],
          };
          this.selectedName = district.name_en;
          this.selectedContents = [];
          this.cdr.detectChanges();

          // Strict scoped projects only
          const scoped = this.getStrictDistrictProjects(String(district.id));
          this.setSidebarProjects(scoped, `district_fallback_${district.id}`);
          return;
        }

        // Projects only show when governorate is selected, not when district is selected
        this.clearProjectsFromMap();
        const hierarchyDistrict = this.getHierarchyDistrict();
        if (!hierarchyDistrict) {
          const scoped = this.getStrictDistrictProjects(String(district.id));
          this.setSidebarProjects(scoped, `district_fallback_${district.id}`);
        }
      }
    );
  }

  async selectGovernorateFromList(governorate: any): Promise<void> {
    const govDisplayName = governorate.displayName || governorate.name;
    const govEntityName  = governorate.name || govDisplayName;

    await this.animateSidebarForMapSelection(
      { type: 'governorate_list', name: govEntityName },
      async () => {
        this.activeSidebarItemKey = this.getGovernorateSelectionKey(String(govEntityName));
        const navIdx = this.unifiedNavList.findIndex(i => i.type === 'governorate' && i.data.name === govEntityName);
        if (navIdx >= 0) this.unifiedNavIndex = navIdx;

        // Unified zoom — fires immediately, independent of hierarchy
        this.zoomToGovernorate(
          govEntityName,
          governorate.districtId,
          Number.isFinite(Number(governorate.index)) ? Number(governorate.index) : undefined
        );

        const opened = await this.openGovernorateHierarchyByName(
          govEntityName, governorate.districtId,
          Number.isFinite(Number(governorate.index)) ? Number(governorate.index) : undefined
        );

        if (!opened) {
          this.selectedDistrict  = null;
          this.showLocationsList = false;
          this.showRightSidebar  = true;
          this.selectedLocation  = {
            id: 0, name: govDisplayName, category: 'GOVERNORATE', type: LocationType.ZONE,
            mapId: this.currentMap?.id || 0, parentId: this.countryLocationId || undefined,
            geometry: null, contents: [],
          };
          this.selectedName = govDisplayName;
          this.selectedContents = [];
          // Show scoped projects even when hierarchy fails
          const govLoc = this.findGovernorateLocationByNames([govEntityName, govDisplayName]);
          const scoped = this.getStrictGovernorateProjects(govLoc, govEntityName);
          this.setSidebarProjects(scoped, `governorate_fallback_${govEntityName}`);
          this.cdr.detectChanges();
        }
      }
    );
  }

  private async flyToGovernorate(govFeature: any): Promise<void> {
    try {
      if (!govFeature?.geometry) return;
      const bbox = turf.bbox(govFeature); const [minLon, minLat, maxLon, maxLat] = bbox;
      const centerLon = (minLon + maxLon) / 2; const centerLat = (minLat + maxLat) / 2;
      this.zoomToCoordinates(centerLon, centerLat);
    } catch (error) { console.error('Governorate fly error:', error); }
  }

  selectGovernorate(gov: any) {
    this.currentGovernorateIndex = this.filteredGovernorates.findIndex(g => g.id === gov.id);
    this.selectedLocation = gov;
    this.flyToGovernorate(gov);
  }

  private async getGovernorateAsLocation(governorateName: string): Promise<LocationDTO> {
    if (!this.currentMap || !this.currentMap.id) throw new Error('No map selected.');
    const normalize = (s: string) => s.toLowerCase().replace(/\s+/g, '').normalize('NFD').replace(/\p{Diacritic}/gu, '');
    const targetNorm = normalize(governorateName);
    if (this.currentMap?.id) {
      try {
        const locations = await this.locationsService.getLocationsByMap(this.currentMap.id).toPromise();
        const existing = locations?.find((loc: any) => normalize(loc.name) === targetNorm && loc.category === 'GOVERNORATE');
        if (existing) {
          if (existing.id && existing.id > 0) {
            try {
              return await this.locationsService.getLocationDetails(existing.id).toPromise();
            } catch (detailError) {
              return existing;
            }
          }
          return existing;
        }
      } catch (e) {}
    }
    let govFeature: any = null;
    for (const dId of Object.keys(this.tunisiaDistrictMatchedFeatures)) {
      const matched = this.tunisiaDistrictMatchedFeatures[dId];
      const found = matched?.find((f: any) => { const name = (f.properties && (f.properties.NAME_1 || f.properties.NAME || f.properties.ADMIN)) || ''; return normalize(name) === targetNorm || normalize(name).includes(targetNorm) || targetNorm.includes(normalize(name)); });
      if (found) { govFeature = found; break; }
    }
    if (!govFeature) throw new Error('Governorate not found');
    const baseName = (govFeature.properties && (govFeature.properties.NAME_1 || govFeature.properties.NAME || govFeature.properties.ADMIN)) || '';
    return { id: 0, name: baseName, category: 'GOVERNORATE', type: LocationType.ZONE, mapId: this.currentMap.id, parentId: this.countryLocationId || undefined, geometry: govFeature.geometry, contents: [] };
  }

  async getDistrictAsLocation(): Promise<LocationDTO | null> {
    if (!this.selectedDistrict) return null;
    const districtId = this.selectedDistrict.id; const meta = this.selectedDistrict.meta;
    const geometry = this.tunisiaDistrictGeometries[districtId];
    if (!geometry) return null;
    if (!this.currentMap || !this.currentMap.id) return null;
    const locationDTO: LocationDTO = { id: 0, name: meta.name_en, category: 'DISTRICT', type: LocationType.ZONE, mapId: this.currentMap.id, parentId: this.countryLocationId || undefined, geometry, contents: [] };
    if (this.currentMap?.id) {
      try {
        const locations = await this.locationsService.getLocationsByMap(this.currentMap.id).toPromise();
        const normalize = (s: string) => s.toLowerCase().replace(/\s+/g, '').normalize('NFD').replace(/\p{Diacritic}/gu, '');
        const existing = locations?.find((loc: any) => normalize(loc.name) === normalize(meta.name_en) && loc.category === 'DISTRICT');
        if (existing) {
          if (existing.id && existing.id > 0) {
            try {
              return await this.locationsService.getLocationDetails(existing.id).toPromise();
            } catch (detailError) {
              return existing;
            }
          }
          return existing;
        }
      } catch (e) {}
    }
    return locationDTO;
  }

  async editGovernorate(): Promise<void> {
    if (!this.selectedLocation) return;

    if (this.selectedLocation.id && this.selectedLocation.id > 0) {
      try {
        const fullLocation = await this.locationsService.getLocationDetails(this.selectedLocation.id).toPromise();
        if (fullLocation) {
          this.selectedLocation = { ...(this.selectedLocation as any), ...(fullLocation as any) };
          this.selectedContents = this.selectedLocation.contents || [];
        }
      } catch (e) {}
    }

    const dialogRef = this.dialog.open(PolygonContentDialogComponent, { width: '600px', maxHeight: '80vh', panelClass: 'dark-dialog', backdropClass: 'dialog-backdrop', disableClose: false, data: { name: this.selectedLocation.name, contents: this.selectedLocation.contents || [], isCreating: this.selectedLocation.id === 0 || !this.selectedLocation.id } });
    dialogRef.afterClosed().subscribe(async (result) => {
      if (result !== null && result !== undefined && this.selectedLocation) {
        if (!this.currentMap || !this.currentMap.id) return;
        const locationDTO: LocationDTO = { ...this.selectedLocation, mapId: this.currentMap.id, parentId: this.countryLocationId || undefined };
        const normalizedContents = Array.isArray(result) ? result : ((result as any)?.contents || []);
        const requestBody = { location: locationDTO, contents: normalizedContents };
        try {
          let saved: LocationDTO;
          if (this.selectedLocation.id && this.selectedLocation.id > 0) saved = await this.locationsService.updateLocation(this.selectedLocation.id, requestBody).toPromise();
          else saved = await this.locationsService.createLocation(requestBody).toPromise();
          Swal.fire({ title: 'Success', text: 'Governorate saved successfully', icon: 'success', timer: 2000, showConfirmButton: false, toast: true, position: 'top-end', background: '#1f2937', color: '#ffffff' });
          this.loadLocations(); this.selectedLocation = saved; this.selectedContents = saved.contents || [];
        } catch (error) { Swal.fire({ title: 'Error', text: 'Failed to save governorate', icon: 'error', background: '#1f2937', color: '#ffffff' }); }
      }
    });
  }

  deleteGovernorate(): void {
    if (!this.selectedLocation) return;
    Swal.fire({ title: 'Are you sure?', text: `Delete governorate "${this.selectedLocation.name}"?`, icon: 'warning', showCancelButton: true, confirmButtonColor: '#f44336', cancelButtonColor: '#6b7280', background: '#1f2937', color: '#ffffff' }).then((result) => {
      if (result.isConfirmed && this.selectedLocation && this.selectedLocation.id) {
        this.locationsService.deleteLocation(this.selectedLocation.id).subscribe(() => { Swal.fire({ title: 'Deleted!', icon: 'success', timer: 2000, showConfirmButton: false, toast: true, position: 'top-end', background: '#1f2937', color: '#ffffff' }); this.clearSelection(); this.loadLocations(); }, () => { Swal.fire({ title: 'Error!', text: 'Failed to delete', icon: 'error', background: '#1f2937', color: '#ffffff' }); });
      }
    });
  }

  async editDistrict(): Promise<void> {
    const districtLocation = await this.getDistrictAsLocation();
    if (!districtLocation) return;
    const dialogRef = this.dialog.open(PolygonContentDialogComponent, { width: '600px', maxHeight: '80vh', panelClass: 'dark-dialog', backdropClass: 'dialog-backdrop', disableClose: false, data: { name: districtLocation.name, contents: districtLocation.contents || [], isCreating: districtLocation.id === 0 || !districtLocation.id } });
    dialogRef.afterClosed().subscribe(async (result) => {
      if (result !== null && result !== undefined && districtLocation) {
        if (!this.currentMap || !this.currentMap.id) return;
        const normalizedContents = Array.isArray(result) ? result : ((result as any)?.contents || []);
        const requestBody = { location: { ...districtLocation, mapId: this.currentMap.id, parentId: this.countryLocationId || undefined }, contents: normalizedContents };
        try {
          const saved = await this.locationsService.createOrUpdateDistrict(requestBody, this.countryLocationId || undefined).toPromise();
          Swal.fire({ title: 'Success', text: 'District saved successfully', icon: 'success', timer: 2000, showConfirmButton: false, toast: true, position: 'top-end', background: '#1f2937', color: '#ffffff' });
          this.loadLocations(); this.selectedLocation = saved; this.selectedContents = saved.contents || [];
        } catch (error) { Swal.fire({ title: 'Error', text: 'Failed to save district', icon: 'error', background: '#1f2937', color: '#ffffff' }); }
      }
    });
  }

  deleteDistrict(): void {
    if (!this.selectedLocation) return;
    Swal.fire({ title: 'Are you sure?', text: `Delete district "${this.selectedLocation.name}"?`, icon: 'warning', showCancelButton: true, confirmButtonColor: '#f44336', cancelButtonColor: '#6b7280', background: '#1f2937', color: '#ffffff' }).then((result) => {
      if (result.isConfirmed && this.selectedLocation && this.selectedLocation.id) {
        this.locationsService.deleteLocation(this.selectedLocation.id).subscribe(() => { Swal.fire({ title: 'Deleted!', icon: 'success', timer: 2000, showConfirmButton: false, toast: true, position: 'top-end', background: '#1f2937', color: '#ffffff' }); this.clearSelection(); this.loadLocations(); }, () => { Swal.fire({ title: 'Error!', text: 'Failed to delete', icon: 'error', background: '#1f2937', color: '#ffffff' }); });
      }
    });
  }

  loadSectors(): void {
    this.sectorService.getAllSectors().subscribe({ next: (sectors) => { this.allSectors = sectors; this.filteredSectors = sectors; this.cdr.detectChanges(); }, error: (err) => console.error('Error loading sectors:', err) });
  }

loadProjects(): void {
  if (!this.currentMap?.id) return;
  const mapId = this.currentMap.id;

  if (this._projectsCache?.mapId === mapId && this.isCacheValid(this._projectsCache.ts)) {
    this.allProjects = this._projectsCache.data;
    this.groupProjectsBySector();
    this.refreshDistrictPinsWithProjectIndicators();
    this.buildUnifiedNavList();
    this.cdr.detectChanges();
    return;
  }

  // KEY: deduplicate concurrent calls — only one XHR even if called 15x rapidly
  this.deduped(`projects-${mapId}`, () =>
    this.locationsService.getLocationsByMapWithGeometry(mapId, true).toPromise()
  ).then((locations: any[]) => {
    this.allProjects = locations.filter((l: any) => l.category === 'PROJECT');
    this._projectsCache = { mapId, data: this.allProjects, ts: Date.now() };
    this.groupProjectsBySector();
    this.refreshDistrictPinsWithProjectIndicators();
    this.buildUnifiedNavList();
    this.cdr.detectChanges();
  }).catch(err => console.error('Error loading projects:', err));
}

  private groupProjectsBySector(): void {
    this.projectsBySector = new Map();
    this.allProjects.forEach((project: any) => {
      const sectorId = project.sectorId != null ? Number(project.sectorId) : NaN;
      if (isNaN(sectorId)) return;
      const arr = this.projectsBySector.get(sectorId) || [];
      arr.push(project); this.projectsBySector.set(sectorId, arr);
    });
  }

  private async refreshDistrictPinsWithProjectIndicators(): Promise<void> {
    if (!this.currentMap?.id || this.allProjects.length === 0) return;
    const projectParentIds = new Set<string>(this.allProjects.map((p: any) => String(p.parentId)).filter(id => id && id !== 'null' && id !== 'undefined'));
    if (projectParentIds.size === 0) return;
    let allLocs: any[] = [];
    try { allLocs = await this.locationsService.getLocationsByMap(this.currentMap.id).toPromise() || []; } catch (e) { return; }
    const normalize = (s: string) => (s || '').toLowerCase().replace(/\s+/g, '').normalize('NFD').replace(/\p{Diacritic}/gu, '');
    for (const [districtId, meta] of Object.entries(this.tunisiaDistrictsMapping)) {
      const districtLoc = allLocs.find((l: any) => l.category === 'DISTRICT' && normalize(l.name) === normalize(meta.name_en));
      const districtBackendId = districtLoc?.id ? String(districtLoc.id) : null;
      let districtHasProjects = districtBackendId ? projectParentIds.has(districtBackendId) : false;
      if (!districtHasProjects) {
        for (const memberName of meta.members) {
          const govLoc = allLocs.find((l: any) => l.category === 'GOVERNORATE' && normalize(l.name) === normalize(memberName));
          if (govLoc?.id && projectParentIds.has(String(govLoc.id))) { districtHasProjects = true; break; }
        }
      }
      if (districtHasProjects) this.updateDistrictPinWithProjectIndicator(districtId, meta, true);
    }
    for (const govEntity of this.displayedDistrictEntities) {
      const now = Cesium.JulianDate.now(); const props = govEntity.properties; if (!props) continue;
      const nameEn: string = props['name_en']?.getValue ? props['name_en'].getValue(now) : props['name_en'];
      if (!nameEn) continue;
      const govLoc = allLocs.find((l: any) => l.category === 'GOVERNORATE' && normalize(l.name) === normalize(nameEn));
      if (govLoc?.id && projectParentIds.has(String(govLoc.id))) { if (govEntity.billboard) govEntity.billboard.image = new Cesium.ConstantProperty(this.createDiamondPinIcon(this.governoratePinColor, 46)); }
    }
  }

  private updateDistrictPinWithProjectIndicator(districtId: string, meta: any, hasProjects: boolean): void {
    const pinEntity = this.districtLabelEntities.find(entity => {
      const now = Cesium.JulianDate.now(); const props = entity.properties; if (!props) return false;
      const entityDistrictId = props['districtId']?.getValue ? props['districtId'].getValue(now) : props['districtId'];
      return entityDistrictId === districtId;
    });
    if (!pinEntity) return;
    if (hasProjects) pinEntity.billboard!.image = new Cesium.ConstantProperty(this.createProjectIndicatorDistrictPin(districtId, meta));
  }

  private createProjectIndicatorDistrictPin(districtId: string, meta: any): string {
    void districtId;
    void meta;
    return this.createMapPinSvg(this.districtPinColor, 56, '#ffffff', this.projectDotColor);
  }

  clearProjectsFromMap(): void {
    if (!this.viewer) return;
    this.displayedProjectEntities.forEach(entity => { this.viewer.entities.remove(entity); });
    this.displayedProjectEntities = [];
  }

  // ════════════════════════════════════════════════════════════════════════════
  // selectProject — unified zoom, updates sidebar, used by BOTH map and sidebar
  // ════════════════════════════════════════════════════════════════════════════
  selectProject(project: LocationDTO | null, zoomHeight: number = this.ZOOM_HEIGHT): void {
    if (project) this.clearHierarchyState();
    if (project) this.logSidebarProjectIntegrity(project, 'selectProject');
    this.selectedProject = project; this.selectedDistrict = null;
    if (!project || !this.viewer) return;
    this.selectedLocation = null;
    this.selectedContents = (project as any).contents || [];
    this.selectedName = project.name || this.i18nService.translate('sidebar.unknown_project');
    this.showLocationsList = false;
    this.showRightSidebar = true;
    this.syncNavIndexToEntity('project', project.id!);
    this.cdr.detectChanges();
    this.scheduleSidebarAutoHide();

    const entityId = `project-pin-${project.id}`;
    const entity = this.viewer.entities.getById(entityId);
    if (entity) {
      this.zoomToEntityWithHeight(entity, zoomHeight);
    } else if (project.geometry?.type === 'Point' && Array.isArray(project.geometry.coordinates)) {
      const lon = Number(project.geometry.coordinates[0]);
      const lat = Number(project.geometry.coordinates[1]);
      if (!isNaN(lon) && !isNaN(lat)) {
        this.zoomToCoordinatesWithHeight(lon, lat, zoomHeight);
      }
    }

    if (project.id) {
      this.locationsService.getLocationDetails(project.id).subscribe({
        next: (location: LocationDTO) => {
          this.selectedProject  = { ...(this.selectedProject as any), ...(location as any) };
          this.selectedContents = location.contents || [];
          this.selectedName     = location.name || this.i18nService.translate('sidebar.unknown_project');
          this.cdr.detectChanges();
        },
        error: () => {}
      });
    }
  }

  private logSidebarProjectIntegrity(project: LocationDTO | null | undefined, source: string): void {
    if (!project) {
      console.error(`[${source}] project is null/undefined`);
      return;
    }

    const data: any = project;
    const issues: string[] = [];
    if (project.id == null) issues.push('missing id');
    if (!String(project.name || '').trim()) issues.push('missing name');
    if (data.parentId == null) issues.push('missing parentId');
    if (!project.geometry) issues.push('missing geometry');

    if (issues.length > 0) {
      console.warn(`[${source}] incomplete project payload (${issues.join(', ')}):`, project);
    }
  }

  filterSectors(searchText: string): void {
    if (!searchText || searchText.trim() === '') { this.filteredSectors = this.allSectors; return; }
    const search = searchText.toLowerCase().trim();
    this.filteredSectors = this.allSectors.filter(sector =>
      (sector.nameAr && sector.nameAr.toLowerCase().includes(search)) ||
      (sector.nameEn && sector.nameEn.toLowerCase().includes(search)) ||
      (sector.nameFr && sector.nameFr.toLowerCase().includes(search))
    );
  }

  getProjectsBySector(sectorId: number | undefined): LocationDTO[] {
    if (sectorId == null) return [];
    return this.allProjects.filter(p => { const pid = (p as any).sectorId; return pid != null && Number(pid) === Number(sectorId); });
  }

  editProject(): void {
    if (!this.selectedProject) return;
    Swal.fire({ title: 'Edit Project', text: 'Project editing UI will be implemented soon.', icon: 'info', confirmButtonText: 'OK', background: '#1f2937', color: '#ffffff' });
  }

  deleteProject(): void {
    if (!this.selectedProject || !this.selectedProject.id) return;
    Swal.fire({ title: 'Are you sure?', text: `Delete project "${this.selectedProject.name}"?`, icon: 'warning', showCancelButton: true, confirmButtonColor: '#f44336', cancelButtonColor: '#6b7280', background: '#1f2937', color: '#ffffff' }).then((result) => {
      if (result.isConfirmed && this.selectedProject && this.selectedProject.id) {
        this.locationsService.deleteLocation(this.selectedProject.id).subscribe({
          next: () => { Swal.fire({ title: 'Deleted!', icon: 'success', timer: 2000, showConfirmButton: false, toast: true, position: 'top-end', background: '#1f2937', color: '#ffffff' }); this.selectedProject = null; this.showLocationsList = true; this.loadProjects(); },
          error: () => { Swal.fire({ title: 'Error!', text: 'Failed to delete project', icon: 'error', background: '#1f2937', color: '#ffffff' }); }
        });
      }
    });
  }

  private createMapPinSvg(color: string, size: number = 48, centerColor: string = '#ffffff', indicatorDotColor?: string, selected: boolean = false): string {
    void centerColor;
    const sourceSize = Math.max(this.diamondPinSourceSize, size);
    const canvas = document.createElement('canvas');
    canvas.width = sourceSize;
    canvas.height = sourceSize;
    const ctx = canvas.getContext('2d');
    if (!ctx) return '';

    // Draw in a normalized 128x128 space, then rasterize to a large PNG.
    const unit = sourceSize / 128;
    ctx.scale(unit, unit);

    ctx.save();
    ctx.shadowColor = selected ? 'rgba(0, 0, 0, 0.82)' : 'rgba(0, 0, 0, 0.42)';
    ctx.shadowBlur = selected ? 14 : 3;
    ctx.shadowOffsetY = selected ? 8 : 3;
    ctx.beginPath();
    ctx.moveTo(64, 10);
    ctx.lineTo(118, 64);
    ctx.lineTo(64, 118);
    ctx.lineTo(10, 64);
    ctx.closePath();
    ctx.fillStyle = color;
    ctx.fill();
    ctx.lineJoin = 'round';
    ctx.lineWidth = selected ? 8 : 6;
    ctx.strokeStyle = selected ? '#FFF2A8' : '#FFD700';
    ctx.stroke();
    ctx.restore();

    if (indicatorDotColor) {
      ctx.beginPath();
      ctx.arc(92, 92, 10, 0, Math.PI * 2);
      ctx.fillStyle = indicatorDotColor;
      ctx.fill();
      ctx.lineWidth = 2;
      ctx.strokeStyle = 'rgba(0, 0, 0, 0.35)';
      ctx.stroke();
    }

    return canvas.toDataURL('image/png');
  }

  private createSelectedPinIconForEntity(entity: Cesium.Entity): string | null {
    const props = (entity as any).properties;
    if (!props) return null;

    const now = Cesium.JulianDate.now();
    const getProp = (name: string): any => {
      const value = props[name];
      return value?.getValue ? value.getValue(now) : value;
    };

    const isGovernorate = !!getProp('isGovernorate');
    const isDistrictPin = !!getProp('isDistrictPin') || getProp('labelType') === 'district';
    const isZonePin = !!getProp('isZonePin');
    const typeProp = String(getProp('type') || '').toLowerCase();
    const locationObj = getProp('location');
    const isProject = !!(
      typeProp === 'project' ||
      (locationObj && String(locationObj.category || '').toUpperCase() === 'PROJECT')
    );

    if (isGovernorate || isZonePin) {
      return this.createMapPinSvg(this.governoratePinColor, this.diamondPinSourceSize, '#ffffff', undefined, true);
    }
    if (isDistrictPin) {
      return this.createMapPinSvg(this.districtPinColor, this.diamondPinSourceSize, '#ffffff', undefined, true);
    }
    if (isProject) {
      return this.createMapPinSvg(this.projectDotColor, this.diamondPinSourceSize, '#ffffff', undefined, true);
    }
    return null;
  }

  private createDiamondPinIcon(color: string, size: number = 56): string {
    return this.createMapPinSvg(color, size, '#ffffff');
  }

  private createDotPinIcon(color: string = '#050505', size: number = 20): string {
    return this.createMapPinSvg(color, Math.max(26, size), '#ffffff');
  }

  private createProjectDiamondPin(highlighted: boolean = false): string {
    // When highlighted, use a bright gold/yellow color with glow effect
    const color = highlighted ? '#FFD700' : this.projectDotColor;
    const size = highlighted ? 42 : 34;
    return this.createMapPinSvg(color, size, '#ffffff');
  }
  private createUserPointDiamondPin(): string { return this.createDiamondPinIcon('#39FF14', 52); }
  private createCountryDiamondPin(): string { return this.createDiamondPinIcon('#9333ea', 64); }

  // Position the HTML selection-frame over a Cartesian (window coordinates)
  private positionSelectionFrameAtCartesian(cartesian: Cesium.Cartesian3 | undefined): void {
    try {
      const el = document.getElementById('selection-frame');
      if (!el || !this.viewer || !cartesian) return;
      const scene = this.viewer.scene;
      const windowCoords = (Cesium.SceneTransforms as any).wgs84ToWindowCoordinates(scene, cartesian as any);
      if (!windowCoords) { el.classList.remove('visible'); return; }
      el.style.left = `${Math.round(windowCoords.x)}px`;
      el.style.top  = `${Math.round(windowCoords.y)}px`;
      el.classList.add('visible');
    } catch (e) {
      // ignore positioning failures
    }
  }

  private positionSelectionFrameAtEntity(entity: Cesium.Entity | undefined): void {
    if (!entity || !entity.position || !this.viewer) { this.hideSelectionFrame(); return; }
    try {
      const updateFrame = () => {
        try {
          const currentPos = (entity.position as Cesium.PositionProperty).getValue(Cesium.JulianDate.now());
          this.positionSelectionFrameAtCartesian(currentPos);
        } catch (_) {}
      };
      updateFrame();
      // follow camera animation so the frame remains centered on the selected pin
      setTimeout(updateFrame, 120);
      setTimeout(updateFrame, Math.max(260, Math.round((this.ZOOM_DURATION || 0.8) * 1000) + 90));
    } catch (e) { this.hideSelectionFrame(); }
  }

  private hideSelectionFrame(): void {
    const el = document.getElementById('selection-frame'); if (!el) return; el.classList.remove('visible');
  }

  showProjectsForZone(zoneId: number | string): void {
    if (String(this.currentZoneProjectZoneId) === String(zoneId)) {
      this.clearZoneProjects();
      this.sidebarProjectsBase = [];
      this.sidebarProjects = [];
      return;
    }

    const zoneIdStr = String(zoneId);
    let zoneProjects = this.allProjects.filter((p: any) => p.parentId != null && String(p.parentId) === zoneIdStr);

    if (zoneProjects.length === 0) {
      const parentLocation = this.findLocationById(zoneId);
      if (parentLocation) {
        zoneProjects = this.getStrictGovernorateProjects(parentLocation, parentLocation.name);
      }
    }

    if (zoneProjects.length === 0) {
      console.warn(`[showProjectsForZone] No projects found for zoneId=${zoneIdStr}`);
    }

    this.setSidebarProjects(zoneProjects, zoneIdStr);
  }

  private renderZoneProjectPins(projects: any[]): void {
    const activeZoneKey = String(this.currentZoneProjectZoneId);
    const addProjectPin = (projectData: any): void => {
      if (String(this.currentZoneProjectZoneId) !== activeZoneKey) return;
      if (!projectData?.geometry?.coordinates) return;
      const lon = Number(projectData.geometry.coordinates[0]);
      const lat = Number(projectData.geometry.coordinates[1]);
      if (isNaN(lon) || isNaN(lat)) return;
      const entityId = `project-pin-${projectData.id}`;
      const existing = this.viewer.entities.getById(entityId);
      if (existing) this.viewer.entities.remove(existing);
      const pinImage = this.createProjectDiamondPin();
      const entity = this.viewer.entities.add({
        id: entityId,
        position: Cesium.Cartesian3.fromDegrees(lon, lat),
        billboard: {
          image: pinImage,
          verticalOrigin: Cesium.VerticalOrigin.CENTER,
          horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
          disableDepthTestDistance: Number.POSITIVE_INFINITY,
          scale: this.projectPinScale,
          scaleByDistance: this.createDiamondScaleByDistance(),
          width: this.diamondPinBillboardBaseSize,
          height: this.diamondPinBillboardBaseSize
        },
        properties: { type: 'project', projectData }
      });
      this.currentZoneProjectEntities.push(entity);
      this.displayedProjectEntities.push(entity);
    };

    const missingGeometryProjectIds: number[] = [];
    projects.forEach(project => {
      if (project?.geometry?.coordinates) addProjectPin(project);
      else if (project?.id != null) missingGeometryProjectIds.push(Number(project.id));
    });

    if (this.currentZoneProjectEntities.length > 0) {
      this.viewer.flyTo(this.currentZoneProjectEntities, {
        duration: this.ZOOM_DURATION,
        offset: new Cesium.HeadingPitchRange(this.ZOOM_HEADING, this.ZOOM_PITCH, this.PROJECT_PINS_ZOOM_HEIGHT)
      });
    }

    missingGeometryProjectIds.forEach(projectId => {
      this.locationsService.getLocationDetails(projectId).subscribe({
        next: (fullProject: any) => { addProjectPin(fullProject); this.cdr.detectChanges(); },
        error: () => {}
      });
    });
  }

  clearZoneProjects(): void {
    this.currentZoneProjectEntities.forEach(entity => { try { this.viewer.entities.remove(entity); } catch (e) {} });
    this.displayedProjectEntities = this.displayedProjectEntities.filter(e => !this.currentZoneProjectEntities.includes(e));
    this.currentZoneProjectEntities = []; this.currentZoneProjectZoneId = null;
  }

  /**
   * Highlight the selected project on the map with a larger scale and glow effect
   */
  private highlightProjectEntity(projectId: number | string): void {
    // First unhighlight any previously highlighted project
    this.unhighlightProjectEntity();

    // Find the project entity
    const entityId = `project-pin-${projectId}`;
    const entity = this.viewer.entities.getById(entityId);
    if (!entity || !entity.billboard) return;

    // Store reference and apply highlight
    this.highlightedProjectEntity = entity;
    entity.billboard.scale = new Cesium.ConstantProperty(this.projectHighlightScale);
    entity.billboard.image = new Cesium.ConstantProperty(this.createProjectDiamondPin(true)); // true = highlighted
  }

  /**
   * Remove highlight from the currently highlighted project
   */
  private unhighlightProjectEntity(): void {
    if (!this.highlightedProjectEntity || !this.highlightedProjectEntity.billboard) return;

    // Reset scale and image to normal
    this.highlightedProjectEntity.billboard.scale = new Cesium.ConstantProperty(this.projectPinScale);
    this.highlightedProjectEntity.billboard.image = new Cesium.ConstantProperty(this.createProjectDiamondPin(false)); // false = normal
    this.highlightedProjectEntity = null;
  }

  showProjectsForMultipleZones(zoneIds: (number | string)[]): void {
    const zoneIdStrs = zoneIds.map(id => String(id));
    const matchingProjects = this.allProjects.filter((p: any) => p.parentId != null && zoneIdStrs.includes(String(p.parentId)));
    this.setSidebarProjects(matchingProjects, `multi_${zoneIds[0]}`);
  }

  async onSectorProjectClick(project: LocationDTO): Promise<void> {
    this.logSidebarProjectIntegrity(project, 'onSectorProjectClick');
    await this.animateSidebarForMapSelection(
      { type: 'project_list', data: project },
      async () => {
        if ((project as any).parentId) this.showProjectsForZone((project as any).parentId);
        this.selectProject(project);
      }
    );
  }

  getProjectImage(project: LocationDTO): string | null {
    if (!project) return null;
    if (project.contents && project.contents.length > 0) return this.getLocationImage(project);
    const cached = this.allProjects.find(p => p.id === project.id);
    if (cached && cached.contents && cached.contents.length > 0) return this.getLocationImage(cached as any);
    return null;
  }

  // Called by the sidebar search input. Behavior depends on active tab.
  onSidebarSearch(term: string): void {
    this.sidebarSearchTerm = String(term || '').trim().toLowerCase();
    this.applyRightSidebarFilter();
    this.applySidebarSearchFilters();
    this.scheduleChangeDetection();
  }

  initializeAmazing3DGlobe() {
    this.viewer.scene.globe.enableLighting = true; this.viewer.scene.globe.dynamicAtmosphereLighting = true; this.viewer.scene.globe.dynamicAtmosphereLightingFromSun = true;
    if (this.viewer.scene.skyAtmosphere) { this.viewer.scene.skyAtmosphere.show = true; this.viewer.scene.skyAtmosphere.hueShift = 0.0; this.viewer.scene.skyAtmosphere.saturationShift = 0.2; this.viewer.scene.skyAtmosphere.brightnessShift = 0.1; }
    this.viewer.scene.globe.showGroundAtmosphere = true; this.viewer.scene.globe.atmosphereLightIntensity = 30.0; this.viewer.scene.globe.atmosphereSaturationShift = 0.3;
    this.addGlobeRotation(); this.addDynamicLighting();
  }

  isStrategicConnection(city1: any[], city2: any[]): boolean {
    const majorHubs = ['New York', 'London', 'Tokyo', 'Singapore', 'Dubai', 'Hong Kong', 'Paris', 'Beijing', 'Sydney', 'Los Angeles'];
    return majorHubs.includes(city1[2]) && majorHubs.includes(city2[2]);
  }

  generateArcPositions(lon1: number, lat1: number, lon2: number, lat2: number): number[] {
    const positions: number[] = [];
    for (let i = 0; i <= 50; i++) { const t = i / 50; positions.push(lon1 + (lon2 - lon1) * t, lat1 + (lat2 - lat1) * t, Math.sin(t * Math.PI) * 500000); }
    return positions;
  }

  addAnimatedConnections(cities: any[][]) {
    const animatedPairs = [['New York', 'London'], ['Tokyo', 'Singapore'], ['Dubai', 'Singapore'], ['London', 'Paris'], ['Beijing', 'Tokyo']];
    animatedPairs.forEach(([city1Name, city2Name]) => {
      const city1 = cities.find(c => c[2] === city1Name); const city2 = cities.find(c => c[2] === city2Name);
      if (city1 && city2) {
        const arcPositions = this.generateArcPositions(Number(city1[0]), Number(city1[1]), Number(city2[0]), Number(city2[1]));
        this.viewer.entities.add({ polyline: { positions: Cesium.Cartesian3.fromDegreesArrayHeights(arcPositions), width: 3, material: new Cesium.PolylineGlowMaterialProperty({ glowPower: 0.25, taperPower: 0.5, color: new Cesium.CallbackProperty(() => Cesium.Color.fromCssColorString('#00FFFF').withAlpha(Math.abs(Math.sin(Date.now() * 0.001)) * 0.9), false) }) } });
      }
    });
  }

  private reverseArabicText(text: string): string { return text || text; }
  private createArabicTextImage(text: string, fontSize: number = 18, withBox: boolean = false): string { return ''; }
  private createDistrictPinIcon(districtIndex: number, totalDistricts: number): string {
    void districtIndex;
    void totalDistricts;
    return this.createDiamondPinIcon(this.districtPinColor, 48);
  }
  private getDistrictLabelText(meta: any): string {
    if (this.currentLanguage === 'ar') return this.reverseArabicText(meta.name_ar);
    if (this.currentLanguage === 'fr') return meta.name_fr;
    return meta.name_en;
  }
  private refreshHierarchyNodeIfActive(updatedLocation: LocationDTO): void {
  const district = this.hierarchyState.selectedDistrict;
  if (!district) return;

  // Check district node
  if (district.location?.id === updatedLocation.id) {
    district.location = updatedLocation;
    district.image = this.extractLocationImage(updatedLocation);
    district.description = this.extractLocationDescription(updatedLocation);
  }

  // Check governorate nodes
  district.governorates.forEach(gov => {
    if (gov.location?.id === updatedLocation.id) {
      gov.location = updatedLocation;
      gov.image = this.extractLocationImage(updatedLocation);
      gov.description = this.extractLocationDescription(updatedLocation);
      // Refresh projects list for this governorate
      const refreshed = this.getGovernorateProjects(gov.location, gov.name);
      if (refreshed.length > 0) gov.projects = refreshed;
    }

    // Check project nodes
    gov.projects.forEach(pNode => {
      if (pNode.project?.id === updatedLocation.id) {
        pNode.project = updatedLocation;
        pNode.image = this.extractLocationImage(updatedLocation);
        pNode.description = this.extractLocationDescription(updatedLocation);
      }
    });
  });

  // Re-sync legacy selection fields
  this.syncLegacySelectionFromHierarchy();
}
private getDistrictNeonOverride(districtId: string): Cesium.Color | null {
  const id = String(districtId);
  if (id === 'district1' || id === '1') {
    return Cesium.Color.fromCssColorString('#1a7fd4');   // blue neon
  }
  if (id === 'district2' || id === '2') {
    return Cesium.Color.fromCssColorString('#c9940a');   // gold neon
  }
  if (id === 'district3' || id === '3') {
    return Cesium.Color.fromCssColorString('#0b2a6b');   // dark blue neon
  }
  if (id === 'district4' || id === '4') {
    return Cesium.Color.fromCssColorString('#c9940a');   // gold neon
  }
  if (id === 'district5' || id === '5') {
    return Cesium.Color.fromCssColorString('#0b2a6b');   // dark blue neon
  }
  return null;
}
private highlightSelectedPin(entity: Cesium.Entity | null): void {
  // Restore previous pin
  if (this._selectedPinEntity) {
    try {
      if (this._selectedPinEntity.billboard) {
        if (this._selectedPinOriginalImage) {
          this._selectedPinEntity.billboard.image = 
            new Cesium.ConstantProperty(this._selectedPinOriginalImage);
        }
        (this._selectedPinEntity.billboard as any).color = 
          new Cesium.ConstantProperty(Cesium.Color.WHITE);
        if (this._selectedPinOriginalScale !== null) {
          (this._selectedPinEntity.billboard as any).scale =
            new Cesium.ConstantProperty(this._selectedPinOriginalScale);
        }
      }
    } catch (_) {}
    this._selectedPinEntity = null;
    this._selectedPinOriginalImage = null;
    this._selectedPinOriginalScale = null;
  }

  if (!entity?.billboard) return;

  // Store original
  try {
    const img = (entity.billboard as any).image?.getValue
      ? (entity.billboard as any).image.getValue(Cesium.JulianDate.now())
      : null;
    const currentScale = (entity.billboard as any).scale?.getValue
      ? (entity.billboard as any).scale.getValue(Cesium.JulianDate.now())
      : this.governoratePinScale;

    this._selectedPinEntity = entity;
    this._selectedPinOriginalImage = img;
    this._selectedPinOriginalScale = currentScale;

    const selectedIcon = this.createSelectedPinIconForEntity(entity);
    if (selectedIcon) {
      (entity.billboard as any).image = new Cesium.ConstantProperty(selectedIcon);
    }

    // Apply white glow tint
    (entity.billboard as any).color = new Cesium.ConstantProperty(
      Cesium.Color.fromCssColorString('#ffffff').withAlpha(1.0)
    );
    // Scale up slightly to make it obvious
    (entity.billboard as any).scale = new Cesium.ConstantProperty(currentScale * 1.35);

    this.viewer.scene.requestRender();
  } catch (_) {}
}
}