
import { asuka2DPerfDebug, isAsuka2DPerfDebugEnabled } from '../historyDebug';
import { refreshFabricVariationFrame } from '../canvas/fabricVariationGeometry';

const perfNow = () => {
  try { return (typeof performance !== 'undefined' && typeof performance.now === 'function') ? performance.now() : Date.now(); } catch { return Date.now(); }
};
const roundPerf = (value: number) => Math.round(Number(value || 0) * 100) / 100;


export const attachObjectModifiedRuntime = (config: {
  canvas: any;
  instanceId: string;
  useAsukaStore: any;
  ensureObjectId: (target: any) => string;
  isListenerEnabled: (id: string) => boolean;
  isPanning: () => boolean;
  getTransformState: () => { isTransforming: boolean; currentTransformObjectId: string | null; currentTransformKind: string; isLiveTransform: boolean };
  setTransformState: (patch: Partial<{ isTransforming: boolean; currentTransformObjectId: string | null; currentTransformKind: string; isLiveTransform: boolean }>) => void;
  emitBefore: (name: string, payload?: any, meta?: any) => void;
  emitAfter: (name: string, payload?: any, meta?: any) => void;
  emitEvent: (name: string, payload?: any, meta?: any) => void;
  emitSelectionChange: (reason: string) => void;
  classifyObjectPosition: (target: any, zone: any) => any;
  getZoneForObject: (target: any) => any;
  updateObjectStrokeForZone: (target: any, status: any) => void;
  revertToLastGoodPositionIfNeeded: (target: any, status: any) => void;
  commitPlacement?: () => void;
  snapLinesRuntime: { hide: () => void };
  snapConfig: { enabled: boolean };
  applySnapping: (target: any) => void;
  clampObjectInsideBoundary: (target: any) => void;
  applySoftBoundaryFeel: (target: any) => void;
  resolveCollisions: (target: any) => void;
  commitHistoryChange: (options?: any) => void;
  springBackToBoundary: (target: any) => void;
  scheduleHistorySnapshot: (options?: any) => void;
  syncDocumentObjectsForView: () => void;
  applyFixedConstraintsToObject?: (target: any, constraints?: any) => void;
  fitObjectInsideBoundaryPreserveCenter?: (target: any, centerOverride?: { x: number; y: number } | null) => boolean;
}) => {
  const {
    canvas,
    instanceId,
    useAsukaStore,
    ensureObjectId,
    isListenerEnabled,
    isPanning,
    getTransformState,
    setTransformState,
    emitBefore,
    emitAfter,
    emitEvent,
    emitSelectionChange,
    classifyObjectPosition,
    getZoneForObject,
    updateObjectStrokeForZone,
    revertToLastGoodPositionIfNeeded,
    commitPlacement,
    snapLinesRuntime,
    clampObjectInsideBoundary,
    applySoftBoundaryFeel,
    resolveCollisions,
    commitHistoryChange,
    springBackToBoundary,
    scheduleHistorySnapshot,
    syncDocumentObjectsForView,
    applyFixedConstraintsToObject,
    fitObjectInsideBoundaryPreserveCenter,
  } = config;

  const refreshElementVariationFrame = (target: any) => {
    if (!target?._asukaElementIsInstance) return;
    try { refreshFabricVariationFrame(target); } catch {}
  };

  const maybeOpenElementVariationPicker = (target: any) => {
    if (!isListenerEnabled('clickElement') || isPanning() || !target || !(target as any)._asukaElementIsInstance || (target as any).evented === false || (target as any).selectable === true) return;
    try {
      const objectId = ensureObjectId(target as any);
      const elementName = String((target as any)._asukaElementName ?? '');
      const elementId = String((target as any)._asukaElementId ?? '').trim();
      if (!elementId && !elementName) return;
      const store = useAsukaStore.getState() as any;
      store.openElementVariationPicker?.({ objectId, elementId, elementName, variationId: String((target as any)._asukaElementVariationId ?? ''), objectSrc: String((target as any)?.getSrc?.() ?? (target as any)?.src ?? '') });
    } catch (_e) {
      void 0;
    }
  };

  const maybeRunInertia = (target: any): boolean => {
    try {
      const st: any = useAsukaStore.getState?.();
      const inertiaEnabled = st?.listenerSettings?.inertia !== false;
      const state = getTransformState();
      if (!inertiaEnabled || !target || state.currentTransformKind !== 'moving') return false;
      const v = (target as any).__asukaMoveVel;
      const minSpeed = Number(st?.listenerSettings?.inertiaMinSpeed ?? 0.25);
      const friction = Math.min(0.999, Math.max(0.5, Number(st?.listenerSettings?.inertiaFriction ?? 0.92)));
      const maxMs = Math.max(50, Number(st?.listenerSettings?.inertiaMaxMs ?? 450));
      const speed = typeof v?.speed === 'number' ? v.speed : 0;
      if (speed < minSpeed) return false;

      const startT = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
      let vx = Number(v.vx) || 0;
      let vy = Number(v.vy) || 0;
      const step = () => {
        const now = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
        const dt = Math.min(32, Math.max(8, now - (target as any).__asukaInertiaLastT || 16));
        (target as any).__asukaInertiaLastT = now;
        target.left = (target.left ?? 0) + (vx * dt);
        target.top = (target.top ?? 0) + (vy * dt);
        // Post-release motion should never resurrect snapping guides. We keep the
        // inertia feel, but disable snap alignment/visuals once the pointer is up.
        try { (target as any).__asukaSnapPrev = { x: null, y: null }; } catch {}
        try { snapLinesRuntime.hide(); } catch {}
        try {
          const fixedConstraints: any = (target as any)?._asukaFixedSlotConstraints ?? (target as any)?._asukaFixedConstraints;
          if (fixedConstraints?.lockX || fixedConstraints?.lockY) {
            const center = target.getCenterPoint?.();
            const nextCenter = {
              x: fixedConstraints?.lockX && Number.isFinite((target as any).__asukaFixedLockedCenterX)
                ? Number((target as any).__asukaFixedLockedCenterX)
                : Number(center?.x ?? target.left ?? 0),
              y: fixedConstraints?.lockY && Number.isFinite((target as any).__asukaFixedLockedCenterY)
                ? Number((target as any).__asukaFixedLockedCenterY)
                : Number(center?.y ?? target.top ?? 0),
            };
            if (fixedConstraints?.lockX) vx = 0;
            if (fixedConstraints?.lockY) vy = 0;
            try { target.setPositionByOrigin?.(nextCenter, 'center', 'center'); } catch { target.set?.({ left: nextCenter.x, top: nextCenter.y }); }
          }
        } catch {}
        try {
          if (isListenerEnabled('preventBoundingBoxOvertaking')) clampObjectInsideBoundary(target);
          else applySoftBoundaryFeel(target);
        } catch {}
        try { resolveCollisions(target); } catch {}
        try { target.setCoords?.(); } catch {}
        try { canvas.requestRenderAll?.(); } catch {}
        vx *= friction;
        vy *= friction;
        const sp = Math.hypot(vx, vy);
        const elapsed = now - startT;
        if (sp < 0.01 || elapsed > maxMs) {
          try { (target as any).__asukaInertiaLastT = null; } catch {}
          refreshElementVariationFrame(target);
          try { commitHistoryChange({ force: true }); } catch {}
          try { canvas.requestRenderAll?.(); } catch {}
          return;
        }
        requestAnimationFrame(step);
      };
      requestAnimationFrame(step);
      return true;
    } catch {
      return false;
    }
  };

  const onObjectModified = (event: any) => {
    const perfEnabled = isAsuka2DPerfDebugEnabled();
    const perfStart = perfEnabled ? perfNow() : 0;
    const timings: Record<string, number> = {};
    const mark = (name: string, startedAt: number) => { if (perfEnabled) timings[name] = roundPerf(perfNow() - startedAt); };
    const target = event?.target;
    setTransformState({ isLiveTransform: false });
    const pickerStart = perfEnabled ? perfNow() : 0;
    maybeOpenElementVariationPicker(target);
    mark('openElementVariationPickerMs', pickerStart);

    if (target) {
      const objectId = ensureObjectId(target);
      const fixedScalingCenter = (target as any)?.__asukaScalingCenter ?? (target as any)?.__asukaFixedScalingCenter ?? null;
      try {
        const fixedConstraints = (target as any)?._asukaFixedSlotConstraints ?? (target as any)?._asukaFixedConstraints;
        if (fixedConstraints) {
          applyFixedConstraintsToObject?.(target, fixedConstraints);
        }
        if (fixedScalingCenter) {
          fitObjectInsideBoundaryPreserveCenter?.(target, fixedScalingCenter);
        }
      } catch {}
      if (isListenerEnabled('constraints') && !(target as any)._asukaIgnoreDrawingZones) {
        const status = classifyObjectPosition(target, getZoneForObject(target));
        if (isListenerEnabled('adjustBoundingBoxColors') || isListenerEnabled('boundingBoxesBorders')) {
          updateObjectStrokeForZone(target, status);
        }
        if (isListenerEnabled('preventBoundingBoxExit')) {
          revertToLastGoodPositionIfNeeded(target, status);
        }
      }
      try { (target as any).__asukaSnapPrev = { x: null, y: null }; } catch {}
      emitBefore('canvas.object.update', { objectId }, { instanceId });
      emitAfter('canvas.object.update', { objectId }, { instanceId });
    }

    const inertiaStart = perfEnabled ? perfNow() : 0;
    const inertiaStarted = maybeRunInertia(target);
    mark('maybeRunInertiaMs', inertiaStart);

    try {
      if (target && !isListenerEnabled('preventBoundingBoxOvertaking') && !(target as any)?.__asukaScalingCenter) {
        const fixedConstraints = (target as any)?._asukaFixedSlotConstraints ?? (target as any)?._asukaFixedConstraints;
        if (!fixedConstraints) springBackToBoundary(target);
      }
    } catch {}

    try { delete (target as any).__asukaScalingCenter; } catch {}
    try { delete (target as any).__asukaFixedScalingCenter; } catch {}
    snapLinesRuntime.hide();
    if (!inertiaStarted) refreshElementVariationFrame(target);
    const scheduleStart = perfEnabled ? perfNow() : 0;
    if (!inertiaStarted) scheduleHistorySnapshot({ force: true, reason: 'object:modified' });
    mark('scheduleHistorySnapshotMs', scheduleStart);
    const syncStart = perfEnabled ? perfNow() : 0;
    try { syncDocumentObjectsForView(); } catch {}
    mark('syncDocumentObjectsForViewMs', syncStart);
    const renderStart = perfEnabled ? perfNow() : 0;
    try { canvas.requestRenderAll?.(); } catch {}
    mark('requestRenderAllMs', renderStart);

    const state = getTransformState();
    if (state.isTransforming && state.currentTransformObjectId) {
      emitEvent('canvas.transform.end', { objectId: state.currentTransformObjectId, kind: state.currentTransformKind }, { instanceId });
    }
    try { commitPlacement?.(); } catch {}

    setTransformState({ isTransforming: false, currentTransformObjectId: null, currentTransformKind: 'unknown' });
    const selectionStart = perfEnabled ? perfNow() : 0;
    emitSelectionChange('object:modified');
    mark('emitSelectionChangeMs', selectionStart);

    asuka2DPerfDebug('designer.fabric.objectModified', {
      durationMs: roundPerf(perfNow() - perfStart),
      timings,
      targetType: String(target?.type ?? 'unknown'),
      objectId: (() => { try { return target ? ensureObjectId(target) : null; } catch { return null; } })(),
      canvasObjects: (() => { try { return Number(canvas.getObjects?.().length ?? 0); } catch { return null; } })(),
    });
  };

  canvas.on('object:modified', onObjectModified);

  return {
    onObjectModified,
    maybeRunInertia,
    maybeOpenElementVariationPicker,
  };
};
