/**
 * KeepAlive service to handle widget disappearing due to external JS files
 * This service helps ensure that the widget remains visible even if other scripts
 * accidentally remove it from the DOM.
 */
import { Debug } from '../debug';

interface KeepAliveOptions {
  targetSelector: string;
  onDisappear: (element: HTMLElement | null) => void;
  maxAttempts?: number;
  checkInterval?: number;
}

export class KeepAlive {
  private targetSelector: string;
  private onDisappear: (element: HTMLElement | null) => void;
  private maxAttempts: number;
  private checkInterval: number;
  private attempts: number = 0;
  private observer: MutationObserver | null = null;
  private intervalId: number | null = null;
  private originalHTML: string | null = null;
  private originalElement: HTMLElement | null = null;

  /**
   * Creates a KeepAlive instance
   *
   * @param options Configuration options
   * @param options.targetSelector CSS selector for the target element to watch
   * @param options.onDisappear Callback to execute when target disappears
   * @param options.maxAttempts Maximum number of recovery attempts (default: 10)
   * @param options.checkInterval Interval in ms to check element visibility (default: 500)
   */
  constructor(options: KeepAliveOptions) {
    this.targetSelector = options.targetSelector;
    this.onDisappear = options.onDisappear;
    this.maxAttempts = options.maxAttempts || 10;
    this.checkInterval = options.checkInterval || 500;
  }

  /**
   * Starts monitoring the target element
   * This combines both mutation observation and interval checking
   */
  public start(): void {
    Debug.log('Starting keep-alive monitoring');
    this.originalElement = document.querySelector<HTMLElement>(this.targetSelector);

    if (!this.originalElement) {
      Debug.error('KeepAlive: Target element not found');
      return;
    }

    // Store original state
    this.originalHTML = this.originalElement.innerHTML;

    // Set up mutation observer
    this.setupMutationObserver();

    // Start interval check as a backup detection method
    this.startIntervalCheck();
  }

  /**
   * Stops all monitoring
   */
  public stop(): void {
    Debug.log('Stopping keep-alive monitoring');
    if (this.observer) {
      this.observer.disconnect();
      this.observer = null;
    }

    if (this.intervalId !== null) {
      window.clearInterval(this.intervalId);
      this.intervalId = null;
    }
  }

  /**
   * Set up mutation observer to detect DOM changes, scoped to the parent of the target element.
   */
  private setupMutationObserver(): void {
    if (!this.originalElement) return;
    // Observe the parent, if not present observe the html element
    const observeTarget = this.originalElement.parentNode || document.documentElement;

    this.observer = new MutationObserver(() => {
      const targetElement = document.querySelector<HTMLElement>(this.targetSelector);
      // If our element was removed, trigger recovery logic
      if (!targetElement || !document.body.contains(targetElement)) {
        Debug.log('Widget element removed from DOM by mutation', { targetElement });
        this.handleDisappearance(targetElement);
      }
    });

    // Observe only direct children of the parent to minimize performance impact
    this.observer.observe(observeTarget, {
      childList: true,
      subtree: false,
      attributes: false,
      characterData: false,
    });
  }

  /**
   * Start interval checking as a backup mechanism after several consecutive failed checks
   */
  private startIntervalCheck(): void {
    let consecutiveMisses = 0;
    const REQUIRED_MISSES = 3;
    this.intervalId = window.setInterval(() => {
      const targetElement = document.querySelector<HTMLElement>(this.targetSelector);
      const missing = !targetElement || !document.body.contains(targetElement);
      if (missing) {
        consecutiveMisses += 1;
        if (consecutiveMisses >= REQUIRED_MISSES) {
          consecutiveMisses = 0;
          Debug.log('Widget element not found during interval check');
          this.handleDisappearance(targetElement);
        }
      } else {
        consecutiveMisses = 0;
      }
    }, this.checkInterval);
  }

  /**
   * Handle when element disappears
   */
  private handleDisappearance(currentElement: HTMLElement | null): void {
    if (this.attempts >= this.maxAttempts) {
      Debug.log('KeepAlive: Max recovery attempts reached');
      this.stop();
      return;
    }

    this.attempts++;
    Debug.log(`KeepAlive: Attempting recovery (${this.attempts}/${this.maxAttempts})`);

    try {
      this.onDisappear(currentElement);
    } catch (error) {
      Debug.error('Error in onDisappear callback', error);
    }
  }
}

export default KeepAlive;
