import * as React from 'react';
import clsx from 'clsx';
import styles from './style.module.css';

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.
 */

export type SliderOrientation = 'horizontal' | 'vertical';

type SliderDerivedElement = HTMLInputElement;

export interface SliderProps extends Omit<React.HTMLProps<HTMLInputElement>, 'type'> {
  orient?: SliderOrientation;
  children?: React.ReactNode;
  length?: React.CSSProperties['width'];
}

export const Slider = React.forwardRef<SliderDerivedElement, SliderProps>(({
  className,
  style,
  children,
  orient = 'horizontal',
  length,
  min = 0,
  max = 100,
  ...etcProps
}, forwardedRef) => {
  const [browser, setBrowser] = React.useState<string>();
  React.useEffect(() => {
    const isFirefox = typeof (window as unknown as Record<string, unknown>).InstallTrigger !== 'undefined';
    if (isFirefox) {
      setBrowser('firefox');
    } else {
      // TODO - detect other browsers
      setBrowser('chrome');
    }
  }, []);

  const defaultRef = React.useRef<HTMLInputElement>(null);
  const ref = forwardedRef ?? defaultRef;
  const tickMarkId = React.useId();

  React.useEffect(() => {
    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 (isFirefox) {
      slider.setAttribute('orient', orient);
      wrapper.dataset[browser] = orient;
      wrapper.removeAttribute('data-orient');
      grandParent.style.width = children ? '2.5em' : '1em';
    }

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

  React.useEffect(() => {
    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;

    const isNotFirefox = typeof browser === 'string' && browser !== 'firefox';
    if (isNotFirefox) {
      wrapper.dataset[browser] = orient;
    }

    const shouldEffectExecute = isNotFirefox && orient === 'vertical' && slider && parent && 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';
    }

    return () => {
      if (shouldEffectExecute) {
        grandParent.style.height = grandParent.dataset.height + 'px';
        grandParent.style.width = grandParent.dataset.width + 'px';
        grandParent.removeAttribute('data-height');
        grandParent.removeAttribute('data-width');
      }
      wrapper.removeAttribute(`data-${browser}`);
    };
  }, [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={clsx(
              styles['slider-wrapper'],
              'flex'
            )}
            data-orient={orient}
          >
            <input
              {...etcProps}
              ref={ref}
              min={minValue}
              max={maxValue}
              type="range"
              list={childrenValues ? tickMarkId : undefined}
              className={clsx(
                styles.slider,
                '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={clsx(
                              styles['tick-mark'],
                              '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';
