import { useCallback, useEffect, useRef, useState, type CSSProperties } from "react";
import {
  calculateToolbarTargets,
  NODEDC_TOOLBAR_DEFAULTS,
  normalizeToolbarSettings,
  stepToolbarSpring,
  type ToolbarPlacement as CoreToolbarPlacement,
} from "@nodedc/ui-core";
import { cn } from "./cn.js";
import { Icon, type IconName } from "./Icon.js";

export type ToolbarPlacement = CoreToolbarPlacement;

export interface ToolbarItem<T extends string = string> {
  id: T;
  label: string;
  icon: IconName;
  active?: boolean;
  disabled?: boolean;
  onSelect: (id: T) => void;
}

export interface ToolbarProps<T extends string = string> {
  items: Array<ToolbarItem<T>>;
  placement?: ToolbarPlacement;
  background?: string;
  border?: string;
  outline?: string;
  accent?: string;
  minSize?: number;
  maxSize?: number;
  lensCount?: number;
  autoHide?: boolean;
  label?: string;
  className?: string;
}

interface ToolbarMotion {
  scale: number;
  targetScale: number;
  scaleVelocity: number;
  shift: number;
  targetShift: number;
  shiftVelocity: number;
}

export function Toolbar<T extends string = string>({
  items,
  placement = "left",
  background = NODEDC_TOOLBAR_DEFAULTS.background,
  border = NODEDC_TOOLBAR_DEFAULTS.border,
  outline = NODEDC_TOOLBAR_DEFAULTS.outline,
  accent = "#ff2f92",
  minSize = NODEDC_TOOLBAR_DEFAULTS.minSize,
  maxSize = NODEDC_TOOLBAR_DEFAULTS.maxSize,
  lensCount = NODEDC_TOOLBAR_DEFAULTS.lensCount,
  autoHide = false,
  label = "Панель инструментов",
  className,
}: ToolbarProps<T>) {
  const [visible, setVisible] = useState(!autoHide);
  const [dockActive, setDockActive] = useState(false);
  const toolbarRef = useRef<HTMLDivElement | null>(null);
  const buttonRefs = useRef<Record<string, HTMLButtonElement | null>>({});
  const motionRef = useRef<Record<string, ToolbarMotion>>({});
  const actionKeysRef = useRef<string[]>([]);
  const frameRef = useRef<number | null>(null);
  const frameLastTsRef = useRef(0);
  const hideTimerRef = useRef<number | null>(null);
  const hoverDockRef = useRef(false);

  const vertical = placement !== "bottom";
  const normalized = normalizeToolbarSettings({ minSize, maxSize, lensCount });
  const safeMin = normalized.minSize;
  const safeMax = normalized.maxSize;
  const safeLens = normalized.lensCount;
  const radius = Math.max(7, Math.round(safeMin * 0.28));
  const glyphSize = Math.max(12, Math.min(18, Math.round(safeMin * 0.62)));
  const settingsRef = useRef({ minSize: safeMin, maxSize: safeMax, lensCount: safeLens, vertical });
  settingsRef.current = { minSize: safeMin, maxSize: safeMax, lensCount: safeLens, vertical };
  actionKeysRef.current = items.map((item) => String(item.id));

  const getMotion = useCallback((key: string) => {
    let motion = motionRef.current[key];
    if (!motion) {
      motion = {
        scale: 1,
        targetScale: 1,
        scaleVelocity: 0,
        shift: 0,
        targetShift: 0,
        shiftVelocity: 0,
      };
      motionRef.current[key] = motion;
    }
    return motion;
  }, []);

  const renderFrame = useCallback((timestamp = performance.now()) => {
    const keys = actionKeysRef.current;
    if (!keys.length) {
      frameRef.current = null;
      return;
    }

    const dt = frameLastTsRef.current
      ? Math.min(0.032, Math.max(0.001, (timestamp - frameLastTsRef.current) / 1000))
      : 0.016;
    frameLastTsRef.current = timestamp;
    let moving = false;

    keys.forEach((key) => {
      const button = buttonRefs.current[key];
      if (!button) return;
      const motion = getMotion(key);
      [motion.scale, motion.scaleVelocity] = stepToolbarSpring(
        motion.scale,
        motion.scaleVelocity,
        motion.targetScale,
        dt,
      );
      [motion.shift, motion.shiftVelocity] = stepToolbarSpring(
        motion.shift,
        motion.shiftVelocity,
        motion.targetShift,
        dt,
      );

      if (
        Math.abs(motion.scale - motion.targetScale) < 0.001
        && Math.abs(motion.scaleVelocity) < 0.001
        && Math.abs(motion.shift - motion.targetShift) < 0.05
        && Math.abs(motion.shiftVelocity) < 0.05
      ) {
        motion.scale = motion.targetScale;
        motion.scaleVelocity = 0;
        motion.shift = motion.targetShift;
        motion.shiftVelocity = 0;
      } else {
        moving = true;
      }

      button.style.setProperty("--nodedc-toolbar-scale", motion.scale.toFixed(4));
      button.style.setProperty("--nodedc-toolbar-shift", `${motion.shift.toFixed(2)}px`);
    });

    if (moving) {
      frameRef.current = window.requestAnimationFrame(renderFrame);
      return;
    }
    frameRef.current = null;
    frameLastTsRef.current = 0;
  }, [getMotion]);

  const startAnimation = useCallback(() => {
    if (frameRef.current != null) return;
    frameLastTsRef.current = 0;
    frameRef.current = window.requestAnimationFrame(renderFrame);
  }, [renderFrame]);

  const setButtonTarget = useCallback((key: string, size: number, shift: number) => {
    const motion = getMotion(key);
    const { minSize: currentMin } = settingsRef.current;
    motion.targetScale = currentMin > 0 ? size / currentMin : 1;
    motion.targetShift = shift;
    startAnimation();
  }, [getMotion, startAnimation]);

  const resetMagnification = useCallback(() => {
    const { minSize: currentMin } = settingsRef.current;
    actionKeysRef.current.forEach((key) => setButtonTarget(key, currentMin, 0));
    setDockActive(false);
  }, [setButtonTarget]);

  const updateMagnification = useCallback((pointerCoordinate: number) => {
    const toolbar = toolbarRef.current;
    const keys = actionKeysRef.current;
    if (!toolbar || !keys.length) return;
    const { minSize: currentMin, maxSize: currentMax, lensCount: currentLens, vertical: isVertical } = settingsRef.current;
    const toolbarRect = toolbar.getBoundingClientRect();
    const entries = keys
      .map((key) => {
        const button = buttonRefs.current[key];
        if (!button) return null;
        const offset = isVertical ? button.offsetTop : button.offsetLeft;
        const length = isVertical ? button.offsetHeight : button.offsetWidth;
        const origin = isVertical ? toolbarRect.top : toolbarRect.left;
        return { key, center: origin + offset + length / 2 };
      })
      .filter((entry): entry is { key: string; center: number } => Boolean(entry))
      .sort((a, b) => a.center - b.center);

    const targets = calculateToolbarTargets(
      entries.map((entry) => entry.center),
      pointerCoordinate,
      { minSize: currentMin, maxSize: currentMax, lensCount: currentLens, gap: NODEDC_TOOLBAR_DEFAULTS.gap },
    );
    entries.forEach(({ key }, index) => {
      const target = targets[index];
      if (target) setButtonTarget(key, target.size, target.shift);
    });
  }, [setButtonTarget]);

  const showDock = useCallback(() => {
    if (hideTimerRef.current != null) {
      window.clearTimeout(hideTimerRef.current);
      hideTimerRef.current = null;
    }
    setVisible(true);
  }, []);

  const scheduleHide = useCallback(() => {
    if (!autoHide || hideTimerRef.current != null) return;
    hideTimerRef.current = window.setTimeout(() => {
      hideTimerRef.current = null;
      if (hoverDockRef.current) return;
      setVisible(false);
      resetMagnification();
    }, NODEDC_TOOLBAR_DEFAULTS.hideDelay);
  }, [autoHide, resetMagnification]);

  useEffect(() => {
    if (!autoHide) {
      setVisible(true);
      return;
    }
    setVisible(false);
    setDockActive(false);
  }, [autoHide]);

  useEffect(() => {
    resetMagnification();
  }, [placement, safeMin, safeMax, safeLens, resetMagnification]);

  useEffect(() => {
    if (!autoHide) return;
    const handlePointerMove = (event: PointerEvent) => {
      const threshold = Math.max(32, settingsRef.current.maxSize * 0.75);
      const nearEdge = placement === "left"
        ? event.clientX <= threshold
        : placement === "right"
          ? event.clientX >= window.innerWidth - threshold
          : event.clientY >= window.innerHeight - threshold;
      if (nearEdge) showDock();
      else if (!hoverDockRef.current) scheduleHide();
    };
    window.addEventListener("pointermove", handlePointerMove, { passive: true });
    return () => window.removeEventListener("pointermove", handlePointerMove);
  }, [autoHide, placement, scheduleHide, showDock]);

  useEffect(() => {
    const handlePointerMove = (event: PointerEvent) => {
      const toolbar = toolbarRef.current;
      if (!hoverDockRef.current || !toolbar) return;
      const rect = toolbar.getBoundingClientRect();
      const inside = event.clientX >= rect.left - 2
        && event.clientX <= rect.right + 2
        && event.clientY >= rect.top - 2
        && event.clientY <= rect.bottom + 2;
      if (inside) return;
      hoverDockRef.current = false;
      resetMagnification();
      scheduleHide();
    };
    window.addEventListener("pointermove", handlePointerMove, { passive: true });
    return () => window.removeEventListener("pointermove", handlePointerMove);
  }, [resetMagnification, scheduleHide]);

  useEffect(() => () => {
    if (frameRef.current != null) window.cancelAnimationFrame(frameRef.current);
    if (hideTimerRef.current != null) window.clearTimeout(hideTimerRef.current);
  }, []);

  const style = {
    "--nodedc-toolbar-bg": background,
    "--nodedc-toolbar-border": border,
    "--nodedc-toolbar-outline": outline,
    "--nodedc-toolbar-accent": accent,
    "--nodedc-toolbar-min-size": `${safeMin}px`,
    "--nodedc-toolbar-max-size": `${safeMax}px`,
    "--nodedc-toolbar-radius": `${radius}px`,
    "--nodedc-toolbar-gap": `${NODEDC_TOOLBAR_DEFAULTS.gap}px`,
    "--nodedc-toolbar-padding": `${NODEDC_TOOLBAR_DEFAULTS.padding}px`,
  } as CSSProperties;

  return (
    <div
      className={cn("nodedc-toolbar-wrap", className)}
      data-placement={placement}
      data-visible={visible ? "true" : undefined}
      data-active={dockActive ? "true" : undefined}
      data-autohide={autoHide ? "true" : undefined}
      style={style}
    >
      {autoHide ? (
        <div
          className="nodedc-toolbar__hotzone"
          aria-hidden="true"
          onPointerEnter={showDock}
          onPointerMove={showDock}
        />
      ) : null}
      <div
        ref={toolbarRef}
        className="nodedc-toolbar"
        role="toolbar"
        aria-label={label}
        onPointerEnter={() => {
          hoverDockRef.current = true;
          showDock();
          setDockActive(true);
        }}
        onPointerMove={(event) => {
          hoverDockRef.current = true;
          showDock();
          setDockActive(true);
          updateMagnification(vertical ? event.clientY : event.clientX);
        }}
        onPointerLeave={() => {
          hoverDockRef.current = false;
          resetMagnification();
          scheduleHide();
        }}
      >
        {items.map((item) => {
          const key = String(item.id);
          return (
            <button
              key={item.id}
              ref={(element) => { buttonRefs.current[key] = element; }}
              type="button"
              className="nodedc-toolbar__button"
              data-active={item.active ? "true" : undefined}
              aria-label={item.label}
              aria-pressed={item.active}
              disabled={item.disabled}
              onFocus={(event) => {
                const button = event.currentTarget;
                const toolbarRect = toolbarRef.current?.getBoundingClientRect();
                if (!toolbarRect) return;
                const coordinate = vertical
                  ? toolbarRect.top + button.offsetTop + button.offsetHeight / 2
                  : toolbarRect.left + button.offsetLeft + button.offsetWidth / 2;
                setDockActive(true);
                updateMagnification(coordinate);
              }}
              onBlur={resetMagnification}
              onClick={() => item.onSelect(item.id)}
            >
              <Icon name={item.icon} size={glyphSize} />
            </button>
          );
        })}
      </div>
    </div>
  );
}
