

export interface AsukaUiScalingOptions {
  /** Keep Fabric controls readable when the canvas is visually shrunk with CSS. */
  keepControlsReadable?: boolean;
  /** Keep helper lines / overlays readable when the canvas is visually shrunk with CSS. */
  keepHelpersReadable?: boolean;
  /** Base corner size used by Asuka custom controls before CSS compensation. */
  baseControlSize?: number;
  /** Base touch target size used by Asuka custom controls before CSS compensation. */
  baseTouchControlSize?: number;
  /** Minimum visible helper stroke width in canvas units after compensation. */
  minHelperStrokeWidth?: number;
  /** Maximum multiplier applied when the canvas becomes very small. */
  maxCompensationMultiplier?: number;
}

export const DEFAULT_ASUKA_UI_SCALING: Required<AsukaUiScalingOptions> = {
  keepControlsReadable: true,
  keepHelpersReadable: true,
  baseControlSize: 26,
  baseTouchControlSize: 40,
  minHelperStrokeWidth: 1,
  maxCompensationMultiplier: 4,
};

function clamp(n: number, min: number, max: number) {
  return Math.min(max, Math.max(min, n));
}

export function resolveAsukaUiScalingOptions(input?: Partial<AsukaUiScalingOptions> | null): Required<AsukaUiScalingOptions> {
  return {
    ...DEFAULT_ASUKA_UI_SCALING,
    ...(input ?? {}),
  };
}

export function getCanvasCssScale(canvas: any): number {
  try {
    const internalWidth = Math.max(1, Number(canvas?.getWidth?.() ?? canvas?.width ?? 1) || 1);
    const lower = canvas?.getElement?.() ?? canvas?.lowerCanvasEl ?? null;
    const wrapper = canvas?.wrapperEl ?? null;
    const measuredWidth = Number(lower?.clientWidth || wrapper?.clientWidth || lower?.getBoundingClientRect?.()?.width || wrapper?.getBoundingClientRect?.()?.width || internalWidth) || internalWidth;
    if (!Number.isFinite(measuredWidth) || measuredWidth <= 0) return 1;
    return clamp(measuredWidth / internalWidth, 0.01, 100);
  } catch {
    return 1;
  }
}

export function getCanvasViewportZoom(canvas: any): number {
  try {
    const zoom = Number(canvas?.getZoom?.() ?? canvas?.viewportTransform?.[0] ?? 1) || 1;
    return clamp(zoom, 0.01, 100);
  } catch {
    return 1;
  }
}

/**
 * Return the effective on-screen scale applied to document-space units.
 * This combines the Fabric viewport zoom and the responsive CSS scale applied
 * by the shell, so editor-only helpers can keep a stable size on screen.
 */
export function getCanvasVisualScale(canvas: any): number {
  const cssScale = getCanvasCssScale(canvas);
  const viewportZoom = getCanvasViewportZoom(canvas);
  return clamp(cssScale * viewportZoom, 0.01, 100);
}

function getCompensationMultiplier(canvas: any, options?: Partial<AsukaUiScalingOptions> | null): number {
  const cfg = resolveAsukaUiScalingOptions(options ?? (canvas as any)?.__asukaUiScaling ?? null);
  const visualScale = getCanvasVisualScale(canvas);
  if (!Number.isFinite(visualScale) || visualScale <= 0) return 1;
  const maxMultiplier = Math.max(1, Number(cfg.maxCompensationMultiplier) || 4);
  return clamp(1 / visualScale, 1 / maxMultiplier, maxMultiplier);
}

function ensureBaseControlMetrics(control: any, options?: Partial<AsukaUiScalingOptions> | null) {
  if (!control || typeof control !== 'object') return;
  const cfg = resolveAsukaUiScalingOptions(options);
  if (typeof control.__asukaBaseCornerSize !== 'number') {
    control.__asukaBaseCornerSize = Number(control.cornerSize) || cfg.baseControlSize;
  }
  if (typeof control.__asukaBaseTouchCornerSize !== 'number') {
    control.__asukaBaseTouchCornerSize = Number(control.touchCornerSize) || cfg.baseTouchControlSize;
  }
}

function ensureBaseVisualMetrics(obj: any) {
  if (!obj || typeof obj !== 'object') return null;
  if (!obj.__asukaUiBaseVisualStyle || typeof obj.__asukaUiBaseVisualStyle !== 'object') {
    obj.__asukaUiBaseVisualStyle = {
      strokeWidth: typeof obj.strokeWidth === 'number' ? Number(obj.strokeWidth) : undefined,
      fontSize: typeof obj.fontSize === 'number' ? Number(obj.fontSize) : undefined,
      radius: typeof obj.radius === 'number' ? Number(obj.radius) : undefined,
      rx: typeof obj.rx === 'number' ? Number(obj.rx) : undefined,
      ry: typeof obj.ry === 'number' ? Number(obj.ry) : undefined,
      strokeDashArray: Array.isArray(obj.strokeDashArray) ? obj.strokeDashArray.slice() : undefined,
      borderScaleFactor: typeof obj.borderScaleFactor === 'number' ? Number(obj.borderScaleFactor) : undefined,
      padding: typeof obj.padding === 'number' ? Number(obj.padding) : undefined,
    };
  }
  return obj.__asukaUiBaseVisualStyle;
}

export function syncAsukaObjectControlsForCssScale(obj: any, options?: Partial<AsukaUiScalingOptions> | null) {
  if (!obj || typeof obj !== 'object') return;
  const canvas = obj.canvas;
  if (!canvas) return;
  const cfg = resolveAsukaUiScalingOptions(options ?? (canvas as any)?.__asukaUiScaling ?? null);
  if (!cfg.keepControlsReadable) return;

  const multiplier = getCompensationMultiplier(canvas, cfg);
  const base = ensureBaseVisualMetrics(obj);
  try {
    if (base && typeof base.borderScaleFactor === 'number') {
      obj.borderScaleFactor = Math.max(0.5, base.borderScaleFactor * multiplier);
    } else {
      obj.borderScaleFactor = Math.max(0.5, multiplier);
    }
  } catch {}
  try {
    if (base && typeof base.padding === 'number' && base.padding > 0) {
      obj.padding = base.padding * multiplier;
    }
  } catch {}

  const controls = obj.controls;
  if (!controls || typeof controls !== 'object') return;
  for (const key of Object.keys(controls)) {
    const control = controls[key];
    if (!control || typeof control !== 'object') continue;
    ensureBaseControlMetrics(control, cfg);
    try {
      const baseCornerSize = Number(control.__asukaBaseCornerSize) || cfg.baseControlSize;
      control.cornerSize = baseCornerSize * multiplier;
      const baseTouch = Number(control.__asukaBaseTouchCornerSize) || cfg.baseTouchControlSize;
      control.touchCornerSize = Math.max(control.cornerSize, baseTouch * multiplier);
    } catch {}
  }
}

function shouldScaleAsHelper(obj: any): boolean {
  if (!obj || typeof obj !== 'object') return false;
  if (String((obj as any)?.itemGroup ?? '') === 'Tools') return true;
  const role = String((obj as any)?._asukaRole ?? '').trim();
  if (!role) return !!obj.excludeFromExport;
  return role !== 'user';
}

export function syncAsukaHelperVisualScale(obj: any, options?: Partial<AsukaUiScalingOptions> | null, canvasArg?: any) {
  if (!obj || typeof obj !== 'object') return;
  const canvas = canvasArg ?? obj.canvas;
  if (!canvas) return;
  const cfg = resolveAsukaUiScalingOptions(options ?? (canvas as any)?.__asukaUiScaling ?? null);
  if (!cfg.keepHelpersReadable) return;
  const multiplier = getCompensationMultiplier(canvas, cfg);
  const base = ensureBaseVisualMetrics(obj);
  if (base) {
    try {
      if (typeof base.strokeWidth === 'number') obj.strokeWidth = Math.max(cfg.minHelperStrokeWidth, base.strokeWidth * multiplier);
    } catch {}
    try {
      if (typeof base.fontSize === 'number') obj.fontSize = Math.max(8, base.fontSize * multiplier);
    } catch {}
    try {
      if (typeof base.radius === 'number') obj.radius = Math.max(1, base.radius * multiplier);
    } catch {}
    try {
      if (typeof base.rx === 'number') obj.rx = Math.max(0, base.rx * multiplier);
    } catch {}
    try {
      if (typeof base.ry === 'number') obj.ry = Math.max(0, base.ry * multiplier);
    } catch {}
    try {
      if (Array.isArray(base.strokeDashArray)) obj.strokeDashArray = base.strokeDashArray.map((v: any) => Math.max(1, Number(v || 0) * multiplier));
    } catch {}
  }
  try { obj.dirty = true; } catch {}
  try {
    const children = typeof obj.getObjects === 'function' ? obj.getObjects() : [];
    if (Array.isArray(children)) {
      for (const child of children) syncAsukaHelperVisualScale(child, cfg, canvas);
    }
  } catch {}
}

export function syncCanvasUiScaling(canvas: any, options?: Partial<AsukaUiScalingOptions> | null) {
  if (!canvas) return;
  const cfg = resolveAsukaUiScalingOptions(options ?? (canvas as any)?.__asukaUiScaling ?? null);
  try { (canvas as any).__asukaUiScaling = cfg; } catch {}
  const objects = canvas.getObjects?.() ?? [];
  for (const obj of objects) {
    if (!obj) continue;
    if (shouldScaleAsHelper(obj)) syncAsukaHelperVisualScale(obj, cfg, canvas);
    else syncAsukaObjectControlsForCssScale(obj, cfg);
  }
  try {
    const active = canvas.getActiveObject?.();
    if (active) syncAsukaObjectControlsForCssScale(active, cfg);
  } catch {}
}
