import { clamp } from 'lodash';
import React, { CSSProperties, useMemo, useRef, useState } from 'react';
import { useCallbackRef, useDragging } from '../../hooks';
import { SilkeBox } from '../silke-box';
import { SilkeButton } from '../silke-button';
import { SilkeText } from '../silke-text';
import { SilkeTooltip } from '../silke-tooltip';
import styles from './silke-gradient-picker.module.scss';
import { SilkeGradient, getCSSFromGradientModel } from './silke-gradient-utils';
import { SilkeCssNumberField } from '../silke-css-number-field';

type SilkeGradientPositionProps = {
  gradient: SilkeGradient;
  onChange: (gradient: SilkeGradient) => void;
};

function getAngleFromCenter(el: HTMLElement, x: number, y: number) {
  const rect = el.getBoundingClientRect();
  const centerX = rect.left + rect.width / 2;
  const centerY = rect.top + rect.height / 2;
  return Math.atan2(y - centerY, x - centerX);
}

export function SilkeGradientPosition({ gradient, onChange }: SilkeGradientPositionProps) {
  const ref = useRef<HTMLElement>(null);
  const [showPanel, setShowPanel] = useState(false);
  const background = useMemo(() => getCSSFromGradientModel(gradient), [gradient]);
  const handlePosChange = (pos: string) => onChange({ ...gradient, pos });

  const Comp = gradient.type === 'radial' ? Position : Angle;

  return (
    <>
      <SilkeButton
        ref={ref}
        kind="ghost"
        size="s"
        icon={<Comp pos={gradient.pos} />}
        onClick={() => setShowPanel(true)}
      />
      <SilkeTooltip
        anchor={ref}
        show={showPanel}
        anchorOrigin="bottom-center"
        targetOrigin="top-center"
        onRequestClose={() => setShowPanel(false)}
      >
        <SilkeBox pad="s">
          <Comp pos={gradient.pos} big style={{ background }} onChange={handlePosChange} />
        </SilkeBox>
      </SilkeTooltip>
    </>
  );
}

type PosComponentProps = {
  big?: boolean;
  pos?: string;
  style?: CSSProperties;
  onChange?: (value: string) => void;
};

function Angle({ pos, big, style, onChange }: PosComponentProps) {
  const position = pos || '0deg';
  const value = position?.endsWith('deg') ? parseInt(position) || 0 : 0;
  const [dragging, setDragging] = useDragging((e) => handleChange(e.pageX, e.pageY, e.shiftKey));
  const ref = useRef<HTMLDivElement>(null);
  const changeRef = useCallbackRef(onChange);

  const handleChange = (pageX: number, pageY: number, roundTo45: boolean) => {
    const el = ref.current;
    if (!el) return;
    let angleFromCenter = getAngleFromCenter(el, pageX, pageY);
    angleFromCenter = Math.round((angleFromCenter * 180) / Math.PI) + 90;
    if (roundTo45) angleFromCenter = Math.round(angleFromCenter / 45) * 45;
    changeRef.current?.(angleFromCenter + 'deg');
    setDragging(true);
  };

  const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
    handleChange(e.pageX, e.pageY, e.shiftKey);
    setDragging(true);
  };

  let cl = styles.position + ' ' + styles.circle;
  if (big) cl += ` ${styles.big}`;
  if (dragging) cl += ` ${styles.dragging}`;
  return (
    <div
      ref={ref}
      className={cl}
      style={{ '--angle': `${value - 90 || 0}deg`, ...style } as CSSProperties}
      onMouseDown={onChange && handleMouseDown}
    >
      {big && (
        <SilkeBox>
          <SilkeCssNumberField
            inline
            onMouseDown={(e) => e.stopPropagation()}
            autoFocus
            value={value + 'deg'}
            onChange={(v) => onChange?.((parseInt(v) % 360) + 'deg')}
            size="s"
            kind="ghost"
          />
        </SilkeBox>
      )}
    </div>
  );
}

function Position({ pos, big, style, onChange }: PosComponentProps) {
  let [size, offset] = (pos || '100% 100% at 50% 50%').split('at');
  if (!size) size = '100% 100%';
  if (!offset) offset = '0% 0%';
  size = size?.trim();
  offset = offset?.trim();

  let [x, y] = offset.split(' ');
  if (!x) x = '50%';
  if (!y) y = x;

  let [width, height] = size.split(' ');
  if (!width) width = '100%';
  if (!height) height = width;

  const [dragging, setDragging] = useDragging((e) => handleChange(e.pageX, e.pageY));
  const ref = useRef<HTMLDivElement>(null);
  const changeRef = useCallbackRef(onChange);

  const handleChange = (pageX: number, pageY: number) => {
    const el = ref.current;
    if (!el) return;

    const { left, width, height, top } = el.getBoundingClientRect();
    const percentLeft = clamp((pageX - left) / width, 0, 1);
    const percentTop = clamp((pageY - top) / height, 0, 1);
    changeRef.current?.(
      `${size || '100%'} at ${Math.round(percentLeft * 100)}% ${Math.round(percentTop * 100)}%`,
    );
  };

  const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
    handleChange(e.pageX, e.pageY);
    setDragging(true);
  };

  let cl = styles.position + ' ' + styles.square;
  if (big) cl += ` ${styles.big}`;
  if (dragging) cl += ` ${styles.dragging}`;

  return (
    <div
      ref={ref}
      className={cl}
      style={{ '--left': x, '--top': y, ...style } as CSSProperties}
      onMouseDown={onChange && handleMouseDown}
    ></div>
  );
}
