/*
 * Copyright (C) 2023 StApps
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <https://www.gnu.org/licenses/>.
 */
import {Directive, ElementRef, HostBinding, Input, OnDestroy, OnInit} from '@angular/core';
import {waitForElement} from './ion-icon/shadow-attacher';

type IonicColor =
  | 'danger'
  | 'dark'
  | 'light'
  | 'medium'
  | 'primary'
  | 'secondary'
  | 'success'
  | 'tertiary'
  | 'warning'
  | string;

/**
 * Adds a parallax effect to `<ion-content/>` elements
 *
 * Why is this necessary and why do we need to inject
 * elements into a foreign element's shadow DOM just for this?
 *
 * We previously had used a global style in conjunction with
 * adding a child `<div/>`, however, unfortunately Safari 16.4
 * broke 3D transforms where the parent is inside the shadow
 * DOM and child elements are outside.
 *
 * We also have to use `<ion-content/>` as ionic relies on the
 * `<ion-content/>` selector to make iOS animations work.
 *
 * Which means this is pretty much the only way to solve this
 * problem.
 *
 * This directive, when applied using `<ion-content parallax/>`,
 * will transform its shadow DOM from
 *
 * ```html
 * <div id="background-content" part="background"></div>
 * <main class="inner-scroll scroll-y" part="scroll">
 *   <slot></slot>
 * </main>
 * <slot name="fixed"><slot />
 * ```
 *
 * To
 *
 * ```html
 * <div id="background-content" part="background"></div>
 * <main class="inner-scroll scroll-y" part="scroll parallax-scroll">
 *   <div part="parallax-parent">
 *     <div part="parallax"></div>
 *     <slot></slot>
 *   </div>
 * </main>
 * <slot name="fixed"><slot />
 * ```
 *
 * Which we can then be used through `::part()` to style it from
 * a global style sheet.
 */
@Directive({
  selector: 'ion-content[parallax]',
})
export class IonContentParallaxDirective implements OnInit, OnDestroy {
  @HostBinding('style.--parallax-content-size.px') @Input() parallaxSize = 230;

  @Input() set parallaxColor(value: IonicColor) {
    this.parallaxBackground = `var(--ion-color-${value})`;
  }

  @HostBinding('style.--parallax-background') parallaxBackground?: string;

  private mutationObserver: MutationObserver;

  constructor(private element: ElementRef) {}

  async ngOnInit() {
    this.mutationObserver = new MutationObserver(this.replace.bind(this));
    const element = this.element.nativeElement;
    if (element.shadowRoot) {
      this.mutationObserver.observe(element.shadowRoot, {childList: true});
    } else {
      console.warn("Shadow root didn't exist for parallax, retrying...");
      await waitForElement(() => this.element.nativeElement.shadowRoot);
      this.mutationObserver.observe(element.shadowRoot, {childList: true});
      this.replace();
    }
  }

  ngOnDestroy() {
    this.mutationObserver.disconnect();
  }

  replace() {
    const inner = this.element.nativeElement.shadowRoot.querySelector('.inner-scroll') as
      | HTMLDivElement
      | undefined;
    if (!inner) return;

    // eslint-disable-next-line unicorn/no-array-for-each
    inner.childNodes.forEach(node => node.remove());

    inner.part.add('parallax-scroll');
    const parallaxParentElement = document.createElement('div');
    parallaxParentElement.part.add('parallax-parent');
    const parallaxElement = document.createElement('div');
    parallaxElement.part.add('parallax');

    inner.append(parallaxParentElement);
    parallaxParentElement.append(parallaxElement, document.createElement('slot'));
  }
}
