import * as React from 'react';
import clsx from 'clsx';
import plugin from 'tailwindcss/plugin';
import { useBrowser } from '@modal-sh/react-utils';

const filterOptions = (children: React.ReactNode): React.ReactNode => {
  const childrenArray = Array.isArray(children) ? children : [children];
  return childrenArray.reduce(
    (optionChildren, child) => {
      if (!(typeof child === 'object' && child)) {
        return optionChildren;
      }
      if (child.type === 'option') {
        return [
          ...optionChildren,
          child,
        ];
      }
      if (child.type === 'optgroup') {
        return [
          ...optionChildren,
          ...filterOptions(child.props.children) as unknown[],
        ];
      }
      return optionChildren;
    },
    [],
  ) as React.ReactNode;
};

/*
 * Caveat for slider:
 *
 * Since sliders are not as customizable as other components, especially with orientations,
 * prefer using sliders where using horizontal sliders would be as acceptable as vertical
 * sliders and vv.
 */

/**
 * Orientation of the {@link Slider} component.
 */
export type SliderOrientation = 'horizontal' | 'vertical';

/**
 * Derived HTML element of the {@link Slider} component.
 */
export type SliderDerivedElement = HTMLInputElement;

/**
 * Props of the {@link Slider} component.
 */
export interface SliderProps extends Omit<React.HTMLProps<HTMLInputElement>, 'type'> {
  /**
   * Orientation of the component.
   */
  orient?: SliderOrientation;
  /**
   * Options of the component.
   */
  children?: React.ReactNode;
  /**
   * Length of the component.
   */
  length?: React.CSSProperties['width'];
}

export const sliderPlugin = plugin(({ addComponents }) => {
  addComponents({
    '.slider': {
      '& > input': {
        appearance: 'none',
        cursor: 'pointer',
        position: 'relative',
        overflow: 'hidden',
        height: '1em',
        'box-sizing': 'border-box',
        color: 'rgb(var(--color-primary))',
      },

      '& > input::-webkit-slider-container': {
        width: '100%',
        height: '100%',
        'min-block-size': '0',
        'background-color': 'rgb(var(--color-primary) / 50%)',
        'border-radius': '9999px',
        display: 'block',
        'box-sizing': 'border-box',
        'background-clip': 'content-box',
        padding: '0.25em',
        appearance: 'none',
      },

      '& > input::-webkit-slider-runnable-track': {
        appearance: 'none',
        'border-radius': '9999px',
        display: 'block',
        width: '100%',
        height: '100%',
        margin: '-0.25em',
        'box-sizing': 'border-box',
        'background-clip': 'content-box',
      },

      '& > input::-webkit-slider-thumb': {
        width: '1em',
        height: '1em',
        margin: '-0.25em 0 0 0',
        'border-radius': '9999px',
        'background-color': 'currentColor',
        appearance: 'none',
        'aspect-ratio': '1 / 1',
        'z-index': '1',
        position: 'relative',
        'box-shadow': 'calc(-200001em / 2) 0 0 100000em rgb(var(--color-primary) / 50%)',
      },

      '& > input:focus::-webkit-slider-container': {
        'background-color': 'rgb(var(--color-secondary) / 50%)',
      },

      '& > input:active::-webkit-slider-container': {
        'background-color': 'rgb(var(--color-tertiary) / 50%)',
      },

      '& > input:focus::-webkit-slider-thumb': {
        'box-shadow': 'calc(-200001em / 2) 0 0 100000em rgb(var(--color-secondary) / 50%)',
      },

      '& > input:active::-webkit-slider-thumb': {
        'box-shadow': 'calc(-200001em / 2) 0 0 100000em rgb(var(--color-tertiary) / 50%)',
      },

      '&[data-orient="horizontal"]': {
        'flex-direction': 'column',
      },

      '&[data-firefox]': {
        'flex-direction': 'column',
      },

      '&[data-firefox="vertical"]': {
        'flex-direction': 'row',
      },

      '&[data-orient="vertical"]': {
        'flex-direction': 'column',
        'rotate': '-90deg',
        'translate': 'calc(-100% + 0.5em * 2)',
        'transform-origin': 'calc(100% - 0.5em) 0.5em',
      },

      '& > input::-moz-range-track': {
        appearance: 'none',
        'border-radius': '9999px',
        'content': '""',
        display: 'block',
        position: 'absolute',
        'background-color': 'currentColor',
        'opacity': '0.5',
        width: 'calc(100% - 0.5em)',
        height: '50%',
        top: '25%',
        margin: '-0.25em',
      },

      '& > input::-moz-range-thumb': {
        height: '100%',
        outline: '0',
        border: '0',
        'border-radius': '9999px',
        'background-color': 'currentColor',
        appearance: 'none',
        'aspect-ratio': '1 / 1',
        'z-index': '1',
        position: 'relative',
        'box-shadow': 'calc(-200001em / 2) 0 0 100000em rgb(var(--color-primary) / 50%)',
      },

      '& > input:focus::-moz-range-thumb': {
        'box-shadow': 'calc(-200001em / 2) 0 0 100000em rgb(var(--color-secondary) / 50%)',
      },

      '& > input:active::-moz-range-thumb': {
        'box-shadow': 'calc(-200001em / 2) 0 0 100000em rgb(var(--color-tertiary) / 50%)',
      },

      '& > input[orient="vertical"]': {
        width: '1em',
        height: '16em',
      },

      '& > input[orient="vertical"]::-moz-range-track': {
        width: '50%',
        height: 'calc(100% - 0.5em)',
      },

      '& > input[orient="vertical"]::-moz-range-thumb': {
        width: '100%',
        height: '1em',
        'box-shadow': '0 calc(200001em / 2) 0 100000em rgb(var(--color-primary) / 50%)',
      },

      '& > input[orient="vertical"]:focus::-moz-range-thumb': {
        'box-shadow': '0 calc(200001em / 2) 0 100000em rgb(var(--color-secondary) / 50%)',
      },

      '& > input[orient="vertical"]:active::-moz-range-thumb': {
        'box-shadow': '0 calc(200001em / 2) 0 100000em rgb(var(--color-tertiary) / 50%)',
      },

      '&[data-chrome] > input + *': {
        'padding': '0 0.5em',
        'height': '1.5em',
      },

      '&[data-firefox="horizontal"] > input + *': {
        'padding': '0 0.5em',
        'height': '1.5em',
      },

      '&[data-firefox="vertical"] > input + *': {
        'padding': '0.5em 0',
        'width': '1.5em',
      },
    },
  });
});

/**
 * Component for inputting continuous numeric values.
 */
export const Slider = React.forwardRef<SliderDerivedElement, SliderProps>((
  {
    className,
    style,
    children,
    orient = 'horizontal',
    length,
    min = 0,
    max = 100,
    ...etcProps
  },
  forwardedRef,
) => {
  const browser = useBrowser();
  const defaultRef = React.useRef<HTMLInputElement>(null);
  const ref = forwardedRef ?? defaultRef;
  const tickMarkId = React.useId();

  React.useEffect(() => {
    const setupGecko = (theOrient: string, theChildren: unknown, theBrowser?: string) => {
      if (!(typeof ref === 'object' && ref)) {
        return;
      }
      const { current: slider } = ref;
      if (!slider) {
        return;
      }

      const isFirefox = theBrowser === 'firefox';
      const wrapper = slider?.parentElement as HTMLElement;
      const parent = wrapper?.parentElement as HTMLElement;
      const grandParent = parent?.parentElement as HTMLElement;
      if (isFirefox) {
        slider.setAttribute('orient', theOrient);
        wrapper.dataset[theBrowser] = theOrient;
        wrapper.removeAttribute('data-orient');
        grandParent.style.width = theChildren ? '2.5em' : '1em';
      }
    };

    setupGecko(orient, children, browser);

    return () => {
      if (!(typeof ref === 'object' && ref)) {
        return;
      }
      const { current: slider } = ref;
      if (!slider) {
        return;
      }

      const isFirefox = browser === 'firefox';
      const wrapper = slider?.parentElement as HTMLElement;
      const parent = wrapper?.parentElement as HTMLElement;
      const grandParent = parent?.parentElement as HTMLElement;
      if (slider && isFirefox && parent && grandParent && wrapper) {
        grandParent.style.width = 'auto';
        wrapper.removeAttribute(`data-${browser}`);
        wrapper.dataset.orient = slider.getAttribute(orient) ?? undefined;
        slider.removeAttribute('orient');
      }
    };
  }, [ref, orient, children, browser]);

  React.useEffect(() => {
    let shouldEffectExecute: boolean;
    const setupNonGecko = (theOrient: string, theBrowser?: string) => {
      if (!(typeof ref === 'object' && ref)) {
        return;
      }
      const { current: slider } = ref;
      if (!slider) {
        return;
      }

      if (!theBrowser) {
        return;
      }

      const wrapper = slider?.parentElement as HTMLElement;
      const parent = wrapper?.parentElement as HTMLElement;
      const grandParent = parent?.parentElement as HTMLElement;

      const isNotFirefox = theBrowser !== 'firefox';
      if (isNotFirefox) {
        wrapper.dataset[theBrowser] = theOrient;
      }

      shouldEffectExecute = isNotFirefox && theOrient === 'vertical' && Boolean(slider) && Boolean(parent) && Boolean(grandParent);
      if (shouldEffectExecute) {
        const trueHeight = parent.clientWidth;
        const trueWidth = parent.clientHeight;

        grandParent.dataset.height = grandParent.clientHeight.toString();
        grandParent.dataset.width = grandParent.clientWidth.toString();
        grandParent.style.height = `${trueHeight}px`;
        grandParent.style.width = `${trueWidth}px`;
      }
    };

    setupNonGecko(orient, browser);

    return () => {
      if (!(typeof ref === 'object' && ref)) {
        return;
      }
      const { current: slider } = ref;
      if (!slider) {
        return;
      }

      const wrapper = slider?.parentElement as HTMLElement;
      const parent = wrapper?.parentElement as HTMLElement;
      const grandParent = parent?.parentElement as HTMLElement;

      if (shouldEffectExecute) {
        grandParent.style.height = `${grandParent.dataset.height ?? 0}px`;
        grandParent.style.width = `${grandParent.dataset.width ?? 0}px`;
        grandParent.removeAttribute('data-height');
        grandParent.removeAttribute('data-width');
      }
      wrapper.removeAttribute(`data-${browser ?? 'unknown'}`);
    };
  }, [ref, orient, browser]);

  React.useEffect(() => {
    if (!(typeof ref === 'object' && ref)) {
      return;
    }
    const { current: slider } = ref;
    if (!slider) {
      return;
    }

    const isFirefox = browser === 'firefox';
    const isNotFirefox = typeof browser === 'string' && browser !== 'firefox';
    const tickMarkContainer = slider.nextElementSibling;
    if (tickMarkContainer) {
      const tickMarks = tickMarkContainer.children[0].children;
      Array.from(tickMarks).forEach((tickMarkRaw) => {
        const tickMark = tickMarkRaw as HTMLElement;
        const offset = tickMark.dataset.offset as string;
        const tickMarkWrapper = tickMark.children[0] as HTMLElement;
        const tickMarkLine = tickMarkWrapper.children[0] as HTMLElement;
        const tickMarkLabel = tickMarkLine.nextElementSibling as HTMLElement | null;
        if (isNotFirefox) {
          tickMark.style.left = offset;
          tickMark.style.bottom = '';
          tickMarkWrapper.style.translate = '-50%';
          tickMarkWrapper.style.flexDirection = 'column';
          tickMarkLine.style.width = '1px';
          tickMarkLine.style.height = '0.5em';
          if (tickMarkLabel) {
            tickMarkLabel.style.writingMode = 'initial';
          }
        } else if (isFirefox && orient === 'horizontal') {
          tickMark.style.left = offset;
          tickMark.style.bottom = '';
          tickMarkWrapper.style.translate = '-50%';
          tickMarkWrapper.style.flexDirection = 'column';
          tickMarkLine.style.width = '1px';
          tickMarkLine.style.height = '0.5em';
          if (tickMarkLabel) {
            tickMarkLabel.style.writingMode = 'initial';
          }
        } else {
          tickMark.style.bottom = offset;
          tickMark.style.left = '';
          tickMarkWrapper.style.translate = '0 50%';
          tickMarkWrapper.style.flexDirection = 'row';
          tickMarkLine.style.width = '0.5em';
          tickMarkLine.style.height = '1px';
          if (tickMarkLabel) {
            tickMarkLabel.style.writingMode = 'sideways-lr';
          }
        }
      });
    }
  }, [ref, orient, browser]);

  const block = typeof length === 'string' && length.trim() === '100%';
  const minValue = Number(min);
  const maxValue = Number(max);

  if (minValue >= maxValue) {
    return null;
  }

  const childrenValues = children
    ? filterOptions(children)
    : undefined;

  return (
    <>
      {children
      && (
        <datalist
          id={tickMarkId}
        >
          {children}
        </datalist>
      )}
      <div
        className={clsx(
          block && orient !== 'vertical' && 'w-full',
          !block && 'align-middle',
          className,
          orient !== 'vertical' && 'inline-block min-h-[1rem]',
          orient === 'vertical' && 'inline-block min-w-[1rem]',
          orient !== 'vertical' && !block && 'min-w-[16rem]',
        )}
        style={style}
      >
        <div
          style={{
            width: orient === 'vertical' ? (length ?? '16rem') : undefined,
            position: 'relative',
          }}
        >
          <div
            className="flex slider"
            data-orient={orient}
          >
            <input
              {...etcProps}
              ref={ref}
              min={minValue}
              max={maxValue}
              type="range"
              list={childrenValues ? tickMarkId : undefined}
              className={clsx(
                'w-full h-full bg-inherit block text-primary ring-secondary/50 rounded-full',
                'focus:text-secondary focus:outline-0 focus:ring-4',
                'active:text-tertiary active:ring-tertiary/50',
              )}
            />
            {Array.isArray(childrenValues) && (
              <div className="select-none">
                <div className="relative w-full h-full">
                  {
                    childrenValues
                      .filter((c) => {
                        const value = Number(c.props.value);
                        return minValue <= value && value <= maxValue;
                      })
                      .map((c) => {
                        const value = Number(c.props.value);
                        return (
                          <div
                            key={c.props.value}
                            className="absolute w-full h-full box-border"
                            data-offset={`${((value - minValue) / (maxValue - minValue)) * 100}%`}
                          >
                            <div
                              className="flex h-full text-xs items-center justify-between"
                            >
                              <div
                                className="bg-current"
                              />
                              <div
                                className={c.props.className}
                              >
                                {c.props.children}
                              </div>
                            </div>
                          </div>
                        );
                      })
                  }
                </div>
              </div>
            )}
          </div>
        </div>
      </div>
    </>
  );
});

Slider.displayName = 'Slider';

Slider.defaultProps = {
  orient: 'horizontal',
  length: undefined,
  children: undefined,
};
