import {
  calculateToolbarTargets,
  NODEDC_TOOLBAR_DEFAULTS,
  normalizeToolbarSettings,
  stepToolbarSpring,
  type ToolbarPlacement,
} from "@nodedc/ui-core";

export interface ToolbarControllerOptions {
  root: HTMLElement;
  placement?: ToolbarPlacement;
  background?: string;
  border?: string;
  outline?: string;
  accent?: string;
  minSize?: number;
  maxSize?: number;
  lensCount?: number;
  autoHide?: boolean;
}

export interface ToolbarController {
  refresh: () => void;
  update: (options: Partial<Omit<ToolbarControllerOptions, "root">>) => void;
  show: () => void;
  hide: () => void;
  reset: () => void;
  destroy: () => void;
}

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

export function createToolbarController(initial: ToolbarControllerOptions): ToolbarController {
  const root = initial.root;
  const toolbar = root.querySelector<HTMLElement>(".nodedc-toolbar");
  if (!toolbar) throw new Error("Toolbar root must contain .nodedc-toolbar");

  let options = { ...initial };
  let buttons: HTMLButtonElement[] = [];
  let settings = normalizeToolbarSettings(options);
  let visible = !options.autoHide;
  let hovering = false;
  let frame: number | null = null;
  let lastTimestamp = 0;
  let hideTimer: number | null = null;
  const motion = new Map<HTMLButtonElement, MotionState>();

  const getMotion = (button: HTMLButtonElement) => {
    let state = motion.get(button);
    if (!state) {
      state = { scale: 1, targetScale: 1, scaleVelocity: 0, shift: 0, targetShift: 0, shiftVelocity: 0 };
      motion.set(button, state);
    }
    return state;
  };

  const render = (timestamp = performance.now()) => {
    const dt = lastTimestamp ? (timestamp - lastTimestamp) / 1000 : 0.016;
    lastTimestamp = timestamp;
    let moving = false;
    buttons.forEach((button) => {
      const state = getMotion(button);
      [state.scale, state.scaleVelocity] = stepToolbarSpring(state.scale, state.scaleVelocity, state.targetScale, dt);
      [state.shift, state.shiftVelocity] = stepToolbarSpring(state.shift, state.shiftVelocity, state.targetShift, dt);
      if (
        Math.abs(state.scale - state.targetScale) < 0.001
        && Math.abs(state.scaleVelocity) < 0.001
        && Math.abs(state.shift - state.targetShift) < 0.05
        && Math.abs(state.shiftVelocity) < 0.05
      ) {
        state.scale = state.targetScale;
        state.scaleVelocity = 0;
        state.shift = state.targetShift;
        state.shiftVelocity = 0;
      } else {
        moving = true;
      }
      button.style.setProperty("--nodedc-toolbar-scale", state.scale.toFixed(4));
      button.style.setProperty("--nodedc-toolbar-shift", `${state.shift.toFixed(2)}px`);
    });
    if (moving) {
      frame = window.requestAnimationFrame(render);
    } else {
      frame = null;
      lastTimestamp = 0;
    }
  };

  const start = () => {
    if (frame != null) return;
    lastTimestamp = 0;
    frame = window.requestAnimationFrame(render);
  };

  const setTarget = (button: HTMLButtonElement, size: number, shift: number) => {
    const state = getMotion(button);
    state.targetScale = settings.minSize > 0 ? size / settings.minSize : 1;
    state.targetShift = shift;
    start();
  };

  const reset = () => {
    buttons.forEach((button) => setTarget(button, settings.minSize, 0));
    delete root.dataset.active;
  };

  const updateMagnification = (coordinate: number) => {
    const vertical = options.placement !== "bottom";
    const toolbarRect = toolbar.getBoundingClientRect();
    const entries = buttons
      .map((button) => ({
        button,
        center: (vertical ? toolbarRect.top + button.offsetTop + button.offsetHeight / 2 : toolbarRect.left + button.offsetLeft + button.offsetWidth / 2),
      }))
      .sort((a, b) => a.center - b.center);
    const targets = calculateToolbarTargets(entries.map((entry) => entry.center), coordinate, settings);
    entries.forEach(({ button }, index) => {
      const target = targets[index];
      if (target) setTarget(button, target.size, target.shift);
    });
  };

  const show = () => {
    if (hideTimer != null) {
      window.clearTimeout(hideTimer);
      hideTimer = null;
    }
    visible = true;
    root.dataset.visible = "true";
  };

  const hide = () => {
    if (!options.autoHide) return;
    visible = false;
    delete root.dataset.visible;
    reset();
  };

  const scheduleHide = () => {
    if (!options.autoHide || hideTimer != null) return;
    hideTimer = window.setTimeout(() => {
      hideTimer = null;
      if (!hovering) hide();
    }, NODEDC_TOOLBAR_DEFAULTS.hideDelay);
  };

  const applyOptions = () => {
    settings = normalizeToolbarSettings(options);
    const placement = options.placement ?? "left";
    root.dataset.placement = placement;
    root.dataset.autohide = options.autoHide ? "true" : "false";
    root.style.setProperty("--nodedc-toolbar-bg", options.background ?? NODEDC_TOOLBAR_DEFAULTS.background);
    root.style.setProperty("--nodedc-toolbar-border", options.border ?? NODEDC_TOOLBAR_DEFAULTS.border);
    root.style.setProperty("--nodedc-toolbar-outline", options.outline ?? NODEDC_TOOLBAR_DEFAULTS.outline);
    root.style.setProperty("--nodedc-toolbar-accent", options.accent ?? "#ff2f92");
    root.style.setProperty("--nodedc-toolbar-min-size", `${settings.minSize}px`);
    root.style.setProperty("--nodedc-toolbar-max-size", `${settings.maxSize}px`);
    root.style.setProperty("--nodedc-toolbar-radius", `${Math.max(7, Math.round(settings.minSize * 0.28))}px`);
    root.style.setProperty("--nodedc-toolbar-gap", `${settings.gap}px`);
    root.style.setProperty("--nodedc-toolbar-padding", `${NODEDC_TOOLBAR_DEFAULTS.padding}px`);
    if (options.autoHide) hide();
    else {
      visible = true;
      root.dataset.visible = "true";
    }
    reset();
  };

  const refresh = () => {
    buttons = Array.from(toolbar.querySelectorAll<HTMLButtonElement>(".nodedc-toolbar__button"));
    const live = new Set(buttons);
    Array.from(motion.keys()).forEach((button) => { if (!live.has(button)) motion.delete(button); });
    buttons.forEach(getMotion);
    reset();
  };

  const handleEnter = () => {
    hovering = true;
    show();
    root.dataset.active = "true";
  };
  const handleMove = (event: PointerEvent) => {
    hovering = true;
    show();
    root.dataset.active = "true";
    updateMagnification(options.placement === "bottom" ? event.clientX : event.clientY);
  };
  const handleLeave = () => {
    hovering = false;
    reset();
    scheduleHide();
  };
  const handleWindowMove = (event: PointerEvent) => {
    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 (hovering && !inside) handleLeave();
    if (!options.autoHide) return;
    const threshold = Math.max(32, settings.maxSize * 0.75);
    const nearEdge = options.placement === "right"
      ? event.clientX >= window.innerWidth - threshold
      : options.placement === "bottom"
        ? event.clientY >= window.innerHeight - threshold
        : event.clientX <= threshold;
    if (nearEdge) show();
    else if (visible && !hovering) scheduleHide();
  };

  toolbar.addEventListener("pointerenter", handleEnter);
  toolbar.addEventListener("pointermove", handleMove);
  toolbar.addEventListener("pointerleave", handleLeave);
  window.addEventListener("pointermove", handleWindowMove, { passive: true });
  refresh();
  applyOptions();

  return {
    refresh,
    update: (next) => { options = { ...options, ...next }; applyOptions(); },
    show,
    hide,
    reset,
    destroy: () => {
      toolbar.removeEventListener("pointerenter", handleEnter);
      toolbar.removeEventListener("pointermove", handleMove);
      toolbar.removeEventListener("pointerleave", handleLeave);
      window.removeEventListener("pointermove", handleWindowMove);
      if (frame != null) window.cancelAnimationFrame(frame);
      if (hideTimer != null) window.clearTimeout(hideTimer);
    },
  };
}
