import type { ComponentInterface } from '@stencil/core';
import { Component, Host, Listen, Prop, forceUpdate, h } from '@stencil/core';
import { matchBreakpoint } from '@utils/media';

import { getIonMode } from '../../global/ionic-global';

const win = typeof (window as any) !== 'undefined' ? (window as any) : undefined;
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
const SUPPORTS_VARS = win && !!(win.CSS && win.CSS.supports && win.CSS.supports('--a: 0'));
const BREAKPOINTS = ['', 'xs', 'sm', 'md', 'lg', 'xl'];

@Component({
  tag: 'ion-col',
  styleUrl: 'col.scss',
  shadow: true,
})
export class Col implements ComponentInterface {
  /**
   * The amount to offset the column, in terms of how many columns it should shift to the end
   * of the total available.
   */
  @Prop() offset?: string;

  /**
   * The amount to offset the column for xs screens, in terms of how many columns it should shift
   * to the end of the total available.
   */
  @Prop() offsetXs?: string;

  /**
   * The amount to offset the column for sm screens, in terms of how many columns it should shift
   * to the end of the total available.
   */
  @Prop() offsetSm?: string;

  /**
   * The amount to offset the column for md screens, in terms of how many columns it should shift
   * to the end of the total available.
   */
  @Prop() offsetMd?: string;

  /**
   * The amount to offset the column for lg screens, in terms of how many columns it should shift
   * to the end of the total available.
   */
  @Prop() offsetLg?: string;

  /**
   * The amount to offset the column for xl screens, in terms of how many columns it should shift
   * to the end of the total available.
   */
  @Prop() offsetXl?: string;

  /**
   * The amount to pull the column, in terms of how many columns it should shift to the start of
   * the total available.
   */
  @Prop() pull?: string;

  /**
   * The amount to pull the column for xs screens, in terms of how many columns it should shift
   * to the start of the total available.
   */
  @Prop() pullXs?: string;
  /**
   * The amount to pull the column for sm screens, in terms of how many columns it should shift
   * to the start of the total available.
   */
  @Prop() pullSm?: string;
  /**
   * The amount to pull the column for md screens, in terms of how many columns it should shift
   * to the start of the total available.
   */
  @Prop() pullMd?: string;
  /**
   * The amount to pull the column for lg screens, in terms of how many columns it should shift
   * to the start of the total available.
   */
  @Prop() pullLg?: string;
  /**
   * The amount to pull the column for xl screens, in terms of how many columns it should shift
   * to the start of the total available.
   */
  @Prop() pullXl?: string;

  /**
   * The amount to push the column, in terms of how many columns it should shift to the end
   * of the total available.
   */
  @Prop() push?: string;

  /**
   * The amount to push the column for xs screens, in terms of how many columns it should shift
   * to the end of the total available.
   */
  @Prop() pushXs?: string;

  /**
   * The amount to push the column for sm screens, in terms of how many columns it should shift
   * to the end of the total available.
   */
  @Prop() pushSm?: string;

  /**
   * The amount to push the column for md screens, in terms of how many columns it should shift
   * to the end of the total available.
   */
  @Prop() pushMd?: string;

  /**
   * The amount to push the column for lg screens, in terms of how many columns it should shift
   * to the end of the total available.
   */
  @Prop() pushLg?: string;

  /**
   * The amount to push the column for xl screens, in terms of how many columns it should shift
   * to the end of the total available.
   */
  @Prop() pushXl?: string;

  /**
   * The size of the column, in terms of how many columns it should take up out of the total
   * available. If `"auto"` is passed, the column will be the size of its content.
   */
  @Prop() size?: string;

  /**
   * The size of the column for xs screens, in terms of how many columns it should take up out
   * of the total available. If `"auto"` is passed, the column will be the size of its content.
   */
  @Prop() sizeXs?: string;

  /**
   * The size of the column for sm screens, in terms of how many columns it should take up out
   * of the total available. If `"auto"` is passed, the column will be the size of its content.
   */
  @Prop() sizeSm?: string;

  /**
   * The size of the column for md screens, in terms of how many columns it should take up out
   * of the total available. If `"auto"` is passed, the column will be the size of its content.
   */
  @Prop() sizeMd?: string;

  /**
   * The size of the column for lg screens, in terms of how many columns it should take up out
   * of the total available. If `"auto"` is passed, the column will be the size of its content.
   */
  @Prop() sizeLg?: string;

  /**
   * The size of the column for xl screens, in terms of how many columns it should take up out
   * of the total available. If `"auto"` is passed, the column will be the size of its content.
   */
  @Prop() sizeXl?: string;

  @Listen('resize', { target: 'window' })
  onResize() {
    forceUpdate(this);
  }

  // Loop through all of the breakpoints to see if the media query
  // matches and grab the column value from the relevant prop if so
  private getColumns(property: string) {
    let matched;

    for (const breakpoint of BREAKPOINTS) {
      const matches = matchBreakpoint(breakpoint);

      // Grab the value of the property, if it exists and our
      // media query matches we return the value
      const columns = (this as any)[property + breakpoint.charAt(0).toUpperCase() + breakpoint.slice(1)];

      if (matches && columns !== undefined) {
        matched = columns;
      }
    }

    // Return the last matched columns since the breakpoints
    // increase in size and we want to return the largest match
    return matched;
  }

  private calculateSize() {
    const columns = this.getColumns('size');

    // If size wasn't set for any breakpoint
    // or if the user set the size without a value
    // it means we need to stick with the default and return
    // e.g. <ion-col size-md>
    if (!columns || columns === '') {
      return;
    }

    // If the size is set to auto then don't calculate a size
    const colSize =
      columns === 'auto'
        ? 'auto'
        : // If CSS supports variables we should use the grid columns var
        SUPPORTS_VARS
        ? `calc(calc(${columns} / var(--ion-grid-columns, 12)) * 100%)`
        : // Convert the columns to a percentage by dividing by the total number
          // of columns (12) and then multiplying by 100
          (columns / 12) * 100 + '%';

    return {
      flex: `0 0 ${colSize}`,
      width: `${colSize}`,
      'max-width': `${colSize}`,
    };
  }

  // Called by push, pull, and offset since they use the same calculations
  private calculatePosition(property: string, modifier: string) {
    const columns = this.getColumns(property);

    if (!columns) {
      return;
    }

    // If the number of columns passed are greater than 0 and less than
    // 12 we can position the column, else default to auto
    const amount = SUPPORTS_VARS
      ? // If CSS supports variables we should use the grid columns var
        `calc(calc(${columns} / var(--ion-grid-columns, 12)) * 100%)`
      : // Convert the columns to a percentage by dividing by the total number
      // of columns (12) and then multiplying by 100
      columns > 0 && columns < 12
      ? (columns / 12) * 100 + '%'
      : 'auto';

    return {
      [modifier]: amount,
    };
  }

  private calculateOffset(isRTL: boolean) {
    return this.calculatePosition('offset', isRTL ? 'margin-right' : 'margin-left');
  }

  private calculatePull(isRTL: boolean) {
    return this.calculatePosition('pull', isRTL ? 'left' : 'right');
  }

  private calculatePush(isRTL: boolean) {
    return this.calculatePosition('push', isRTL ? 'right' : 'left');
  }

  render() {
    const isRTL = document.dir === 'rtl';
    const mode = getIonMode(this);
    return (
      <Host
        class={{
          [mode]: true,
        }}
        style={{
          ...this.calculateOffset(isRTL),
          ...this.calculatePull(isRTL),
          ...this.calculatePush(isRTL),
          ...this.calculateSize(),
        }}
      >
        <slot></slot>
      </Host>
    );
  }
}
