import { Component, ScopeBase } from "@ribajs/core";
import {
  ShopifyProductVariant,
  ShopifyProduct,
  ShopifyProductVariantOption,
  ShopifyCartService,
  ShopifyProductService,
} from "@ribajs/shopify";
import { hasChildNodesTrim } from "@ribajs/utils/src/dom.js";

export interface Scope extends ScopeBase {
  handle: string | null;
  product: ShopifyProduct | null;
  variant: ShopifyProductVariant | null;
  quantity: number;
  showDetailMenu: boolean;
  detailMenuPadding: string;
  // showAddToCartButton: boolean;
  chooseOption: ShopifyProductItemComponent["chooseOption"];
  addToCart: ShopifyProductItemComponent["addToCart"];
  toggleDetailMenu: ShopifyProductItemComponent["toggleDetailMenu"];
  decrease: ShopifyProductItemComponent["decrease"];
  increase: ShopifyProductItemComponent["increase"];
  colorOption: ShopifyProductVariantOption | null;
  sizeOption: ShopifyProductVariantOption | null;
  available: boolean;
}

/**
 * TODO minify this, create a general product service instead of extend from ShopifyProductItemComponent
 * or create a product list for all products
 * or just get the attributes we need like the options
 * or render the most with liquid
 */
export class ShopifyProductItemComponent extends Component {
  public static tagName = "shopify-product-item";

  protected autobind = true;

  /**
   * handle is the product handle to get the product json object
   * extras are product data which is only available over liquid and not over the product json object
   * product is the product object itself if you want to pass it directly
   */
  static get observedAttributes(): string[] {
    return ["handle", "extras", "product"];
  }

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

  public scope: Scope = {
    handle: null,
    product: null,
    variant: null,
    quantity: 1,
    showDetailMenu: false,
    detailMenuPadding: "60px",
    // showAddToCartButton: false,
    chooseOption: this.chooseOption,
    addToCart: this.addToCart,
    toggleDetailMenu: this.toggleDetailMenu,
    decrease: this.decrease,
    increase: this.increase,
    colorOption: null,
    sizeOption: null,
    /**
     * If the variant is available, used to disable the add to cart button
     */
    available: false,
  };

  /**
   * Array with all selected product options
   */
  private selectedOptions: string[] = [];

  /**
   * Number of detail menü padding without px
   */
  private _menuPadding = 60;

  /**
   * Is true if the user has chosen an option
   */
  private optionChosen = false;

  protected set menuPadding(padding: number) {
    this._menuPadding = padding;
    this.scope.detailMenuPadding = this._menuPadding + "px";
  }

  /**
   * available is only true if the variant is available and the user has clicked on an option
   */
  protected set available(available: boolean) {
    this.scope.available = available && this.optionChosen;
  }

  protected set showMenu(show: boolean) {
    if (show) {
      this.menuPadding = 215;
    } else {
      this.menuPadding = 60;
    }
    this.scope.showDetailMenu = show;
  }

  protected get showMenu() {
    return this.scope.showDetailMenu;
  }

  protected set product(product: ShopifyProduct | null) {
    if (product) {
      this.scope.product = ShopifyProductService.prepare(product);

      this.scope.handle = this.scope.product.handle;

      this.scope.colorOption =
        ShopifyProductService.getOption(this.scope.product, "color") || null;
      this.scope.sizeOption =
        ShopifyProductService.getOption(this.scope.product, "size") || null;

      // set the first variant to the selected one
      this.variant = this.scope.product.variants[0];
    }
  }

  protected get product(): ShopifyProduct | null {
    return this.scope.product;
  }

  protected set variant(variant: ShopifyProductVariant | null) {
    if (variant === null) {
      return;
    }
    this.scope.variant = variant;
    if (this.scope.variant) {
      this.selectedOptions = this.scope.variant.options.slice();
      this.available = this.scope.variant.available;
      this.activateOptions();
    }
  }

  protected get variant() {
    return this.scope.variant;
  }

  constructor() {
    super();
  }

  protected connectedCallback() {
    super.connectedCallback();
    this.init(ShopifyProductItemComponent.observedAttributes);
  }

  public chooseOption(
    optionValue: string | number,
    position1: number,
    optionName: string,
    event: MouseEvent,
  ) {
    optionValue = optionValue.toString();

    if (!this.scope.product) {
      throw new Error("Product not set!");
    }

    this.selectedOptions[position1 - 1] = optionValue.toString();
    const variant = ShopifyProductService.getVariantOfOptions(
      this.scope.product,
      this.selectedOptions,
    );
    if (variant) {
      // Option chosen -> so enable add to cart button
      this.optionChosen = true;

      this.variant = variant as ShopifyProductVariant;
    }

    event.stopPropagation();
  }

  public addToCart() {
    if (!this.variant) {
      return;
    }
    ShopifyCartService.add(this.variant.id, this.scope.quantity)
      .then((response: any /** TODO not any */) => {
        console.debug("addToCart response", response);
      })
      .catch((error: Error) => {
        console.debug("addToCart error", error);
      });
  }

  public toggleDetailMenu() {
    this.showMenu = !this.showMenu;
  }

  public increase() {
    this.scope.quantity++;
  }

  public decrease() {
    this.scope.quantity--;
    if (this.scope.quantity <= 0) {
      this.scope.quantity = 1;
    }
  }

  /**
   * Workaround because `rv-class-active="isOptionActive | call size"` is not updating if selectedOptions changes
   * @param optionValue
   * @param optionName
   */
  protected activateOption(optionValue: string, optionName: string) {
    optionValue = optionValue.toString().replace("#", "");
    this.querySelector<HTMLElement>(
      `.option-${optionName.toLocaleLowerCase()}`,
    )?.classList.remove("active");
    this.querySelector<HTMLElement>(
      `.option-${optionName.toLocaleLowerCase()}-${optionValue}`,
    )?.classList.add("active");
  }

  /**
   * Activate option by selected options (scope.selectedOptions)
   * This method sets the active class to the options elements
   */
  protected activateOptions() {
    for (const position0 in this.selectedOptions) {
      if (this.selectedOptions[position0]) {
        const optionValue = this.selectedOptions[position0];
        if (this.scope.product) {
          const optionName = this.scope.product.options[position0].name;
          // Only activate size if it was clicked by the user
          if (optionName === "size") {
            if (this.optionChosen) {
              this.activateOption(optionValue, optionName);
            }
          } else {
            this.activateOption(optionValue, optionName);
          }
        }
      }
    }
  }

  protected async beforeBind() {
    await super.beforeBind();
    if (this.scope.handle === null) {
      throw new Error("Product handle not set");
    }
    if (!this.product) {
      this.fetchProduct(this.scope.handle);
    }
  }

  protected async fetchProduct(handle: string) {
    const product = await ShopifyProductService.get(handle);
    if (product) {
      this.product = product;
    }
    return product;
  }

  protected parsedAttributeChangedCallback(
    attributeName: string,
    oldValue: any,
    newValue: any,
    namespace: string | null,
  ) {
    super.parsedAttributeChangedCallback(
      attributeName,
      oldValue,
      newValue,
      namespace,
    );
    switch (attributeName) {
      case "product":
        this.product = newValue;
        break;
    }
  }

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

  protected async template() {
    // Only set the component template if there no childs already
    if (this && hasChildNodesTrim(this)) {
      return null;
    } else {
      const { default: template } =
        await import("./product-item.component.html?raw");
      return template;
    }
  }
}
