// @vitest-environment jsdom
import { afterEach, describe, expect, it } from "vitest";
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
import {
  COLOR_GRADING_SOURCE_HIDDEN_ATTR,
  HF_COLOR_GRADING_CANVAS_ID_PREFIX,
} from "@hyperframes/core/color-grading";
import { readAllAnimatedProperties, readGsapProperty } from "./gsapRuntimeReaders";

/**
 * Regression: converting a property-group tween to keyframes resolves "current
 * values" via readAllAnimatedProperties. Two ways that used to bake garbage
 * into the composition file:
 *
 * 1. The group filter only pruned the tween's OWN properties — the baseline
 *    pass still captured every property ANY tween on the element touches, so
 *    a rotation commit carried `opacity` from the intro from() tween.
 * 2. `gsap.getProperty(el, "opacity")` on a color-grading source reads the
 *    runtime hide (inline `opacity: 0 !important`), not the animated value —
 *    so the captured opacity was the transient 0, which then animated 0 → 0
 *    on the next full load and the element disappeared.
 */

function fakeIframe(
  el: Element,
  opts: { gsapValues: Record<string, number>; otherTweenVars?: Record<string, number> },
): HTMLIFrameElement {
  const children = opts.otherTweenVars
    ? [{ targets: () => [el], vars: { duration: 0.8, ...opts.otherTweenVars } }]
    : [];
  return {
    contentWindow: {
      __timelines: { main: { getChildren: () => children } },
      gsap: { getProperty: (_el: Element, prop: string) => opts.gsapValues[prop] ?? 0 },
    },
    contentDocument: document,
  } as unknown as HTMLIFrameElement;
}

function rotationSetAnim(): GsapAnimation {
  return {
    id: "#clip-set-0-rotation",
    targetSelector: "#clip",
    method: "set",
    properties: { rotation: 0 },
  } as unknown as GsapAnimation;
}

afterEach(() => {
  document.body.innerHTML = "";
});

describe("readAllAnimatedProperties group filter", () => {
  it("keeps other tweens' out-of-group properties out of a grouped resolve", () => {
    const el = document.createElement("div");
    el.id = "clip";
    document.body.appendChild(el);
    const iframe = fakeIframe(el, {
      gsapValues: { rotation: -28.1, opacity: 0, rotationX: 52, rotationY: -47 },
      otherTweenVars: { opacity: 0, rotationX: 52, rotationY: -47 },
    });

    const result = readAllAnimatedProperties(iframe, "#clip", rotationSetAnim(), "rotation");

    expect(result).toEqual({ rotation: -28.1 });
  });
});

describe("color-grading opacity truth", () => {
  function gradedElement(): HTMLElement {
    const el = document.createElement("img");
    el.id = "clip";
    el.setAttribute(COLOR_GRADING_SOURCE_HIDDEN_ATTR, "");
    const canvas = document.createElement("canvas");
    canvas.id = `${HF_COLOR_GRADING_CANVAS_ID_PREFIX}clip`;
    canvas.style.opacity = "0.98";
    document.body.append(el, canvas);
    return el;
  }

  it("resolves opacity from the grading canvas, not the runtime hide", () => {
    const el = gradedElement();
    const iframe = fakeIframe(el, { gsapValues: { opacity: 0 } });
    const anim = {
      id: "#clip-from-200-visual",
      targetSelector: "#clip",
      method: "from",
      properties: { opacity: 0.5 },
    } as unknown as GsapAnimation;

    const result = readAllAnimatedProperties(iframe, "#clip", anim);

    expect(result.opacity).toBe(0.98);
  });

  it("readGsapProperty takes the same detour", () => {
    const el = gradedElement();
    const iframe = fakeIframe(el, { gsapValues: { opacity: 0 } });

    expect(readGsapProperty(iframe, "#clip", "opacity")).toBe(0.98);
  });

  it("reads GSAP directly when the source is not grading-hidden", () => {
    const el = document.createElement("div");
    el.id = "clip";
    document.body.appendChild(el);
    const iframe = fakeIframe(el, { gsapValues: { opacity: 0.3 } });

    expect(readGsapProperty(iframe, "#clip", "opacity")).toBe(0.3);
  });
});
