export type LivePreviewMode = "move" | "resize-start" | "resize-end";

export type LivePreviewTarget = {
  element: HTMLElement;
  baseLeft: number;
  baseWidth: number;
  baseTransform: string;
};

export type LivePreviewSession = {
  mode: LivePreviewMode;
  cellWidth: number;
  targets: LivePreviewTarget[];
  pointerDeltaX: number;
  daysDelta: number;
  frame: number | null;
};

export function toPxValue(value: string): number {
  const parsed = Number.parseFloat(value);
  return Number.isFinite(parsed) ? parsed : 0;
}

export function joinTransforms(...parts: Array<string | undefined>): string {
  return parts
    .map((part) => part?.trim())
    .filter((part): part is string => Boolean(part) && part !== "none")
    .join(" ");
}

export function captureLivePreviewTargets(elements: Iterable<HTMLElement>): LivePreviewTarget[] {
  const seen = new Set<HTMLElement>();
  const targets: LivePreviewTarget[] = [];

  for (const element of elements) {
    if (seen.has(element)) continue;
    seen.add(element);
    targets.push({
      element,
      baseLeft: toPxValue(element.style.left),
      baseWidth: toPxValue(element.style.width),
      baseTransform: element.style.transform,
    });
  }

  return targets;
}

function renderLivePreview(session: LivePreviewSession) {
  const pointerOffsetX = session.pointerDeltaX - session.daysDelta * session.cellWidth;

  for (const target of session.targets) {
    let left = target.baseLeft;
    let width = target.baseWidth;

    if (session.mode === "move") {
      left += session.daysDelta * session.cellWidth;
    } else if (session.mode === "resize-start") {
      left += session.daysDelta * session.cellWidth;
      width = Math.max(session.cellWidth, width - session.daysDelta * session.cellWidth);
    } else {
      width = Math.max(session.cellWidth, width + session.daysDelta * session.cellWidth);
    }

    if (session.mode === "resize-start") {
      const nextWidth = width - pointerOffsetX;
      if (nextWidth < session.cellWidth) {
        left += width - session.cellWidth;
        width = session.cellWidth;
      } else {
        left += pointerOffsetX;
        width = nextWidth;
      }
    } else if (session.mode === "resize-end") {
      width = Math.max(session.cellWidth, width + pointerOffsetX);
    }

    target.element.style.left = `${left}px`;
    target.element.style.width = `${Math.max(session.cellWidth, width)}px`;
    target.element.style.transform = joinTransforms(
      target.baseTransform,
      session.mode === "move" && pointerOffsetX !== 0
        ? `translateX(${pointerOffsetX}px)`
        : undefined,
    );
  }
}

export function scheduleLivePreview(session: LivePreviewSession) {
  if (session.frame !== null) return;
  session.frame = requestAnimationFrame(() => {
    session.frame = null;
    renderLivePreview(session);
  });
}

export function clearLivePreview(session: LivePreviewSession | null) {
  if (!session) return;
  if (session.frame !== null) {
    cancelAnimationFrame(session.frame);
    session.frame = null;
  }

  for (const target of session.targets) {
    target.element.style.left = `${target.baseLeft}px`;
    target.element.style.width = `${target.baseWidth}px`;
    target.element.style.transform = target.baseTransform;
  }
}

export function datesMatch(a: Date | null, b: Date | null) {
  if (!a || !b) return false;
  return a.getTime() === b.getTime();
}

export function preserveLivePreview(session: LivePreviewSession | null) {
  if (!session) return;
  if (session.frame !== null) {
    cancelAnimationFrame(session.frame);
    session.frame = null;
  }

  for (const target of session.targets) {
    target.baseLeft = toPxValue(target.element.style.left);
    target.baseWidth = toPxValue(target.element.style.width);
    target.baseTransform = target.element.style.transform;
  }
}
