import { getCSSNumberFormat } from '@vev/utils';
import { get, isNumber, isString } from 'lodash';
import React, { Children, ReactNode, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { useCallbackRef, useFormField } from '../../hooks';
import { SilkeBox } from '../silke-box';
import { FormFieldProps } from '../silke-form';
import { SilkeIcon } from '../silke-icon';
import { SilkeOverflowMenuItem } from '../silke-overflow-menu';
import { SilkePopover } from '../silke-popover';
import { SilkeTextFieldItem } from '../silke-text-field';
import {
  SilkeTextFieldOutline,
  SilkeTextFieldOutlineProps,
} from '../silke-text-field/silke-text-field-outline';
import { useSilkeCSSContext } from './silke-css-context';
import styles from './silke-css-number-field.scss';
import {
  convertCSSNumberFormat,
  FORMAT_LABELS,
  formatNumberForDisplay,
  getDragCursorImage,
  getFormatFromText,
  roundCSSNumber,
} from './utils';
import { useTextFieldContext } from '../silke-text-field/text-field-context';
import { SilkeDivider } from '../silke-divider';

export type SilkeCssNumberFieldProps = FormFieldProps<string> &
  SilkeTextFieldOutlineProps & {
    formats?: string[];
    options?: (SilkeOverflowMenuItem | 'divider')[];
    min?: number;
    max?: number;
    /** Override the default decimal precision for the current format */
    precision?: number;
    autoFocus?: boolean;
    allowAuto?: boolean | string;
    disableVariables?: boolean;
    disabled?: boolean;
    /** Define witch axis the number belongs to (used when converting values for the CSSContext) */
    axis?: 'x' | 'y';
    onEnter?: (e: React.KeyboardEvent<HTMLDivElement>) => void;
  };

const handleSelectText = (el: HTMLDivElement, selectAll: boolean) => {
  const range = document.createRange();
  if (selectAll) range.selectNodeContents(el);
  else range.setStart(el, 1);
  window.getSelection()?.removeAllRanges();
  window.getSelection()?.addRange(range);
};

const preventDefault = (e: React.SyntheticEvent) => e.preventDefault();

export function SilkeCssNumberField(props: SilkeCssNumberFieldProps) {
  const {
    children,
    disabled,
    value,
    min,
    max,
    precision,
    formats,
    axis,
    allowAuto,
    autoFocus,
    className,
    options,
    onEnter,
    onChange,
    ...rest
  } = useFormField(props);

  const [openFormat, setFormatOpen] = useState(false);
  const rootRef = useRef<HTMLDivElement>(null);
  const numberRef = useRef<HTMLDivElement>(null);
  const context = useSilkeCSSContext();
  const textFieldContext = useTextFieldContext();
  const isNotDefined = value === undefined || value === null;
  const isAuto = allowAuto && value === 'auto';
  const isMixed = value === 'mixed';
  const isCustomLabel = !isAuto && !isMixed && /^[a-z]/i.test(value || '');
  const number = isNotDefined
    ? 'None'
    : isAuto
      ? isString(allowAuto)
        ? allowAuto
        : 'Auto'
      : isMixed
        ? 'Mixed'
        : isCustomLabel
          ? value
          : parseFloat(value || '0') || 0;
  const format =
    isAuto || isMixed || isNotDefined || isCustomLabel
      ? ''
      : getCSSNumberFormat(value || '0', formats?.includes('None'));

  let cl = styles.root;
  if (openFormat) cl += ' ' + styles.openFormat;
  if (className) cl += ' ' + className;
  if (disabled) cl += ' ' + styles.disabled;
  if (isNotDefined) cl += ' ' + styles.noneValue;

  const variables = React.useMemo(() => {
    return (context.variablesv2 || []).filter((v) => formats?.includes(v.unit || ''));
  }, [context.variablesv2, formats]);

  const handleNumberChange = () => {
    // const formatEl = formatRef.current;
    const numberEl = numberRef.current;
    if (numberEl) {
      const numberText = (numberEl.innerText || '').replace(/,/g, '.') || '0';

      if (allowAuto && numberText && /au?t?o?/i.test(numberText)) {
        // numberEl.innerText = 'Auto';
        onChange('auto');
        // handleSelectText(numberEl, false);
      } else {
        const newFormat = getFormatFromText(numberText, format, formats);
        let value: string | number;
        try {
          value = eval(numberText.replace(/[^()*+-./%0-9]/g, ''));
        } catch {
          value = parseFloat(numberText) || 0;
        }
        if (!value) value = 0;

        handleChange(value, newFormat);
      }
    }
  };

  const handleOpenFormat = (e: React.MouseEvent) => {
    e.preventDefault();
    setFormatOpen(true);
  };

  const handleFocus = () => {
    if (disabled) return;
    const selection = window.getSelection();
    const el = numberRef.current;
    if (selection?.isCollapsed && el) {
      handleSelectText(el, true);
    }
  };

  const handleNumberBlur = () => {
    if (openFormat) setFormatOpen(false);
    const numberEl = numberRef.current;

    if (numberEl) {
      const parent = numberEl.parentElement;
      if (parent) parent.scrollLeft = 0;
      numberEl.innerText = formatNumberForDisplay(
        format === ''
          ? `${number}`
          : isNumber(number)
            ? roundCSSNumber(number, format, undefined, undefined, precision).toString() + format
            : number + format,
      );
    }
  };

  const handleChange = (number: number | string, format: string) => {
    const newValue = isString(number)
      ? number + format
      : roundCSSNumber(number, format, min, max, precision) + format;
    onChange(newValue);
  };

  const handleFormatSelect = (newFormat: string) => {
    const el = numberRef.current;
    setFormatOpen(false);
    if (el && newFormat !== format) {
      const fromFormat = isAuto ? 'auto' : format.toLowerCase();
      const newValue = convertCSSNumberFormat(
        isString(number) ? 0 : number,
        fromFormat,
        newFormat.toLowerCase(),
        context,
        min,
        max,
        axis,
      );
      onChange(newValue);
      handleSelectText(el, true);
    }
  };

  useEffect(() => {
    if (!disabled && autoFocus) {
      // Delay so focus trap dosn't steal the focus
      setTimeout(() => {
        const el = numberRef.current;
        if (el) el.focus();
      }, 100);
    }
  }, [autoFocus]);

  useLayoutEffect(() => {
    const el = numberRef.current;
    if (el && document.activeElement !== el) {
      el.innerText = formatNumberForDisplay(
        isNumber(number) ? roundCSSNumber(number, format, undefined, undefined, precision).toString() + format : number + format,
      );
    }
  }, [number, format, variables, isAuto, isMixed]);

  const handleNumberKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
    if (openFormat) setFormatOpen(false);
    let step = 0;
    if (e.key === 'Enter') {
      e.preventDefault();
      if (onEnter) onEnter(e);
    }

    if (e.key === 'ArrowUp') step = 1;
    else if (e.key === 'ArrowDown') step = -1;

    if (step && !isString(number)) {
      e.preventDefault();
      if (e.shiftKey) step *= 10;
      else if (e.altKey) step /= 10;
      handleNumberStepRef.current(step);
    }
  };

  const handleNumberStepRef = useCallbackRef((step: number) => {
    const numberEl = numberRef.current;
    if (!isString(number) && numberEl) {
      const newNumber = roundCSSNumber(number + step, format, min, max, precision);

      handleChange(newNumber, format);
      numberEl.innerText = formatNumberForDisplay(newNumber.toString());
      handleSelectText(numberEl, false);
    }
  });

  const before: ReactNode[] = [];
  const after: ReactNode[] = [];
  Children.forEach(children, (child) => {
    if (React.isValidElement(child) && child.type === SilkeTextFieldItem && child.props.before) {
      before.push(child);
    } else {
      after.push(child);
    }
  });

  const handleStartDrag = async (e: React.MouseEvent) => {
    const canvas = document.createElement('canvas');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    canvas.classList.add(styles.counterCanvas);
    document.body.appendChild(canvas);
    const img = await getDragCursorImage();
    const ctx = canvas.getContext('2d');
    canvas.requestPointerLock();

    const y = e.pageY - 20;
    let x = e.pageX - 16;

    const handleMouseMove = (e: MouseEvent) => {
      let step = Math.round(e.movementX) / 2;
      if (e.altKey) step /= 10;
      if (e.shiftKey) step *= 2;

      if (step) {
        handleNumberStepRef.current(step);
        x += step;
        draw();
      }
    };

    const handleMouseUp = () => {
      window.removeEventListener('mousemove', handleMouseMove);
      window.removeEventListener('mouseup', handleMouseUp);
      canvas.remove();
    };

    window.addEventListener('mousemove', handleMouseMove);
    window.addEventListener('mouseup', handleMouseUp);

    const draw = () => {
      if (x > canvas.width) x = 0;
      else if (x < 0) x = canvas.width;
      ctx?.clearRect(0, 0, canvas.width, canvas.height);
      ctx?.drawImage(img, x, y);
    };

    draw();
  };

  const showFormatsArrow =
    !disabled && !context.readonly && ((formats?.length || 0) > 1 || (options?.length || 0) > 1);

  let inputCl = styles.field;
  if (showFormatsArrow) inputCl += ' ' + styles.hasFormats;

  const size = rest.size || textFieldContext.size;

  return (
    <SilkeTextFieldOutline
      {...rest}
      disabled={disabled}
      className={cl}
      ref={rootRef}
      hAlign="start"
      vAlign="center"
      onClick={handleFocus}
      onContextMenu={(e: React.MouseEvent) => {
        e.preventDefault();
        handleOpenFormat(e);
      }}
    >
      {before}

      <SilkeBox flex className={styles.inputHolder} overflow="hidden" vAlign="center">
        <div
          key="number"
          data-input
          className={inputCl + ' ' + styles.editable}
          ref={numberRef}
          role="textbox"
          tabIndex={disabled ? -1 : undefined}
          contentEditable={!disabled && !context.readonly}
          autoCorrect="off"
          spellCheck={false}
          onInput={disabled ? preventDefault : handleNumberChange}
          onKeyDown={disabled ? preventDefault : handleNumberKeyDown}
          onFocus={(e) => {
            handleFocus();
            if (props.onFocus) props.onFocus(e.nativeEvent);
          }}
          onBlur={(e) => {
            handleNumberBlur();
            if (props.onBlur) props.onBlur(e.nativeEvent);
          }}
        />

        {showFormatsArrow && (
          <SilkeBox
            className={styles.downIcon}
            width="s"
            height="s"
            align="center"
            tag="button"
            tabIndex={-1}
            onMouseDown={handleOpenFormat}
          >
            <SilkeIcon icon={openFormat ? 'chevron.up' : 'chevron.down'} />
          </SilkeBox>
        )}
      </SilkeBox>
      {!disabled && !context.readonly && !isAuto && (
        <div className={styles.dragBar} onMouseDown={handleStartDrag} />
      )}
      {after}

      {openFormat && (
        <SilkePopover
          anchor={rootRef}
          targetOrigin="top-left"
          anchorOrigin="bottom-left"
          shadow="level2"
          offsetY={1}
          onRequestClose={() => setFormatOpen(false)}
        >
          <SilkeBox
            column
            bg="neutral-20"
            rounded={size === 's' ? 'tiny' : 'small'}
            pad="xs"
            overflow="hidden"
            className={styles.menu}
          >
            {formats?.map((item) => (
              <SilkeOverflowMenuItem
                key={item}
                size="s"
                style={{ justifyContent: 'start' }}
                kind={item === format ? 'selected' : 'default'}
                onMouseDown={() => handleFormatSelect(item)}
                label={item in FORMAT_LABELS ? `${get(FORMAT_LABELS, item)} (${item})` : item}
              />
            ))}
            {allowAuto && (
              <SilkeOverflowMenuItem
                label="Auto"
                size="s"
                onMouseDown={() => onChange('auto')}
                style={{ justifyContent: 'start' }}
              />
            )}
            {(options?.length || 0) > 0 && (
              <>
                {options?.map((item, index) =>
                  !item ? null : item === 'divider' ? (
                    <SilkeDivider key={index} dark />
                  ) : (
                    <SilkeOverflowMenuItem
                      key={index}
                      size="s"
                      style={{ justifyContent: 'start' }}
                      onMouseDown={(e) => {
                        setFormatOpen(false);
                        item.onClick?.(e);
                      }}
                      label={item.label}
                    />
                  ),
                )}
              </>
            )}
          </SilkeBox>
        </SilkePopover>
      )}
    </SilkeTextFieldOutline>
  );
}
