/** Камера вида сцены (общая для Control и Presentation). */

export type SceneViewCamera = {
  /** Множитель поверх contain-fit; 1 = вся картинка влезает. */
  scale: number;
  /** Точка контента (0..1), которая держится в центре viewport. */
  ox: number;
  oy: number;
};

export type SceneViewState = {
  revision: number;
} & SceneViewCamera;

export type SceneViewEvent =
  | { kind: 'reset' }
  | { kind: 'set'; camera: SceneViewCamera };

export const DEFAULT_SCENE_VIEW_CAMERA: SceneViewCamera = {
  scale: 1,
  ox: 0.5,
  oy: 0.5,
};

export const SCENE_VIEW_MIN_SCALE = 1;
export const SCENE_VIEW_MAX_SCALE = 8;

export function clampSceneViewCamera(camera: SceneViewCamera): SceneViewCamera {
  const scale = Math.max(
    SCENE_VIEW_MIN_SCALE,
    Math.min(SCENE_VIEW_MAX_SCALE, Number.isFinite(camera.scale) ? camera.scale : 1),
  );
  if (scale <= 1.001) {
    return { ...DEFAULT_SCENE_VIEW_CAMERA };
  }
  const ox = Math.max(0, Math.min(1, Number.isFinite(camera.ox) ? camera.ox : 0.5));
  const oy = Math.max(0, Math.min(1, Number.isFinite(camera.oy) ? camera.oy : 0.5));
  return { scale, ox, oy };
}

/**
 * Зум относительно точки курсора в координатах хоста (px).
 * `containW/H` — размер contain-fit без зума.
 */
export function sceneViewZoomAt(
  camera: SceneViewCamera,
  args: {
    hostW: number;
    hostH: number;
    containW: number;
    containH: number;
    hostX: number;
    hostY: number;
    factor: number;
  },
): SceneViewCamera {
  const hostW = Math.max(1, args.hostW);
  const hostH = Math.max(1, args.hostH);
  const containW = Math.max(1, args.containW);
  const containH = Math.max(1, args.containH);
  const cur = clampSceneViewCamera(camera);
  const displayW = containW * cur.scale;
  const displayH = containH * cur.scale;
  const left = hostW / 2 - cur.ox * displayW;
  const top = hostH / 2 - cur.oy * displayH;
  const ix = (args.hostX - left) / Math.max(1e-6, displayW);
  const iy = (args.hostY - top) / Math.max(1e-6, displayH);
  const nextScale = Math.max(
    SCENE_VIEW_MIN_SCALE,
    Math.min(SCENE_VIEW_MAX_SCALE, cur.scale * args.factor),
  );
  if (nextScale <= 1.001) return { ...DEFAULT_SCENE_VIEW_CAMERA };
  const nextDisplayW = containW * nextScale;
  const nextDisplayH = containH * nextScale;
  const ox = ix - (args.hostX - hostW / 2) / Math.max(1e-6, nextDisplayW);
  const oy = iy - (args.hostY - hostH / 2) / Math.max(1e-6, nextDisplayH);
  return clampSceneViewCamera({ scale: nextScale, ox, oy });
}

/** Пан на `dx/dy` пикселей хоста (тянуть картинку в сторону движения указателя). */
export function sceneViewPanBy(
  camera: SceneViewCamera,
  args: { containW: number; containH: number; dx: number; dy: number },
): SceneViewCamera {
  const cur = clampSceneViewCamera(camera);
  if (cur.scale <= 1.001) return { ...DEFAULT_SCENE_VIEW_CAMERA };
  const displayW = Math.max(1e-6, args.containW * cur.scale);
  const displayH = Math.max(1e-6, args.containH * cur.scale);
  return clampSceneViewCamera({
    scale: cur.scale,
    ox: cur.ox - args.dx / displayW,
    oy: cur.oy - args.dy / displayH,
  });
}
