export type Product3DFocusPoint = [number, number, number];

export interface Product3DFocusCameraState {
  target: Product3DFocusPoint;
  position: Product3DFocusPoint;
  distance: number;
}

const point = (value: unknown, fallback: Product3DFocusPoint): Product3DFocusPoint => {
  if (!Array.isArray(value) || value.length < 3) return [...fallback];
  const next = value.slice(0, 3).map(Number);
  return next.every(Number.isFinite) ? next as Product3DFocusPoint : [...fallback];
};

const subtract = (left: Product3DFocusPoint, right: Product3DFocusPoint): Product3DFocusPoint => [
  left[0] - right[0],
  left[1] - right[1],
  left[2] - right[2],
];

const add = (left: Product3DFocusPoint, right: Product3DFocusPoint): Product3DFocusPoint => [
  left[0] + right[0],
  left[1] + right[1],
  left[2] + right[2],
];

const scale = (value: Product3DFocusPoint, amount: number): Product3DFocusPoint => [
  value[0] * amount,
  value[1] * amount,
  value[2] * amount,
];

const length = (value: Product3DFocusPoint): number => Math.sqrt(
  value[0] * value[0] + value[1] * value[1] + value[2] * value[2],
);

const normalize = (value: Product3DFocusPoint, fallback: Product3DFocusPoint): Product3DFocusPoint => {
  const valueLength = length(value);
  if (valueLength > 0.000001) return scale(value, 1 / valueLength);
  const fallbackLength = length(fallback);
  return fallbackLength > 0.000001 ? scale(fallback, 1 / fallbackLength) : [0, 0, 1];
};

const lerp = (from: Product3DFocusPoint, to: Product3DFocusPoint, amount: number): Product3DFocusPoint => [
  from[0] + (to[0] - from[0]) * amount,
  from[1] + (to[1] - from[1]) * amount,
  from[2] + (to[2] - from[2]) * amount,
];

/**
 * Resolve an automatic part focus as a pure orbit: zoom stays unchanged and a
 * single-product scene keeps its current spatial target. Multi-product scenes
 * may pan their target partway toward the selected product.
 */
export const resolveProduct3DPartFocusCamera = (input: {
  currentTarget: Product3DFocusPoint;
  currentPosition: Product3DFocusPoint;
  focusDirection: Product3DFocusPoint;
  productCount?: number;
  groupCenter?: Product3DFocusPoint | null;
  productCenter?: Product3DFocusPoint | null;
  multiProductTargetBlend?: number;
}): Product3DFocusCameraState => {
  const currentTarget = point(input.currentTarget, [0, 0, 0]);
  const currentPosition = point(input.currentPosition, [0, 0, 1]);
  const currentOffset = subtract(currentPosition, currentTarget);
  const distance = Math.max(0.01, length(currentOffset));
  const direction = normalize(point(input.focusDirection, currentOffset), currentOffset);

  let target = currentTarget;
  if (Number(input.productCount ?? 0) > 1 && input.productCenter) {
    const groupCenter = point(input.groupCenter, currentTarget);
    const productCenter = point(input.productCenter, groupCenter);
    const blend = Math.max(0, Math.min(1, Number(input.multiProductTargetBlend ?? 0.58) || 0));
    target = lerp(groupCenter, productCenter, blend);
  }

  return {
    target,
    position: add(target, scale(direction, distance)),
    distance,
  };
};

/** Return from a product/part focus to the complete group without changing zoom or orientation. */
export const resolveProduct3DRootFocusCamera = (input: {
  currentTarget: Product3DFocusPoint;
  currentPosition: Product3DFocusPoint;
  groupCenter: Product3DFocusPoint;
}): Product3DFocusCameraState => {
  const currentTarget = point(input.currentTarget, [0, 0, 0]);
  const currentPosition = point(input.currentPosition, [0, 0, 1]);
  const groupCenter = point(input.groupCenter, [0, 0, 0]);
  const offset = subtract(currentPosition, currentTarget);
  const distance = Math.max(0.01, length(offset));
  const resolvedOffset = length(offset) > 0.000001 ? offset : [0, 0, distance] as Product3DFocusPoint;
  return {
    target: groupCenter,
    position: add(groupCenter, resolvedOffset),
    distance,
  };
};
