import {
  ElementRef,
  inject,
  Injectable
} from '@angular/core';
import {
  Overlay,
  OverlayRef,
  ConnectedPosition
} from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import {
  Subscription,
  filter,
  fromEvent
} from 'rxjs';
import { ProfileCardComponent } from './profile-card.component';
import { ProfileCardMobileComponent } from './profile-card-mobile.component';
import { PROFILE_AVATAR_EDITOR_OVERLAY_CLASS } from '../../../domains/profile-avatar';
import { ViewportService } from '../../../core/platform';
import { User } from '../../../shared-kernel';

export type ProfileCardPlacement = 'above' | 'left' | 'auto';

interface ProfileCardOptions {
  editable?: boolean;
  placement?: ProfileCardPlacement;
}

const GAP = 10;
const VIEWPORT_MARGIN = 8;

function positionsFor(placement: ProfileCardPlacement): ConnectedPosition[] {
  switch (placement) {
    case 'above':
      return [
        { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -GAP },
        { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: GAP },
        { originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom', offsetY: -GAP },
        { originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', offsetY: GAP }
      ];
    case 'left':
      return [
        { originX: 'start', originY: 'top', overlayX: 'end', overlayY: 'top', offsetX: -GAP },
        { originX: 'end', originY: 'top', overlayX: 'start', overlayY: 'top', offsetX: GAP },
        { originX: 'start', originY: 'bottom', overlayX: 'end', overlayY: 'bottom', offsetX: -GAP },
        { originX: 'end', originY: 'bottom', overlayX: 'start', overlayY: 'bottom', offsetX: GAP }
      ];
    default: // 'auto'
      return [
        { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -GAP },
        { originX: 'end', originY: 'top', overlayX: 'start', overlayY: 'top', offsetX: GAP },
        { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: GAP },
        { originX: 'start', originY: 'top', overlayX: 'end', overlayY: 'top', offsetX: -GAP }
      ];
  }
}

@Injectable({ providedIn: 'root' })
export class ProfileCardService {
  private readonly overlay = inject(Overlay);
  private readonly viewport = inject(ViewportService);
  private overlayRef: OverlayRef | null = null;
  private currentOrigin: HTMLElement | null = null;
  private outsideClickSub: Subscription | null = null;
  private scrollBlocker: (() => void) | null = null;

  open(origin: ElementRef | HTMLElement, user: User, options: ProfileCardOptions = {}): void {
    const rawEl = origin instanceof ElementRef ? origin.nativeElement : origin;

    if (this.overlayRef) {
      const sameOrigin = rawEl === this.currentOrigin;

      this.close();

      if (sameOrigin)
        return;
    }

    const elementRef = origin instanceof ElementRef ? origin : new ElementRef(origin);
    const placement = options.placement ?? 'auto';
    const isMobile = this.viewport.isMobile();

    this.currentOrigin = rawEl;

    if (isMobile) {
      const positionStrategy = this.overlay
        .position()
        .global()
        .left('0')
        .right('0')
        .bottom('0');

      this.overlayRef = this.overlay.create({
        positionStrategy,
        scrollStrategy: this.overlay.scrollStrategies.block(),
        hasBackdrop: true,
        backdropClass: 'cdk-overlay-dark-backdrop',
        panelClass: 'metoyou-bottom-sheet-panel'
      });
    } else {
      const positionStrategy = this.overlay
        .position()
        .flexibleConnectedTo(elementRef)
        .withPositions(positionsFor(placement))
        .withViewportMargin(VIEWPORT_MARGIN)
        .withPush(true);

      this.overlayRef = this.overlay.create({
        positionStrategy,
        scrollStrategy: this.overlay.scrollStrategies.noop()
      });
    }

    this.syncThemeVars();

    if (isMobile) {
      const portal = new ComponentPortal(ProfileCardMobileComponent);
      const ref = this.overlayRef.attach(portal);

      ref.instance.user.set(user);
      ref.instance.editable.set(options.editable ?? false);

      const subscription = new Subscription();

      subscription.add(ref.instance.closed.subscribe(() => this.close()));
      subscription.add(this.overlayRef.backdropClick().subscribe(() => this.close()));
      this.outsideClickSub = subscription;

      return;
    }

    const portal = new ComponentPortal(ProfileCardComponent);
    const ref = this.overlayRef.attach(portal);

    ref.instance.user.set(user);
    ref.instance.editable.set(options.editable ?? false);

    this.outsideClickSub = fromEvent<PointerEvent>(document, 'pointerdown')
      .pipe(
        filter((event) => {
          const target = event.target as Node;
          const targetElement = target instanceof Element ? target : null;

          if (this.overlayRef?.overlayElement.contains(target))
            return false;

          if (this.currentOrigin?.contains(target))
            return false;

          if (targetElement?.closest(`.${PROFILE_AVATAR_EDITOR_OVERLAY_CLASS}`))
            return false;

          return true;
        })
      )
      .subscribe(() => this.close());

    this.blockScroll();
  }

  close(): void {
    this.scrollBlocker?.();
    this.scrollBlocker = null;
    this.outsideClickSub?.unsubscribe();
    this.outsideClickSub = null;

    if (this.overlayRef) {
      this.overlayRef.dispose();
      this.overlayRef = null;
      this.currentOrigin = null;
    }
  }

  private blockScroll(): void {
    const handler = (event: Event): void => {
      if (this.overlayRef?.overlayElement.contains(event.target as Node))
        return;

      const targetElement = event.target instanceof Element ? event.target : null;

      if (targetElement?.closest(`.${PROFILE_AVATAR_EDITOR_OVERLAY_CLASS}`))
        return;

      event.preventDefault();
    };
    const opts: AddEventListenerOptions = { passive: false, capture: true };

    document.addEventListener('wheel', handler, opts);
    document.addEventListener('touchmove', handler, opts);

    this.scrollBlocker = () => {
      document.removeEventListener('wheel', handler, opts);
      document.removeEventListener('touchmove', handler, opts);
    };
  }

  private syncThemeVars(): void {
    const appRoot = document.querySelector<HTMLElement>('[data-theme-key="appRoot"]');
    const container = document.querySelector<HTMLElement>('.cdk-overlay-container');

    if (!appRoot || !container)
      return;

    for (const prop of Array.from(appRoot.style)) {
      if (prop.startsWith('--')) {
        container.style.setProperty(prop, appRoot.style.getPropertyValue(prop));
      }
    }
  }
}
