/*
 * 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 {AfterContentInit, ChangeDetectionStrategy, Component, Input, ViewContainerRef} from '@angular/core';
import {SCThings} from '@openstapps/core';
import {fromMutationObserver} from './rxjs/mutation-observer';
import {combineLatestWith, mergeMap, OperatorFunction, ReplaySubject, takeLast} from 'rxjs';
import {distinctUntilChanged, filter, map, startWith} from 'rxjs/operators';
import {fromIntersectionObserver} from './rxjs/from-intersection-observer';

/**
 * Operator function that checks if a slide is visible
 */
function isSlideVisible(
  select: (slides: HTMLCollection) => Element | null,
): OperatorFunction<readonly [HTMLElement, HTMLCollection], boolean> {
  return source =>
    source.pipe(
      map(([element, slides]) => [element, select(slides) as HTMLElement]),
      filter(([, slide]) => slide !== null),
      mergeMap(([element, slide]) => fromIntersectionObserver(slide, {threshold: 1, root: element})),
      map(entry => entry.some(it => it.intersectionRatio === 1)),
    );
}

/**
 * Shows a horizontal list of action chips
 */
@Component({
  selector: 'stapps-section',
  templateUrl: 'section.html',
  styleUrl: 'section.scss',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SectionComponent implements AfterContentInit {
  @Input() title = '';

  @Input() item?: SCThings;

  @Input() buttonColor = 'medium';

  nativeElement = new ReplaySubject<HTMLElement>(1);

  /**
   * The swiper child (may not emit at all)
   */
  swiper = this.nativeElement.pipe(
    takeLast(1),
    mergeMap(element =>
      fromMutationObserver(element, {
        childList: true,
        subtree: true,
      }).pipe(
        startWith([]),
        map(() => element.querySelector('simple-swiper') as HTMLElement),
        distinctUntilChanged(),
        filter(element => !!element),
      ),
    ),
  );

  /**
   * Emits the current list of all slides
   */
  slides = this.swiper.pipe(
    mergeMap(element =>
      fromMutationObserver(element, {
        childList: true,
      }).pipe(
        map(() => element.children),
        startWith(element.children),
        map(it => [element, it] as const),
      ),
    ),
  );

  /**
   * Emits true when the first slide is fully visible,
   * false when it's only partially or not visible
   */
  firstSlideVisible = this.slides.pipe(isSlideVisible(slides => slides.item(0)));

  /**
   * Emits true when the last slide is fully visible,
   * false when it's only partially or not visible
   */
  lastSlideVisible = this.slides.pipe(isSlideVisible(slides => slides.item(slides.length - 1)));

  /**
   * If the nav should be shown
   *
   * Emits false if all slides are visible
   */
  showNav = this.slides.pipe(
    map(([, slides]) => slides.length > 1),
    combineLatestWith(this.firstSlideVisible, this.lastSlideVisible),
    map(([multipleSlides, firstVisible, lastVisible]) => multipleSlides && !(firstVisible && lastVisible)),
  );

  constructor(readonly viewContainerRef: ViewContainerRef) {}

  ngAfterContentInit() {
    this.nativeElement.next(this.viewContainerRef.element.nativeElement);
    this.nativeElement.complete();
  }
}
