export default class AnchorNavigation {
  private static readonly MODAL_OPEN_BODY_CLASS = "has-modal";
  private static readonly STICKY_FLOAT_TOLERANCE = 3;
  private static readonly SCROLLSPY_CENTER_BUFFER = 50;
  private static readonly TOP_SECTION_THRESHOLD = 100;
  private static readonly DRAG_START_THRESHOLD = 6;
  private static readonly SCROLL_END_DEBOUNCE_MS = 150;
  private static readonly SCROLL_EDGE_TOLERANCE = 1;
  private static readonly SCROLL_ALIGNMENT_TOLERANCE = 1;
  private static readonly MAX_SCROLL_CORRECTIONS = 2;

  private element: HTMLElement;
  private contentLeftElement: HTMLElement | null;
  private megamenuElement: HTMLElement | null;
  private resizeObserver: ResizeObserver | null;
  private supportsScrollEnd: boolean;
  private anchorClickHandler: (event: Event) => void;
  private scrollHandler: () => void;
  private scrollSpyHandler: () => void;
  private scrollEndHandler: () => void;
  private scrollEndFallbackHandler: () => void;
  private resizeHandler: () => void;
  private mouseDownHandler: (event: MouseEvent) => void;
  private dragStartHandler: (event: MouseEvent) => void;
  private dragMoveHandler: (event: MouseEvent) => void;
  private dragEndHandler: () => void;
  private horizontalScrollHandler: () => void;
  private nativeDragStartHandler: (event: DragEvent) => void;
  private isAutoScrolling: boolean = false;
  private isPointerDown: boolean = false;
  private isDraggingNav: boolean = false;
  private suppressClick: boolean = false;
  private dragStartX: number = 0;
  private dragStartScrollLeft: number = 0;
  private scrollTimeout: ReturnType<typeof setTimeout> | null = null;
  private scrollEndFallbackTimeout: ReturnType<typeof setTimeout> | null = null;
  private navLinks: NodeListOf<HTMLAnchorElement> | null = null;
  private sections: HTMLElement[] = [];
  private currentPath: string;
  private lastActiveIndex: number = 0;
  private autoScrollTargetId: string | null = null;
  private autoScrollCorrectionCount: number = 0;
  private lastStickyOffset: number = 0;
  private modalClassObserver: MutationObserver | null = null;
  private modalStateHandler: () => void;
  private isForcedFixed: boolean = false;
  private wasStickyFloating: boolean = false;
  private forcedFixedTop: number = 0;
  private forcedFixedLeft: number = 0;
  private fixedFlowPlaceholder: HTMLDivElement | null = null;
  private originalInlineStyles: {
    position: string;
    top: string;
    left: string;
    right: string;
    width: string;
  } | null = null;

  constructor(element: HTMLElement) {
    this.element = element;

    this.contentLeftElement = null;
    this.megamenuElement = null;
    this.resizeObserver = null;
    this.supportsScrollEnd = "onscrollend" in window;
    this.isAutoScrolling = false;
    this.currentPath = window.location.pathname;

    this.anchorClickHandler = this.handleAnchorClick.bind(this);
    this.scrollHandler = this.updateStickyPosition.bind(this);
    this.scrollSpyHandler = this.handleScrollSpy.bind(this);
    this.scrollEndHandler = this.handleScrollEnd.bind(this);
    this.scrollEndFallbackHandler = this.handleScrollEndFallback.bind(this);
    this.resizeHandler = this.handleResize.bind(this);
    this.mouseDownHandler = this.handleMouseDown.bind(this);
    this.dragStartHandler = this.handleDragStart.bind(this);
    this.dragMoveHandler = this.handleDragMove.bind(this);
    this.dragEndHandler = this.handleDragEnd.bind(this);
    this.horizontalScrollHandler =
      this.updateHorizontalOverflowState.bind(this);
    this.nativeDragStartHandler = this.handleNativeDragStart.bind(this);
    this.modalStateHandler = this.handleModalStateChange.bind(this);

    (this.element as any).ODS_AnchorNavigation = this;

    this.init();

    return this;
  }

  static getInstance(el: HTMLElement): AnchorNavigation | null {
    return el && (el as any).ODS_AnchorNavigation
      ? (el as any).ODS_AnchorNavigation
      : null;
  }

  private findMegamenuElement(): HTMLElement | null {
    return document.querySelector("[data-megamenu]") as HTMLElement | null;
  }

  private getMegamenuOffsetHeight(): number {
    return this.megamenuElement?.offsetHeight || 0;
  }

  private updateStickyPosition(): void {
    if (this.isForcedFixed) return;

    if (!this.megamenuElement) return;

    const stickyOffset = this.getMegamenuOffsetHeight();
    const hasStickyOffsetChanged = stickyOffset !== this.lastStickyOffset;

    this.lastStickyOffset = stickyOffset;
    this.element.style.top = `${stickyOffset}px`;
    this.updateStickyFloatingState();

    if (
      !hasStickyOffsetChanged ||
      !this.isAutoScrolling ||
      !this.autoScrollTargetId ||
      this.autoScrollCorrectionCount >= AnchorNavigation.MAX_SCROLL_CORRECTIONS
    ) {
      return;
    }

    const targetElement = document.getElementById(this.autoScrollTargetId);
    if (!targetElement) return;

    // Re-target smoothly as soon as sticky offset changes to avoid a visible snap at scroll end.
    this.autoScrollCorrectionCount += 1;
    this.scrollToSection(targetElement, "smooth");
  }

  private setupMegamenuObserver(): void {
    this.megamenuElement = this.findMegamenuElement();

    if (!this.megamenuElement) {
      this.lastStickyOffset = 0;
      this.element.style.top = "0px";
      return;
    }

    this.updateStickyPosition();
    window.addEventListener("scroll", this.scrollHandler, { passive: true });

    this.resizeObserver = new ResizeObserver(
      this.updateStickyPosition.bind(this),
    );
    this.resizeObserver.observe(this.megamenuElement);
  }

  private setupScrollSpy(): void {
    // Set dynamic scroll margin for CSS
    document.documentElement.style.setProperty(
      "--extra-scroll-margin",
      this.element.offsetHeight + "px",
    );

    // Get all anchor navigation links
    this.navLinks = this.element.querySelectorAll(".anchor-navigation__item");

    // Get all sections that correspond to the navigation links
    this.sections = Array.from(this.navLinks || [])
      .map((link) => link.getAttribute("href"))
      .filter((href) => href?.includes("#"))
      .map((href) => document.getElementById(href!.split("#")[1]))
      .filter(Boolean) as HTMLElement[];

    this.teardownScrollSpyListeners();

    this.element.addEventListener("click", this.anchorClickHandler);
    this.element.addEventListener("mousedown", this.mouseDownHandler);
    window.addEventListener("scroll", this.scrollSpyHandler, { passive: true });

    if (this.supportsScrollEnd) {
      window.addEventListener("scrollend", this.scrollEndHandler);
    } else {
      window.addEventListener("scroll", this.scrollEndFallbackHandler, {
        passive: true,
      });
    }

    window.addEventListener("resize", this.resizeHandler);

    this.initScrollSpy();
    this.alignInitialHashIfNeeded();
  }

  private teardownScrollSpyListeners(): void {
    this.element.removeEventListener("click", this.anchorClickHandler);
    this.element.removeEventListener("mousedown", this.mouseDownHandler);
    window.removeEventListener("scroll", this.scrollSpyHandler);
    window.removeEventListener("resize", this.resizeHandler);

    if (this.supportsScrollEnd) {
      window.removeEventListener("scrollend", this.scrollEndHandler);
    } else {
      window.removeEventListener("scroll", this.scrollEndFallbackHandler);
    }

    if (this.scrollEndFallbackTimeout) {
      clearTimeout(this.scrollEndFallbackTimeout);
      this.scrollEndFallbackTimeout = null;
    }
  }

  private handleMouseDown(event: MouseEvent): void {
    if (event.button !== 0) return;

    const target = event.target as HTMLElement | null;
    if (!target || !this.element.contains(target)) return;

    const interactiveSelector =
      "a, button, input, select, textarea, label, [role='button'], [contenteditable='true']";
    if (target.closest(interactiveSelector)) return;

    // Prevent native text-selection drag from triggering page autoscroll.
    event.preventDefault();
  }

  private handleAnchorClick(event: Event): void {
    if (this.suppressClick) {
      event.preventDefault();
      event.stopPropagation();
      this.suppressClick = false;
      return;
    }

    const target = event.target as HTMLElement | null;
    const anchor = target?.closest(
      ".anchor-navigation__item",
    ) as HTMLAnchorElement | null;

    if (!anchor || !this.element.contains(anchor)) return;

    event.preventDefault();

    const href = anchor.getAttribute("href");
    if (!href || !href.includes("#")) return;

    const targetId = href.split("#")[1];
    const targetElement = document.getElementById(targetId);
    if (!targetElement) return;

    this.startAutoScroll(targetId);
    anchor.blur();

    this.scrollToSection(targetElement, "smooth");

    const nextUrl = `${window.location.pathname}${window.location.search}#${targetId}`;
    window.history.pushState(null, "", nextUrl);
    this.initScrollSpy(targetId);
  }

  private getHashSectionId(): string | null {
    const hash = window.location.hash;
    if (!hash || hash.length <= 1) return null;

    let hashId = hash.slice(1);
    try {
      hashId = decodeURIComponent(hashId);
    } catch {
      // Keep raw hash when decoding fails.
    }

    return hashId || null;
  }

  private startAutoScroll(sectionId: string): void {
    this.isAutoScrolling = true;
    this.autoScrollTargetId = sectionId;
    this.autoScrollCorrectionCount = 0;
  }

  private resetAutoScrollState(): void {
    this.isAutoScrolling = false;
    this.autoScrollTargetId = null;
    this.autoScrollCorrectionCount = 0;
  }

  private tryCorrectAutoScrollAlignment(): boolean {
    if (!this.autoScrollTargetId) return true;

    const targetElement = document.getElementById(this.autoScrollTargetId);
    if (!targetElement) return true;

    const targetTop = this.getTargetTop(targetElement);
    const distance = Math.abs(window.scrollY - targetTop);
    if (distance <= AnchorNavigation.SCROLL_ALIGNMENT_TOLERANCE) {
      return true;
    }

    if (
      this.autoScrollCorrectionCount >= AnchorNavigation.MAX_SCROLL_CORRECTIONS
    ) {
      return true;
    }

    this.autoScrollCorrectionCount += 1;
    const correctionBehavior: ScrollBehavior =
      this.autoScrollCorrectionCount === 1 ? "smooth" : "auto";
    this.scrollToSection(targetElement, correctionBehavior);
    return false;
  }

  private getTotalStickyOffset(): number {
    const scrollOffset = this.megamenuElement
      ? this.megamenuElement.offsetHeight
      : 0;
    const anchorNavOffset = this.element.offsetHeight;
    return scrollOffset + anchorNavOffset;
  }

  private getConfiguredScrollPaddingTop(): number {
    const rootStyles = window.getComputedStyle(document.documentElement);
    const rawScrollPaddingTop = rootStyles.scrollPaddingTop;
    const parsedScrollPaddingTop = Number.parseFloat(rawScrollPaddingTop);

    if (Number.isFinite(parsedScrollPaddingTop) && parsedScrollPaddingTop > 0) {
      return parsedScrollPaddingTop;
    }

    return 0;
  }

  private getEffectiveScrollOffset(): number {
    const configuredScrollPaddingTop = this.getConfiguredScrollPaddingTop();
    if (configuredScrollPaddingTop > 0) {
      return configuredScrollPaddingTop;
    }

    return this.getTotalStickyOffset();
  }

  private getTargetTop(targetElement: HTMLElement): number {
    const targetTop =
      targetElement.getBoundingClientRect().top +
      window.scrollY -
      this.getEffectiveScrollOffset();
    return Math.max(0, targetTop);
  }

  private scrollToSection(
    targetElement: HTMLElement,
    behavior: ScrollBehavior,
  ): void {
    window.scrollTo({
      top: this.getTargetTop(targetElement),
      behavior,
    });
  }

  private alignInitialHashIfNeeded(): void {
    const hashId = this.getHashSectionId();
    if (!hashId) return;

    const targetElement = document.getElementById(hashId);
    if (!targetElement) return;

    this.startAutoScroll(hashId);

    requestAnimationFrame(() => {
      this.scrollToSection(targetElement, "auto");
      requestAnimationFrame(() => {
        this.handleScrollEnd();
      });
    });
  }

  private initScrollSpy(forcedSectionId: string | null = null): void {
    if (!this.navLinks || !this.sections.length) return;

    let targetSection: HTMLElement | undefined;
    let targetIndex: number = -1;

    // Remove active class from all links
    this.navLinks.forEach((link) => link.classList.remove("is-active"));

    if (forcedSectionId) {
      targetSection = document.getElementById(forcedSectionId) || undefined;
      if (targetSection) {
        // Find the index of the forced section
        targetIndex = this.sections.findIndex(
          (section) => section.id === forcedSectionId,
        );
      }
    } else {
      const totalOffset = this.getEffectiveScrollOffset();
      const effectiveCenter =
        window.scrollY + totalOffset + AnchorNavigation.SCROLLSPY_CENTER_BUFFER;

      // Find the section that's currently in view
      for (let i = 0; i < this.sections.length; i++) {
        const section = this.sections[i];
        const { top: sectionTopRaw, height: sectionHeight } =
          section.getBoundingClientRect();
        const sectionTop = sectionTopRaw + window.scrollY;
        const sectionBottom = sectionTop + sectionHeight;

        if (effectiveCenter >= sectionTop && effectiveCenter < sectionBottom) {
          targetSection = section;
          targetIndex = i;
          break;
        }
      }
    }

    // Fallback logic: if no section is found, use fallback rules
    if (!targetSection) {
      // If we're at the very top, activate first item
      if (window.scrollY <= AnchorNavigation.TOP_SECTION_THRESHOLD) {
        targetIndex = 0;
        targetSection = this.sections[0];
      } else {
        // Keep the last active item
        targetIndex = this.lastActiveIndex;
        targetSection = this.sections[targetIndex];
      }
    }

    // Update last active index if we found a valid target
    if (targetIndex >= 0) {
      this.lastActiveIndex = targetIndex;
    }

    if (targetSection) {
      const id = targetSection.getAttribute("id");

      // Find the matching navigation link - try different href patterns
      let activeLink = this.element.querySelector(
        `.anchor-navigation__item[href="#${id}"]`,
      ) as HTMLElement;

      if (!activeLink) {
        activeLink = this.element.querySelector(
          `.anchor-navigation__item[href="${this.currentPath}#${id}"]`,
        ) as HTMLElement;
      }

      if (!activeLink) {
        // Try without current path for relative links
        activeLink = Array.from(this.navLinks).find((link) => {
          const href = link.getAttribute("href");
          return href && href.endsWith(`#${id}`);
        }) as HTMLElement;
      }

      if (activeLink) {
        activeLink.classList.add("is-active");

        // Scroll the navigation to center the active link
        const contentLeft = this.element.querySelector(
          ".anchor-navigation__content-left",
        ) as HTMLElement;
        if (contentLeft) {
          this.scrollActiveLinkIntoView(
            contentLeft,
            activeLink,
            Boolean(forcedSectionId),
          );
        }
      }
    }
  }

  private scrollActiveLinkIntoView(
    contentLeft: HTMLElement,
    activeLink: HTMLElement,
    forceCenter: boolean = false,
  ): void {
    const maxScrollLeft = Math.max(
      0,
      contentLeft.scrollWidth - contentLeft.clientWidth,
    );
    const contentRect = contentLeft.getBoundingClientRect();
    const itemRect = activeLink.getBoundingClientRect();
    const itemCenterWithinContent =
      itemRect.left -
      contentRect.left +
      contentLeft.scrollLeft +
      itemRect.width / 2;
    const targetScrollLeft =
      itemCenterWithinContent - contentLeft.clientWidth / 2;

    const behavior = window.innerWidth < 768 ? "auto" : "smooth";
    const nextScrollLeft = Math.min(
      maxScrollLeft,
      Math.max(0, targetScrollLeft),
    );

    const isAlreadyAligned =
      Math.abs(contentLeft.scrollLeft - nextScrollLeft) <=
      AnchorNavigation.SCROLL_ALIGNMENT_TOLERANCE;
    if (!forceCenter && isAlreadyAligned) return;

    if (typeof contentLeft.scrollTo === "function") {
      contentLeft.scrollTo({
        left: nextScrollLeft,
        behavior,
      });
      return;
    }

    contentLeft.scrollLeft = nextScrollLeft;
  }

  private setupDragScroll(): void {
    this.teardownDragScroll();

    this.contentLeftElement = this.element.querySelector(
      ".anchor-navigation__content-left",
    ) as HTMLElement | null;

    if (!this.contentLeftElement) return;

    this.contentLeftElement.addEventListener(
      "mousedown",
      this.dragStartHandler,
    );
    this.contentLeftElement.addEventListener(
      "dragstart",
      this.nativeDragStartHandler,
    );
    this.contentLeftElement.addEventListener(
      "scroll",
      this.horizontalScrollHandler,
      {
        passive: true,
      },
    );
    this.updateDragState();
    this.updateHorizontalOverflowState();
  }

  private teardownDragScroll(): void {
    if (this.contentLeftElement) {
      this.contentLeftElement.removeEventListener(
        "mousedown",
        this.dragStartHandler,
      );
      this.contentLeftElement.removeEventListener(
        "dragstart",
        this.nativeDragStartHandler,
      );
      this.contentLeftElement.removeEventListener(
        "scroll",
        this.horizontalScrollHandler,
      );
      this.contentLeftElement.classList.remove("is-draggable", "is-dragging");
    }

    window.removeEventListener("mousemove", this.dragMoveHandler);
    window.removeEventListener("mouseup", this.dragEndHandler);

    this.contentLeftElement = null;
    this.isPointerDown = false;
    this.isDraggingNav = false;
    this.suppressClick = false;
    this.dragStartX = 0;
    this.dragStartScrollLeft = 0;
  }

  private updateDragState(): void {
    if (!this.contentLeftElement) return;

    const pointerMediaQuery =
      typeof window.matchMedia === "function"
        ? window.matchMedia("(pointer: fine)")
        : null;
    const hasFinePointer = !pointerMediaQuery || pointerMediaQuery.matches;
    const isOverflowing =
      this.contentLeftElement.scrollWidth > this.contentLeftElement.clientWidth;
    const isDraggable = hasFinePointer && isOverflowing;

    this.contentLeftElement.classList.toggle("is-draggable", isDraggable);

    if (!isDraggable) {
      this.contentLeftElement.classList.remove("is-dragging");
      this.isPointerDown = false;
      this.isDraggingNav = false;
      this.suppressClick = false;
      window.removeEventListener("mousemove", this.dragMoveHandler);
      window.removeEventListener("mouseup", this.dragEndHandler);
    }

    this.updateHorizontalOverflowState();
  }

  private updateHorizontalOverflowState(): void {
    if (!this.contentLeftElement) return;

    const contentElement = this.element.querySelector(
      ".anchor-navigation__content",
    ) as HTMLElement | null;
    if (!contentElement) return;

    const maxScrollLeft =
      this.contentLeftElement.scrollWidth - this.contentLeftElement.clientWidth;
    const hasOverflow = maxScrollLeft > AnchorNavigation.SCROLL_EDGE_TOLERANCE;
    const atStart =
      this.contentLeftElement.scrollLeft <=
      AnchorNavigation.SCROLL_EDGE_TOLERANCE;
    const atEnd =
      this.contentLeftElement.scrollLeft >=
      maxScrollLeft - AnchorNavigation.SCROLL_EDGE_TOLERANCE;

    contentElement.classList.toggle(
      "has-left-overflow",
      hasOverflow && !atStart,
    );
    contentElement.classList.toggle(
      "has-right-overflow",
      hasOverflow && !atEnd,
    );
  }

  private handleDragStart(event: MouseEvent): void {
    if (
      event.button !== 0 ||
      !this.contentLeftElement ||
      !this.contentLeftElement.classList.contains("is-draggable")
    ) {
      return;
    }

    this.isPointerDown = true;
    this.isDraggingNav = false;
    this.suppressClick = false;
    this.dragStartX = event.clientX;
    this.dragStartScrollLeft = this.contentLeftElement.scrollLeft;

    window.addEventListener("mousemove", this.dragMoveHandler);
    window.addEventListener("mouseup", this.dragEndHandler);
  }

  private handleDragMove(event: MouseEvent): void {
    if (!this.isPointerDown || !this.contentLeftElement) return;

    const deltaX = event.clientX - this.dragStartX;

    if (
      !this.isDraggingNav &&
      Math.abs(deltaX) >= AnchorNavigation.DRAG_START_THRESHOLD
    ) {
      this.isDraggingNav = true;
      this.suppressClick = true;
      this.contentLeftElement.classList.add("is-dragging");
    }

    if (!this.isDraggingNav) return;

    event.preventDefault();
    this.contentLeftElement.scrollLeft = this.dragStartScrollLeft - deltaX;
  }

  private handleDragEnd(): void {
    if (!this.isPointerDown) return;

    this.isPointerDown = false;
    window.removeEventListener("mousemove", this.dragMoveHandler);
    window.removeEventListener("mouseup", this.dragEndHandler);

    if (this.contentLeftElement) {
      this.contentLeftElement.classList.remove("is-dragging");
    }

    const shouldSuppressClick = this.isDraggingNav;
    this.isDraggingNav = false;

    if (shouldSuppressClick) {
      window.setTimeout(() => {
        this.suppressClick = false;
      }, 0);
    }
  }

  private handleNativeDragStart(event: DragEvent): void {
    if (this.contentLeftElement?.classList.contains("is-draggable")) {
      event.preventDefault();
    }
  }

  private handleScrollEndFallback(): void {
    if (this.scrollEndFallbackTimeout) {
      clearTimeout(this.scrollEndFallbackTimeout);
    }

    this.scrollEndFallbackTimeout = setTimeout(() => {
      this.handleScrollEnd();
    }, AnchorNavigation.SCROLL_END_DEBOUNCE_MS);
  }

  private handleResize(): void {
    if (this.isForcedFixed) {
      const parentRect = this.element.parentElement?.getBoundingClientRect();
      const megamenuHeight = this.getMegamenuOffsetHeight();

      if (parentRect) {
        this.forcedFixedLeft = Math.round(parentRect.left);
        this.element.style.width = `${Math.round(parentRect.width)}px`;
      }

      if (megamenuHeight > 0) {
        this.forcedFixedTop = megamenuHeight;
      }

      this.element.style.top = `${this.forcedFixedTop}px`;
      this.element.style.left = `${this.forcedFixedLeft}px`;

      if (this.fixedFlowPlaceholder) {
        this.fixedFlowPlaceholder.style.height = `${this.element.offsetHeight}px`;
      }
    } else {
      this.updateStickyFloatingState();
    }

    this.initScrollSpy();
    this.updateDragState();
    this.updateHorizontalOverflowState();
  }

  private isAnyModalOpen(): boolean {
    return document.body.classList.contains(
      AnchorNavigation.MODAL_OPEN_BODY_CLASS,
    );
  }

  private isCurrentlyStickyFloating(): boolean {
    if (!this.megamenuElement) return false;

    const stickyTop = this.getMegamenuOffsetHeight();
    const currentTop = this.element.getBoundingClientRect().top;

    return currentTop <= stickyTop + AnchorNavigation.STICKY_FLOAT_TOLERANCE;
  }

  private updateStickyFloatingState(): void {
    if (this.isAnyModalOpen() || this.isForcedFixed) {
      return;
    }

    this.wasStickyFloating = this.isCurrentlyStickyFloating();
  }

  private ensureFixedFlowPlaceholder(): void {
    if (!this.fixedFlowPlaceholder) {
      this.fixedFlowPlaceholder = document.createElement("div");
      this.fixedFlowPlaceholder.setAttribute("aria-hidden", "true");
      this.fixedFlowPlaceholder.style.width = "100%";
      this.fixedFlowPlaceholder.style.pointerEvents = "none";
      this.fixedFlowPlaceholder.style.visibility = "hidden";
      this.element.insertAdjacentElement("afterend", this.fixedFlowPlaceholder);
    }

    this.fixedFlowPlaceholder.style.height = `${this.element.offsetHeight}px`;
  }

  private removeFixedFlowPlaceholder(): void {
    if (!this.fixedFlowPlaceholder) return;

    this.fixedFlowPlaceholder.remove();
    this.fixedFlowPlaceholder = null;
  }

  private applyFixedPosition(): void {
    if (this.isForcedFixed) return;

    const rect = this.element.getBoundingClientRect();
    const parentRect = this.element.parentElement?.getBoundingClientRect();
    const megamenuHeight = this.getMegamenuOffsetHeight();

    this.originalInlineStyles = {
      position: this.element.style.position,
      top: this.element.style.top,
      left: this.element.style.left,
      right: this.element.style.right,
      width: this.element.style.width,
    };

    this.forcedFixedTop =
      megamenuHeight > 0 ? megamenuHeight : Math.max(0, Math.round(rect.top));
    this.forcedFixedLeft = Math.max(
      0,
      Math.round(parentRect?.left ?? rect.left),
    );
    const fixedWidth = Math.round(parentRect?.width ?? rect.width);

    this.element.style.position = "fixed";
    this.element.style.top = `${this.forcedFixedTop}px`;
    this.element.style.left = `${this.forcedFixedLeft}px`;
    this.element.style.right = "";
    this.element.style.width = `${fixedWidth}px`;
    this.ensureFixedFlowPlaceholder();

    this.isForcedFixed = true;
  }

  private restoreStickyPosition(shouldRecalculate: boolean = true): void {
    if (!this.isForcedFixed) return;

    const originalStyles = this.originalInlineStyles;
    this.element.style.position = originalStyles?.position || "";
    this.element.style.top = originalStyles?.top || "";
    this.element.style.left = originalStyles?.left || "";
    this.element.style.right = originalStyles?.right || "";
    this.element.style.width = originalStyles?.width || "";
    this.resetFixedPositionState();

    if (shouldRecalculate) {
      this.updateStickyPosition();
      this.initScrollSpy();
    }
  }

  private handleModalStateChange(): void {
    if (this.isAnyModalOpen()) {
      // Keep sticky in normal flow until it is actually pinned.
      if (this.isForcedFixed || this.wasStickyFloating) {
        this.applyFixedPosition();
      }
      return;
    }

    this.restoreStickyPosition(false);
    this.updateStickyFloatingState();
  }

  private setupModalObserver(): void {
    this.updateStickyFloatingState();
    this.handleModalStateChange();

    if (!document.body || typeof MutationObserver === "undefined") {
      return;
    }

    this.modalClassObserver = new MutationObserver(this.modalStateHandler);
    this.modalClassObserver.observe(document.body, {
      attributes: true,
      attributeFilter: ["class"],
    });
  }

  private teardownModalObserver(): void {
    if (!this.modalClassObserver) return;

    this.modalClassObserver.disconnect();
    this.modalClassObserver = null;
  }

  private teardownMegamenuObserver(): void {
    window.removeEventListener("scroll", this.scrollHandler);

    if (this.resizeObserver) {
      this.resizeObserver.disconnect();
      this.resizeObserver = null;
    }
  }

  private clearAutoScrollTimers(): void {
    if (this.scrollTimeout) {
      clearTimeout(this.scrollTimeout);
      this.scrollTimeout = null;
    }
  }

  private resetFixedPositionState(): void {
    this.originalInlineStyles = null;
    this.isForcedFixed = false;
    this.wasStickyFloating = false;
    this.forcedFixedTop = 0;
    this.forcedFixedLeft = 0;
    this.removeFixedFlowPlaceholder();
  }

  private resetNavigationState(): void {
    this.megamenuElement = null;
    this.lastStickyOffset = 0;
    this.navLinks = null;
    this.sections = [];
    this.lastActiveIndex = 0;
    this.autoScrollTargetId = null;
    this.autoScrollCorrectionCount = 0;
    this.resetFixedPositionState();
  }

  private handleScrollSpy(): void {
    if (this.isAutoScrolling) {
      // Clear existing timeout and set a new one
      if (this.scrollTimeout) {
        clearTimeout(this.scrollTimeout);
      }

      // Set a timeout to detect when scrolling has ended
      this.scrollTimeout = setTimeout(() => {
        this.handleScrollEnd();
      }, AnchorNavigation.SCROLL_END_DEBOUNCE_MS);
    } else {
      this.initScrollSpy();
    }
  }

  private handleScrollEnd(): void {
    if (this.scrollTimeout) {
      clearTimeout(this.scrollTimeout);
      this.scrollTimeout = null;
    }

    if (!this.tryCorrectAutoScrollAlignment()) {
      return;
    }

    this.resetAutoScrollState();
    this.initScrollSpy();
  }

  private init(): void {
    this.setupMegamenuObserver();
    this.setupScrollSpy();
    this.setupDragScroll();
    this.setupModalObserver();
  }

  destroy(): void {
    this.clearAutoScrollTimers();

    this.teardownMegamenuObserver();
    this.teardownScrollSpyListeners();
    this.teardownDragScroll();

    this.teardownModalObserver();

    this.restoreStickyPosition(false);

    this.element.style.top = "";
    this.resetNavigationState();
    (this.element as any).ODS_AnchorNavigation = null;
  }

  update(): void {
    this.clearAutoScrollTimers();

    this.teardownMegamenuObserver();
    this.teardownScrollSpyListeners();
    this.teardownDragScroll();

    this.teardownModalObserver();

    this.restoreStickyPosition(false);
    this.resetNavigationState();

    this.init();
  }
}
