import { Component, TemplateFunction } from "@ribajs/core";
import { TouchEventsService, TouchSwipeData } from "@ribajs/extras";
import { EventDispatcher } from "@ribajs/events";
import { TOGGLE_BUTTON } from "../../constants/index.js";
import { Bs5Service } from "../../services/index.js";
import {
  JsxBs5SidebarProps,
  Bs5SidebarComponentScope,
  SidebarState,
  // Breakpoint,
} from "../../types/index.js";
import { hasChildNodesTrim } from "@ribajs/utils/src/dom.js";
import { debounce } from "@ribajs/utils/src/control";

export class Bs5SidebarComponent extends Component {
  public static tagName = "bs5-sidebar";

  public static states = [
    "hidden",
    "side-left",
    "side-right",
    "overlap-left",
    "overlap-right",
    "move-left",
    "move-right",
  ];

  protected computedStyle?: CSSStyleDeclaration;

  protected autobind = true;

  public _debug = false;

  protected bs5: Bs5Service;

  protected touch: TouchEventsService = new TouchEventsService(this);

  static get observedAttributes(): (keyof JsxBs5SidebarProps)[] {
    return [
      "id",
      "container-selector",
      "position",
      "mode",
      "width",
      "auto-show",
      "auto-hide",
      "force-hide-on-location-pathnames",
      "force-show-on-location-pathnames",
      "watch-new-page-ready-event",
      "close-on-swipe",
      "prevent-scrolling-on-overlap",
    ];
  }

  public events?: EventDispatcher;

  protected routerEvents = new EventDispatcher("main");

  public defaults: Bs5SidebarComponentScope = {
    // Template properties
    containerSelector: undefined,
    state: "hidden",
    oldState: "hidden",
    id: undefined,
    width: "250px",

    // Options
    position: "left",
    mode: "overlap",
    autoShow: false,
    autoHide: false,
    watchNewPageReadyEvent: true,
    forceHideOnLocationPathnames: [],
    forceShowOnLocationPathnames: [],
    closeOnSwipe: true,
    preventScrollingOnOverlap: true,

    // Template methods
    hide: this.hide,
    show: this.show,
    toggle: this.toggle,
  };

  public scope: Bs5SidebarComponentScope = {
    ...this.defaults,
  };

  constructor() {
    super();
    this.bs5 = Bs5Service.getSingleton();
    // assign this to bound version, so we can remove window EventListener later without problem
    this.onEnvironmentChanges = this.onEnvironmentChanges.bind(this);
  }

  public setState(state: SidebarState) {
    this.scope.oldState = this.scope.state;
    this.scope.state = state;
    this.onStateChange();
  }

  public getState() {
    return this.scope.state;
  }

  public getShowMode() {
    const mode: SidebarState =
      `${this.scope.mode}-${this.scope.position}` as SidebarState;
    return mode;
  }

  public hide() {
    this.setState("hidden");
  }

  public show() {
    const state = this.getShowMode();
    this.setState(state);
  }

  public toggle() {
    this.debug("toggle state: " + this.scope.state);
    if (this.scope.state === "hidden") {
      this.show();
    } else {
      this.hide();
    }
    this.debug("toggled state: " + this.scope.state);
  }

  protected preventScrolling(scrollEl = document.body) {
    scrollEl.style.overflow = "hidden";
  }

  protected allowScrolling(scrollEl = document.body) {
    if (scrollEl.style.overflow === "hidden") {
      scrollEl.style.overflow = "";
    }
  }

  protected connectedCallback() {
    super.connectedCallback();
    this.init(Bs5SidebarComponent.observedAttributes);
    this.computedStyle = window.getComputedStyle(this);
    this.addEventListeners();
    // initial
    this.onEnvironmentChanges();
  }

  protected onBreakpoint(/*breakpoint: Breakpoint | null*/) {
    this.onEnvironmentChanges();
  }

  protected addEventListeners() {
    // window.addEventListener("resize", this.onEnvironmentChanges, {
    //   passive: true,
    // });
    this.addEventListener("swipe" as any, this.onSwipe);
    this.bs5.events.on("breakpoint:changed", this.onBreakpoint, this);
  }

  protected removeEventListeners() {
    this.events?.off(TOGGLE_BUTTON.eventNames.init, this.triggerState, this);
    this.events?.off(TOGGLE_BUTTON.eventNames.toggle, this.toggle, this);
    this.routerEvents.off("newPageReady", this.onEnvironmentChanges, this);
    // window.removeEventListener("resize", this.onEnvironmentChanges);
    this.bs5.events.off("breakpoint:changed", this.onBreakpoint, this);
  }

  protected initToggleButtonEventDispatcher() {
    if (this.events) {
      this.events.off(TOGGLE_BUTTON.eventNames.toggle, this.toggle, this);
      this.events.off(TOGGLE_BUTTON.eventNames.init, this.triggerState, this);
    }
    const namespace = TOGGLE_BUTTON.nsPrefix + this.scope.id;
    this.debug(`Init event dispatcher for namespace  ${namespace}`);
    this.events = new EventDispatcher(namespace);
    this.events.on(TOGGLE_BUTTON.eventNames.toggle, this.toggle, this);
    this.events.on(TOGGLE_BUTTON.eventNames.init, this.triggerState, this);
  }

  protected initRouterEventDispatcher() {
    if (this.scope.watchNewPageReadyEvent) {
      this.routerEvents.on("newPageReady", this.onEnvironmentChanges, this);
    }
  }

  protected _onSwipe(event: CustomEvent<TouchSwipeData>) {
    if (!this.scope.closeOnSwipe) {
      return;
    }
    if (this.scope.state === "side-left" || this.scope.state === "side-right") {
      return;
    }
    if (this.scope.position === "left" && event.detail.direction === "left") {
      this.hide();
    }

    if (this.scope.position === "right" && event.detail.direction === "right") {
      this.hide();
    }
  }

  protected onSwipe = this._onSwipe.bind(this);

  protected onHidden() {
    const translateX = this.scope.position === "left" ? "-100%" : "100%";
    this.style.transform = `translateX(${translateX})`;
    this.width = this.scope.width;
    this.setContainersStyle(this.scope.state);
    if (this.scope.preventScrollingOnOverlap) {
      this.allowScrolling();
    }
  }

  protected onMove(state: SidebarState) {
    this.style.transform = `translateX(0)`;
    this.style.width = this.scope.width;
    this.width = this.scope.width;
    this.setContainersStyle(state);
    if (this.scope.preventScrollingOnOverlap) {
      this.allowScrolling();
    }
  }

  protected onSide(state: SidebarState) {
    this.style.transform = `translateX(0)`;
    this.style.width = this.scope.width;
    this.width = this.scope.width;
    this.setContainersStyle(state);
    if (this.scope.preventScrollingOnOverlap) {
      this.allowScrolling();
    }
  }

  protected onOverlap(state: SidebarState) {
    this.style.transform = `translateX(0)`;
    this.width = this.scope.width;
    this.setContainersStyle(state);
    if (this.scope.preventScrollingOnOverlap) {
      this.preventScrolling();
    }
  }

  protected triggerState() {
    // Global event
    this.events?.trigger("state", this.scope.state);
  }

  protected onStateChange() {
    switch (this.scope.state) {
      case "side-left":
      case "side-right":
        this.onSide(this.scope.state);
        break;
      case "overlap-left":
      case "overlap-right":
        this.onOverlap(this.scope.state);
        break;
      case "move-left":
      case "move-right":
        this.onMove(this.scope.state);
        break;
      default:
        this.onHidden();
        break;
    }

    this.classList.remove(...Bs5SidebarComponent.states);
    this.classList.add(this.scope.state);

    if (this.events) {
      this.events.trigger(TOGGLE_BUTTON.eventNames.toggled, this.scope.state);
    }
    this.dispatchEvent(
      new CustomEvent(TOGGLE_BUTTON.eventNames.toggled, {
        detail: this.scope.state,
      }),
    );
  }

  protected get width() {
    if (this.scope.width === this.defaults.width) {
      return this.offsetWidth + "px";
    }
    return this.scope.width;
  }

  protected set width(width: string) {
    this.scope.width = width;
    this.style.width = width;
  }

  protected setStateByEnvironment() {
    if (
      this.scope.forceHideOnLocationPathnames.includes(window.location.pathname)
    ) {
      return this.hide();
    }
    if (
      this.scope.forceShowOnLocationPathnames.includes(window.location.pathname)
    ) {
      return this.show();
    }
    if (this.scope.autoHide) {
      return this.hide();
    }
    if (this.scope.autoShow) {
      return this.show();
    }
  }

  /**
   * Internal (not debounced) version of `onEnvironmentChanges`.
   */
  protected _onEnvironmentChanges() {
    this.setStateByEnvironment();
  }

  /**
   * If viewport size changes, location url changes or something else.
   */
  protected onEnvironmentChanges = debounce(
    this._onEnvironmentChanges.bind(this),
  );

  protected getContainers() {
    return this.scope.containerSelector
      ? document.querySelectorAll<HTMLUnknownElement>(
          this.scope.containerSelector,
        )
      : undefined;
  }

  protected initContainers(state: SidebarState) {
    this.setContainersStyle(state);
  }

  protected setContainersStyle(state: SidebarState) {
    const containers:
      | NodeListOf<HTMLUnknownElement>
      | Array<HTMLUnknownElement> = this.getContainers() || [];

    if (containers) {
      for (let i = 0; i < containers.length; i++) {
        const container = containers[i];
        this.setContainerStyle(container, state);
      }
    }
  }

  /**
   * Sets the container style, takes overs always the transition style of this sidebar
   * @param container
   * @param style
   * @param state
   */
  protected setContainerStyle(
    container: HTMLUnknownElement,
    state: SidebarState,
  ) {
    const currStyle = container.style;
    if (state) {
      const width = this.width;
      const conStyle = window.getComputedStyle(container);

      if (this.scope.mode === "move" && state.startsWith("overlap-")) {
        switch (conStyle.position) {
          case "fixed":
          case "absolute":
            currStyle.left = "0";
            currStyle.right = "0";
            break;
          default:
            currStyle.marginLeft = "0";
            currStyle.marginRight = "0";
            break;
        }
      }

      switch (state) {
        case "side-left":
          switch (conStyle.position) {
            case "fixed":
            case "absolute":
              currStyle.left = width;
              break;
            default:
              currStyle.marginLeft = width;
              break;
          }
          break;
        case "side-right":
          switch (conStyle.position) {
            case "fixed":
            case "absolute":
              currStyle.right = width;
              break;
            default:
              currStyle.marginRight = width;
              break;
          }
          break;
        case "move-left":
          switch (conStyle.position) {
            case "fixed":
            case "absolute":
              currStyle.left = width;
              currStyle.right = `-${width}`;
              break;
            default:
              currStyle.marginLeft = width;
              currStyle.marginRight = `-${width}`;
              break;
          }
          break;
        case "move-right":
          switch (conStyle.position) {
            case "fixed":
            case "absolute":
              currStyle.right = width;
              currStyle.left = `-${width}`;
              break;
            default:
              currStyle.marginRight = width;
              currStyle.marginLeft = `-${width}`;
              break;
          }
          break;
        case "hidden":
          switch (this.scope.oldState) {
            case "side-left":
              switch (conStyle.position) {
                case "fixed":
                case "absolute":
                  currStyle.left = "0";
                  break;
                default:
                  currStyle.marginLeft = "0";
                  break;
              }
              break;
            case "side-right":
              switch (conStyle.position) {
                case "fixed":
                case "absolute":
                  currStyle.right = "0";
                  break;
                default:
                  currStyle.marginRight = "0";
                  break;
              }
              break;
            case "move-left":
              switch (conStyle.position) {
                case "fixed":
                case "absolute":
                  currStyle.left = "0";
                  currStyle.right = "0";
                  break;
                default:
                  currStyle.marginRight = "0";
                  currStyle.marginLeft = "0";
                  break;
              }
              break;
            case "move-right":
              switch (conStyle.position) {
                case "fixed":
                case "absolute":
                  currStyle.right = "0";
                  currStyle.left = "0";
                  break;
                default:
                  currStyle.marginRight = "0";
                  currStyle.marginLeft = "0";
                  break;
              }
              break;
            default:
              break;
          }

        default:
          break;
      }
    }
    container.style.transition = this.computedStyle
      ? this.computedStyle.transition
      : "";
  }

  protected async beforeBind() {
    await super.beforeBind();
    this.scope.oldState = this.getShowMode();
    this.initRouterEventDispatcher();
    return this.onEnvironmentChanges();
  }

  protected async afterBind() {
    this.onEnvironmentChanges();
    await super.afterBind();
  }

  protected requiredAttributes(): string[] {
    return ["id"];
  }

  protected parsedAttributeChangedCallback(
    attributeName: string,
    oldValue: any,
    newValue: any,
    namespace: string | null,
  ) {
    super.parsedAttributeChangedCallback(
      attributeName,
      oldValue,
      newValue,
      namespace,
    );
    if (attributeName === "containerSelector") {
    }
    if (attributeName === "id") {
      this.initToggleButtonEventDispatcher();
    }
    switch (attributeName) {
      case "containerSelector":
        this.initContainers(this.scope.state);
        break;
      case "id":
        this.initToggleButtonEventDispatcher();
        break;
      case "width":
        this.width = newValue;
      case "mode":
        this.onStateChange();
        this.initContainers(this.scope.state);
        break;
      case "autoHide":
      case "autoShow":
        this.setStateByEnvironment();
        break;
      default:
        break;
    }
  }

  // deconstruction
  protected disconnectedCallback() {
    super.disconnectedCallback();
    this.removeEventListeners();
  }

  protected template(): ReturnType<TemplateFunction> {
    if (!hasChildNodesTrim(this)) {
      console.warn(
        "No child elements found, this component as no template so you need to define your own as child of this component.",
      );
    }
    return null;
  }
}
