import { FABRIC_VARIATION_FRAME_PROPERTY } from './canvas/fabricVariationGeometry';

export const DEFAULT_FABRIC_ELEMENT_TARGET_FIND_TOLERANCE = 2;
export const FABRIC_INTERACTION_LOCK_PROPERTY = '_asukaInteractionLocked';
export const FABRIC_LAYER_LOCK_PROPERTY = '_asukaLocked';
export const FABRIC_LAYER_LOCK_RESTORE_PROPERTY = '_asukaLockRestoreState';

export interface FabricLayerInteractionState {
  selectable: boolean;
  evented: boolean;
  hasControls: boolean;
  hasBorders: boolean;
  hoverCursor: unknown;
  perPixelTargetFind: boolean;
  lockMovementX: boolean;
  lockMovementY: boolean;
  lockScalingX: boolean;
  lockScalingY: boolean;
  lockRotation: boolean;
}

const ELEMENT_SNAPSHOT_PROPERTIES = [
  '_asukaObjectId',
  '_asukaElementIsInstance',
  '_asukaElementId',
  '_asukaElementFamilyId',
  '_asukaElementName',
  '_asukaElementVariationId',
  FABRIC_VARIATION_FRAME_PROPERTY,
  'selectable',
  'evented',
  'hasControls',
  'hasBorders',
  'hoverCursor',
  'perPixelTargetFind',
  'targetFindTolerance',
  FABRIC_INTERACTION_LOCK_PROPERTY,
  FABRIC_LAYER_LOCK_PROPERTY,
  FABRIC_LAYER_LOCK_RESTORE_PROPERTY,
  'lockMovementX',
  'lockMovementY',
  'lockScalingX',
  'lockScalingY',
  'lockRotation',
] as const;

export function isFabricElementObject(object: any): boolean {
  return Boolean(
    object?._asukaElementIsInstance
    || String(object?._asukaElementId ?? '').trim()
    || String(object?._asukaElementName ?? '').trim(),
  );
}

export function configureFabricElementInteraction(
  object: any,
  options: { selectable?: boolean; evented?: boolean; targetFindTolerance?: number } = {},
): any {
  if (!object || !isFabricElementObject(object)) return object;

  const selectable = options.selectable ?? object.selectable === true;
  const evented = options.evented ?? object.evented !== false;
  const rawTolerance = Number(options.targetFindTolerance ?? object.targetFindTolerance);
  const targetFindTolerance = Number.isFinite(rawTolerance) && rawTolerance >= 0
    ? rawTolerance
    : DEFAULT_FABRIC_ELEMENT_TARGET_FIND_TOLERANCE;

  object._asukaElementIsInstance = true;
  object.selectable = selectable;
  object.evented = evented;
  object.hasControls = selectable;
  object.hasBorders = selectable;
  object.hoverCursor = evented ? (selectable ? 'move' : 'pointer') : 'default';
  object.perPixelTargetFind = evented;
  object.targetFindTolerance = targetFindTolerance;
  return object;
}

export function isFabricObjectInteractionLocked(object: any): boolean {
  return object?.[FABRIC_INTERACTION_LOCK_PROPERTY] === true;
}

export function lockFabricObjectInteraction(object: any): any {
  if (!object) return object;
  object[FABRIC_INTERACTION_LOCK_PROPERTY] = true;
  object.selectable = false;
  object.evented = false;
  object.hasControls = false;
  object.hasBorders = false;
  object.hoverCursor = 'default';
  object.perPixelTargetFind = false;
  return object;
}

export function isFabricObjectLayerLocked(object: any): boolean {
  return object?.[FABRIC_LAYER_LOCK_PROPERTY] === true;
}

export function captureFabricLayerInteractionState(object: any): FabricLayerInteractionState {
  return {
    selectable: object?.selectable !== false,
    evented: object?.evented !== false,
    hasControls: object?.hasControls !== false,
    hasBorders: object?.hasBorders !== false,
    hoverCursor: object?.hoverCursor,
    perPixelTargetFind: object?.perPixelTargetFind === true,
    lockMovementX: object?.lockMovementX === true,
    lockMovementY: object?.lockMovementY === true,
    lockScalingX: object?.lockScalingX === true,
    lockScalingY: object?.lockScalingY === true,
    lockRotation: object?.lockRotation === true,
  };
}

export function getFabricLayerLockRestoreState(object: any): FabricLayerInteractionState | null {
  const value = object?.[FABRIC_LAYER_LOCK_RESTORE_PROPERTY];
  return value && typeof value === 'object' ? value as FabricLayerInteractionState : null;
}

export function resolveFabricLayerUnlockState(
  object: any,
  fallback: FabricLayerInteractionState,
): FabricLayerInteractionState {
  const saved = getFabricLayerLockRestoreState(object);
  return saved ? { ...fallback, ...saved } : fallback;
}

export function lockFabricLayerInteraction(object: any): any {
  if (!object) return object;
  object[FABRIC_LAYER_LOCK_PROPERTY] = true;
  object.selectable = false;
  object.evented = false;
  object.hasControls = false;
  object.hasBorders = false;
  object.hoverCursor = 'default';
  object.perPixelTargetFind = false;
  object.lockMovementX = true;
  object.lockMovementY = true;
  object.lockScalingX = true;
  object.lockScalingY = true;
  object.lockRotation = true;
  return object;
}

export function restoreFabricSnapshotInteractionPolicy(serialized: any, object: any): any {
  if (!serialized || !object) return object;

  if (isFabricObjectLayerLocked(serialized) || isFabricObjectLayerLocked(object)) {
    return lockFabricLayerInteraction(object);
  }

  // A configurable element deliberately uses selectable=false/evented=true so
  // its opaque pixels open Variations without exposing Fabric transform tools.
  if (isFabricElementObject(serialized) || isFabricElementObject(object)) {
    try { delete object[FABRIC_INTERACTION_LOCK_PROPERTY]; } catch {}
    return object;
  }

  const locked = serialized[FABRIC_INTERACTION_LOCK_PROPERTY] === true
    || (serialized.selectable === false && serialized.evented === false);
  if (locked) return lockFabricObjectInteraction(object);

  try { delete object[FABRIC_INTERACTION_LOCK_PROPERTY]; } catch {}
  return object;
}

export function restoreFabricElementSnapshotProperties(serialized: any, object: any): any {
  if (!serialized || !object) return object;
  for (const property of ELEMENT_SNAPSHOT_PROPERTIES) {
    if (serialized[property] !== undefined) object[property] = serialized[property];
  }
  const restored = configureFabricElementInteraction(object);
  return restoreFabricSnapshotInteractionPolicy(serialized, restored);
}

export function resolveContextualElementVariationScope(
  engineKind: unknown,
  _objectId: unknown,
): 'object' | 'document' {
  if (String(engineKind ?? '').trim().toLowerCase() === '3d') return 'object';
  // Fabric elements can represent the same configurable part across multiple
  // 2D views and a linked 3D product. Document scope performs that mapping via
  // element family/sync keys; it never relies on a generic variation label.
  return 'document';
}
