import {
  ElementRef,
  Injectable,
  Renderer2,
  RendererFactory2,
} from '@angular/core';
import { SkyCoreAdapterService, SkyMediaBreakpoints } from '@skyux/core';

const RESPONSIVE_CLASS_XS = 'sky-selection-box-container-xs';
const RESPONSIVE_CLASS_SM = 'sky-selection-box-container-sm';
const RESPONSIVE_CLASS_MD = 'sky-selection-box-container-md';
const RESPONSIVE_CLASS_LG = 'sky-selection-box-container-lg';

const BREAKPOINT_XS_MAX_PIXELS = 767;
const BREAKPOINT_SM_MIN_PIXELS = 768;
const BREAKPOINT_SM_MAX_PIXELS = 991;
const BREAKPOINT_MD_MIN_PIXELS = 992;
const BREAKPOINT_MD_MAX_PIXELS = 1439;

/**
 * @internal
 */
@Injectable()
export class SkySelectionBoxAdapterService {
  #coreAdapterService: SkyCoreAdapterService;
  #renderer: Renderer2;

  constructor(
    coreAdapterService: SkyCoreAdapterService,
    rendererFactory: RendererFactory2,
  ) {
    this.#coreAdapterService = coreAdapterService;
    this.#renderer = rendererFactory.createRenderer(undefined, null);
  }

  /**
   * Sets focus on the specified element.
   */
  public focus(el: ElementRef): void {
    el.nativeElement.focus();
  }

  /**
   * Returns a child element with the `.sky-switch` class.
   * Useful for getting SKY UX-themed radio buttons or checkboxes.
   */
  public getControl(el: ElementRef): HTMLElement {
    return el.nativeElement.querySelector('.sky-switch');
  }

  /**
   * Returns a breakpoint based on the width.
   * @param width Width of the element in pixels.
   */
  public getBreakpointForWidth(width: number | undefined): SkyMediaBreakpoints {
    if (!width) {
      return SkyMediaBreakpoints.lg;
    }

    if (width <= BREAKPOINT_XS_MAX_PIXELS) {
      return SkyMediaBreakpoints.xs;
    } else if (
      width >= BREAKPOINT_SM_MIN_PIXELS &&
      width <= BREAKPOINT_SM_MAX_PIXELS
    ) {
      return SkyMediaBreakpoints.sm;
    } else if (
      width >= BREAKPOINT_MD_MIN_PIXELS &&
      width <= BREAKPOINT_MD_MAX_PIXELS
    ) {
      return SkyMediaBreakpoints.md;
    } else {
      return SkyMediaBreakpoints.lg;
    }
  }

  /**
   * Returns the width of the `parentNode` of the provided `element`.
   */
  public getParentWidth(element: ElementRef): number | undefined {
    return (
      element.nativeElement as HTMLElement | undefined
    )?.parentElement?.getBoundingClientRect()?.width;
  }

  /**
   * Returns `true` if the `childEl` is a descendant of the `parentEl`.
   */
  public isDescendant(parentEl: ElementRef, childEl: HTMLElement): boolean {
    return parentEl.nativeElement.contains(childEl);
  }

  /**
   * Sets the `tabIndex` of all focusable children of the `element` to the provided `tabIndex`.
   */
  public setChildrenTabIndex(element: ElementRef, tabIndex: number): void {
    const el = element.nativeElement;
    const focusableEls = this.#coreAdapterService.getFocusableChildren(el, {
      ignoreVisibility: true,
    });
    let index = focusableEls.length;
    while (index--) {
      focusableEls[index].tabIndex = tabIndex;
    }
  }

  /**
   * Adds a responsive CSS class on the provided element based on its current width.
   */
  public setResponsiveClass(
    element: ElementRef,
    breakpoint: SkyMediaBreakpoints,
  ): void {
    const nativeEl: HTMLElement = element.nativeElement;

    this.#renderer.removeClass(nativeEl, RESPONSIVE_CLASS_XS);
    this.#renderer.removeClass(nativeEl, RESPONSIVE_CLASS_SM);
    this.#renderer.removeClass(nativeEl, RESPONSIVE_CLASS_MD);
    this.#renderer.removeClass(nativeEl, RESPONSIVE_CLASS_LG);

    let newClass: string;

    switch (breakpoint) {
      case SkyMediaBreakpoints.xs: {
        newClass = RESPONSIVE_CLASS_XS;
        break;
      }
      case SkyMediaBreakpoints.sm: {
        newClass = RESPONSIVE_CLASS_SM;
        break;
      }
      case SkyMediaBreakpoints.md: {
        newClass = RESPONSIVE_CLASS_MD;
        break;
      }
      case SkyMediaBreakpoints.lg: {
        newClass = RESPONSIVE_CLASS_LG;
        break;
      }
    }

    this.#renderer.addClass(nativeEl, newClass);
  }

  /**
   * Sets the `tabIndex` of the `element` to the provided `tabIndex`.
   */
  public setTabIndex(element: ElementRef, tabIndex: number): void {
    const el = element.nativeElement;

    el.tabIndex = tabIndex;
  }
}
