// fallow-ignore-file code-duplication
// @vitest-environment happy-dom

import { describe, it, expect } from "vitest";
import type { PatchOperation } from "../../utils/sourcePatcher";
import {
  STUDIO_OFFSET_X_PROP,
  STUDIO_OFFSET_Y_PROP,
  STUDIO_WIDTH_PROP,
  STUDIO_HEIGHT_PROP,
  STUDIO_ROTATION_PROP,
  STUDIO_PATH_OFFSET_ATTR,
  STUDIO_BOX_SIZE_ATTR,
  STUDIO_ROTATION_ATTR,
  STUDIO_ROTATION_DRAFT_ATTR,
  STUDIO_ORIGINAL_TRANSLATE_ATTR,
  STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR,
  STUDIO_ORIGINAL_WIDTH_ATTR,
  STUDIO_ORIGINAL_HEIGHT_ATTR,
  STUDIO_ORIGINAL_MIN_WIDTH_ATTR,
  STUDIO_ORIGINAL_MIN_HEIGHT_ATTR,
  STUDIO_ORIGINAL_MAX_WIDTH_ATTR,
  STUDIO_ORIGINAL_MAX_HEIGHT_ATTR,
  STUDIO_ORIGINAL_FLEX_BASIS_ATTR,
  STUDIO_ORIGINAL_FLEX_GROW_ATTR,
  STUDIO_ORIGINAL_FLEX_SHRINK_ATTR,
  STUDIO_ORIGINAL_BOX_SIZING_ATTR,
  STUDIO_ORIGINAL_SCALE_ATTR,
  STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR,
  STUDIO_ORIGINAL_DISPLAY_ATTR,
  STUDIO_ORIGINAL_ROTATE_ATTR,
  STUDIO_ORIGINAL_INLINE_ROTATE_ATTR,
  STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR,
  STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR,
} from "./manualEditsTypes";
import {
  STUDIO_MOTION_ATTR,
  STUDIO_MOTION_ORIGINAL_TRANSFORM_ATTR,
  STUDIO_MOTION_ORIGINAL_OPACITY_ATTR,
  STUDIO_MOTION_ORIGINAL_VISIBILITY_ATTR,
} from "./studioMotionTypes";
import {
  buildPathOffsetPatches,
  buildClearPathOffsetPatches,
  buildBoxSizePatches,
  buildClearBoxSizePatches,
  buildRotationPatches,
  buildClearRotationPatches,
  buildMotionPatches,
  buildClearMotionPatches,
} from "./manualEditsDomPatches";

/* ── helpers ── */

function div(): HTMLElement {
  return document.createElement("div");
}

function opKey(op: PatchOperation): string {
  return `${op.type}:${op.property}`;
}

function assertClearCoversKeys(buildOps: PatchOperation[], clearOps: PatchOperation[]): void {
  const clearKeys = new Set(clearOps.map(opKey));
  for (const op of buildOps) {
    expect(clearKeys.has(opKey(op)), `clear missing key "${opKey(op)}"`).toBe(true);
  }
}

/* ── Path offset ─────────────────────────────────────────────────────────── */

describe("buildPathOffsetPatches / buildClearPathOffsetPatches", () => {
  function populatedPathEl(): HTMLElement {
    const e = div();
    e.style.setProperty(STUDIO_OFFSET_X_PROP, "10px");
    e.style.setProperty(STUDIO_OFFSET_Y_PROP, "20px");
    e.style.setProperty("translate", "10px 20px");
    e.setAttribute(STUDIO_ORIGINAL_TRANSLATE_ATTR, "5px 10px");
    e.setAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, "3px");
    e.style.setProperty("display", "flex");
    e.setAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, "block");
    return e;
  }

  it("populated: captures offset styles, attrs, display, and transform-display marker in declaration order", () => {
    const ops = buildPathOffsetPatches(populatedPathEl());
    expect(ops).toEqual([
      { type: "inline-style", property: STUDIO_OFFSET_X_PROP, value: "10px" },
      { type: "inline-style", property: STUDIO_OFFSET_Y_PROP, value: "20px" },
      { type: "inline-style", property: "translate", value: "10px 20px" },
      { type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: "true" },
      { type: "attribute", property: STUDIO_ORIGINAL_TRANSLATE_ATTR, value: "5px 10px" },
      { type: "attribute", property: STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, value: "3px" },
      { type: "inline-style", property: "display", value: "flex" },
      { type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: "block" },
    ]);
  });

  it("empty: bare element yields only the path-offset marker", () => {
    expect(buildPathOffsetPatches(div())).toEqual([
      { type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: "true" },
    ]);
  });

  it("clear: restores translate from STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR and display from STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR", () => {
    const e = div();
    e.setAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, "5px");
    e.setAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, "grid");
    const ops = buildClearPathOffsetPatches(e);
    expect(ops).toEqual([
      { type: "inline-style", property: STUDIO_OFFSET_X_PROP, value: null },
      { type: "inline-style", property: STUDIO_OFFSET_Y_PROP, value: null },
      { type: "inline-style", property: "translate", value: "5px" },
      { type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: null },
      { type: "attribute", property: STUDIO_ORIGINAL_TRANSLATE_ATTR, value: null },
      { type: "attribute", property: STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, value: null },
      { type: "inline-style", property: "display", value: "grid" },
      { type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: null },
    ]);
  });

  it("clear: empty STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR coerces to null (translate not set to empty string)", () => {
    const e = div();
    e.setAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, "");
    const ops = buildClearPathOffsetPatches(e);
    expect(ops.find((o) => o.property === "translate")?.value).toBeNull();
  });

  it("build/clear symmetry: clear addresses every {type,property} key that build emits", () => {
    const e = populatedPathEl();
    assertClearCoversKeys(buildPathOffsetPatches(e), buildClearPathOffsetPatches(e));
  });
});

/* ── Box size ────────────────────────────────────────────────────────────── */

describe("buildBoxSizePatches / buildClearBoxSizePatches", () => {
  function populatedBoxEl(): HTMLElement {
    const e = div();
    e.style.setProperty(STUDIO_WIDTH_PROP, "300px");
    e.style.setProperty(STUDIO_HEIGHT_PROP, "200px");
    e.style.setProperty("width", "300px");
    e.style.setProperty("height", "200px");
    e.style.setProperty("min-width", "100px");
    e.style.setProperty("min-height", "50px");
    e.style.setProperty("max-width", "500px");
    e.style.setProperty("max-height", "400px");
    e.style.setProperty("flex-basis", "auto");
    e.style.setProperty("flex-grow", "1");
    e.style.setProperty("flex-shrink", "0");
    e.style.setProperty("box-sizing", "border-box");
    e.style.setProperty("scale", "1.5");
    e.style.setProperty("transform-origin", "center");
    e.style.setProperty("display", "block");
    e.setAttribute(STUDIO_ORIGINAL_WIDTH_ATTR, "250px");
    e.setAttribute(STUDIO_ORIGINAL_HEIGHT_ATTR, "150px");
    e.setAttribute(STUDIO_ORIGINAL_MIN_WIDTH_ATTR, "0px");
    e.setAttribute(STUDIO_ORIGINAL_MIN_HEIGHT_ATTR, "0px");
    e.setAttribute(STUDIO_ORIGINAL_MAX_WIDTH_ATTR, "none");
    e.setAttribute(STUDIO_ORIGINAL_MAX_HEIGHT_ATTR, "none");
    e.setAttribute(STUDIO_ORIGINAL_FLEX_BASIS_ATTR, "0px");
    e.setAttribute(STUDIO_ORIGINAL_FLEX_GROW_ATTR, "0");
    e.setAttribute(STUDIO_ORIGINAL_FLEX_SHRINK_ATTR, "1");
    e.setAttribute(STUDIO_ORIGINAL_BOX_SIZING_ATTR, "content-box");
    e.setAttribute(STUDIO_ORIGINAL_SCALE_ATTR, "1");
    e.setAttribute(STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR, "50% 50%");
    e.setAttribute(STUDIO_ORIGINAL_DISPLAY_ATTR, "flex");
    e.setAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, "");
    return e;
  }

  it("populated: captures studio-width/height, all BOX_SIZE_STYLE_PROPS, marker, and all orig attrs", () => {
    const ops = buildBoxSizePatches(populatedBoxEl());
    expect(ops).toEqual([
      { type: "inline-style", property: STUDIO_WIDTH_PROP, value: "300px" },
      { type: "inline-style", property: STUDIO_HEIGHT_PROP, value: "200px" },
      { type: "inline-style", property: "width", value: "300px" },
      { type: "inline-style", property: "height", value: "200px" },
      { type: "inline-style", property: "min-width", value: "100px" },
      { type: "inline-style", property: "min-height", value: "50px" },
      { type: "inline-style", property: "max-width", value: "500px" },
      { type: "inline-style", property: "max-height", value: "400px" },
      { type: "inline-style", property: "flex-basis", value: "auto" },
      { type: "inline-style", property: "flex-grow", value: "1" },
      { type: "inline-style", property: "flex-shrink", value: "0" },
      { type: "inline-style", property: "box-sizing", value: "border-box" },
      { type: "inline-style", property: "scale", value: "1.5" },
      { type: "inline-style", property: "transform-origin", value: "center" },
      { type: "inline-style", property: "display", value: "block" },
      { type: "attribute", property: STUDIO_BOX_SIZE_ATTR, value: "true" },
      { type: "attribute", property: STUDIO_ORIGINAL_WIDTH_ATTR, value: "250px" },
      { type: "attribute", property: STUDIO_ORIGINAL_HEIGHT_ATTR, value: "150px" },
      { type: "attribute", property: STUDIO_ORIGINAL_MIN_WIDTH_ATTR, value: "0px" },
      { type: "attribute", property: STUDIO_ORIGINAL_MIN_HEIGHT_ATTR, value: "0px" },
      { type: "attribute", property: STUDIO_ORIGINAL_MAX_WIDTH_ATTR, value: "none" },
      { type: "attribute", property: STUDIO_ORIGINAL_MAX_HEIGHT_ATTR, value: "none" },
      { type: "attribute", property: STUDIO_ORIGINAL_FLEX_BASIS_ATTR, value: "0px" },
      { type: "attribute", property: STUDIO_ORIGINAL_FLEX_GROW_ATTR, value: "0" },
      { type: "attribute", property: STUDIO_ORIGINAL_FLEX_SHRINK_ATTR, value: "1" },
      { type: "attribute", property: STUDIO_ORIGINAL_BOX_SIZING_ATTR, value: "content-box" },
      { type: "attribute", property: STUDIO_ORIGINAL_SCALE_ATTR, value: "1" },
      { type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR, value: "50% 50%" },
      { type: "attribute", property: STUDIO_ORIGINAL_DISPLAY_ATTR, value: "flex" },
      { type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: "" },
    ]);
  });

  it("empty: bare element yields only the box-size marker", () => {
    expect(buildBoxSizePatches(div())).toEqual([
      { type: "attribute", property: STUDIO_BOX_SIZE_ATTR, value: "true" },
    ]);
  });

  it("clear(populated): ops follow interleaved restore-then-null order for every orig attr", () => {
    const ops = buildClearBoxSizePatches(populatedBoxEl());
    expect(ops).toEqual([
      { type: "inline-style", property: STUDIO_WIDTH_PROP, value: null },
      { type: "inline-style", property: STUDIO_HEIGHT_PROP, value: null },
      { type: "attribute", property: STUDIO_BOX_SIZE_ATTR, value: null },
      { type: "inline-style", property: "width", value: "250px" },
      { type: "attribute", property: STUDIO_ORIGINAL_WIDTH_ATTR, value: null },
      { type: "inline-style", property: "height", value: "150px" },
      { type: "attribute", property: STUDIO_ORIGINAL_HEIGHT_ATTR, value: null },
      { type: "inline-style", property: "min-width", value: "0px" },
      { type: "attribute", property: STUDIO_ORIGINAL_MIN_WIDTH_ATTR, value: null },
      { type: "inline-style", property: "min-height", value: "0px" },
      { type: "attribute", property: STUDIO_ORIGINAL_MIN_HEIGHT_ATTR, value: null },
      { type: "inline-style", property: "max-width", value: "none" },
      { type: "attribute", property: STUDIO_ORIGINAL_MAX_WIDTH_ATTR, value: null },
      { type: "inline-style", property: "max-height", value: "none" },
      { type: "attribute", property: STUDIO_ORIGINAL_MAX_HEIGHT_ATTR, value: null },
      { type: "inline-style", property: "flex-basis", value: "0px" },
      { type: "attribute", property: STUDIO_ORIGINAL_FLEX_BASIS_ATTR, value: null },
      { type: "inline-style", property: "flex-grow", value: "0" },
      { type: "attribute", property: STUDIO_ORIGINAL_FLEX_GROW_ATTR, value: null },
      { type: "inline-style", property: "flex-shrink", value: "1" },
      { type: "attribute", property: STUDIO_ORIGINAL_FLEX_SHRINK_ATTR, value: null },
      { type: "inline-style", property: "box-sizing", value: "content-box" },
      { type: "attribute", property: STUDIO_ORIGINAL_BOX_SIZING_ATTR, value: null },
      { type: "inline-style", property: "scale", value: "1" },
      { type: "attribute", property: STUDIO_ORIGINAL_SCALE_ATTR, value: null },
      { type: "inline-style", property: "transform-origin", value: "50% 50%" },
      { type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR, value: null },
      { type: "inline-style", property: "display", value: "flex" },
      { type: "attribute", property: STUDIO_ORIGINAL_DISPLAY_ATTR, value: null },
      { type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: null },
    ]);
  });

  it("clear: empty orig attr coerces to null (style is removed rather than set to empty string)", () => {
    const e = div();
    e.setAttribute(STUDIO_ORIGINAL_WIDTH_ATTR, "");
    const ops = buildClearBoxSizePatches(e);
    expect(ops.find((o) => o.property === "width")?.value).toBeNull();
  });

  it("clear: bare element emits only null ops — no style restores fire when orig attrs are absent", () => {
    const ops = buildClearBoxSizePatches(div());
    // 3 fixed (studio-width, studio-height, box-size marker) + 14 attr-null pushes (one per BOX_SIZE_ORIG_ATTR)
    expect(ops).toHaveLength(17);
    expect(ops.every((op) => op.value === null)).toBe(true);
  });

  it("build/clear symmetry: clear addresses every {type,property} key that build emits", () => {
    const e = populatedBoxEl();
    assertClearCoversKeys(buildBoxSizePatches(e), buildClearBoxSizePatches(e));
  });
});

/* ── Rotation ────────────────────────────────────────────────────────────── */

describe("buildRotationPatches / buildClearRotationPatches", () => {
  function populatedRotEl(): HTMLElement {
    const e = div();
    e.style.setProperty(STUDIO_ROTATION_PROP, "45");
    e.style.setProperty("rotate", "45deg");
    e.style.setProperty("transform-origin", "left center");
    e.style.setProperty("display", "block");
    e.setAttribute(STUDIO_ORIGINAL_ROTATE_ATTR, "0deg");
    e.setAttribute(STUDIO_ORIGINAL_INLINE_ROTATE_ATTR, "0deg");
    e.setAttribute(STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR, "center center");
    e.setAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, "flex");
    return e;
  }

  it("populated: captures rotation styles, attrs, and transform-display marker in declaration order", () => {
    const ops = buildRotationPatches(populatedRotEl());
    expect(ops).toEqual([
      { type: "inline-style", property: STUDIO_ROTATION_PROP, value: "45" },
      { type: "inline-style", property: "rotate", value: "45deg" },
      { type: "inline-style", property: "transform-origin", value: "left center" },
      { type: "inline-style", property: "display", value: "block" },
      { type: "attribute", property: STUDIO_ROTATION_ATTR, value: "true" },
      { type: "attribute", property: STUDIO_ORIGINAL_ROTATE_ATTR, value: "0deg" },
      { type: "attribute", property: STUDIO_ORIGINAL_INLINE_ROTATE_ATTR, value: "0deg" },
      {
        type: "attribute",
        property: STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR,
        value: "center center",
      },
      { type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: "flex" },
    ]);
  });

  it("empty: bare element yields only the rotation marker", () => {
    expect(buildRotationPatches(div())).toEqual([
      { type: "attribute", property: STUDIO_ROTATION_ATTR, value: "true" },
    ]);
  });

  it("clear: restores rotate and transform-origin from orig attrs, nulls draft attr", () => {
    const e = div();
    e.setAttribute(STUDIO_ORIGINAL_INLINE_ROTATE_ATTR, "30deg");
    e.setAttribute(STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR, "top left");
    e.setAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, "grid");
    const ops = buildClearRotationPatches(e);
    expect(ops).toEqual([
      { type: "inline-style", property: STUDIO_ROTATION_PROP, value: null },
      { type: "inline-style", property: "rotate", value: "30deg" },
      { type: "inline-style", property: "transform-origin", value: "top left" },
      { type: "attribute", property: STUDIO_ROTATION_ATTR, value: null },
      { type: "attribute", property: STUDIO_ROTATION_DRAFT_ATTR, value: null },
      { type: "attribute", property: STUDIO_ORIGINAL_ROTATE_ATTR, value: null },
      { type: "attribute", property: STUDIO_ORIGINAL_INLINE_ROTATE_ATTR, value: null },
      { type: "attribute", property: STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR, value: null },
      { type: "inline-style", property: "display", value: "grid" },
      { type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: null },
    ]);
  });

  it("clear: absent STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR yields null for transform-origin", () => {
    const ops = buildClearRotationPatches(div());
    expect(ops.find((o) => o.property === "transform-origin")?.value).toBeNull();
  });

  it("clear: empty STUDIO_ORIGINAL_INLINE_ROTATE_ATTR coerces to null (rotate not set to empty string)", () => {
    const e = div();
    e.setAttribute(STUDIO_ORIGINAL_INLINE_ROTATE_ATTR, "");
    const ops = buildClearRotationPatches(e);
    expect(ops.find((o) => o.property === "rotate")?.value).toBeNull();
  });

  it("build/clear symmetry: clear addresses every {type,property} key that build emits", () => {
    const e = populatedRotEl();
    assertClearCoversKeys(buildRotationPatches(e), buildClearRotationPatches(e));
  });
});

/* ── Motion ──────────────────────────────────────────────────────────────── */

describe("buildMotionPatches / buildClearMotionPatches", () => {
  const MOTION_JSON = '{"kind":"gsap-motion","start":0,"duration":1}';

  function populatedMotionEl(): HTMLElement {
    const e = div();
    e.setAttribute(STUDIO_MOTION_ATTR, MOTION_JSON);
    e.setAttribute(STUDIO_MOTION_ORIGINAL_TRANSFORM_ATTR, "translateX(0)");
    e.setAttribute(STUDIO_MOTION_ORIGINAL_OPACITY_ATTR, "1");
    e.setAttribute(STUDIO_MOTION_ORIGINAL_VISIBILITY_ATTR, "visible");
    return e;
  }

  it("populated: captures motion JSON and all three original attrs when motion attr is present", () => {
    const ops = buildMotionPatches(populatedMotionEl());
    expect(ops).toEqual([
      { type: "attribute", property: STUDIO_MOTION_ATTR, value: MOTION_JSON },
      {
        type: "attribute",
        property: STUDIO_MOTION_ORIGINAL_TRANSFORM_ATTR,
        value: "translateX(0)",
      },
      { type: "attribute", property: STUDIO_MOTION_ORIGINAL_OPACITY_ATTR, value: "1" },
      { type: "attribute", property: STUDIO_MOTION_ORIGINAL_VISIBILITY_ATTR, value: "visible" },
    ]);
  });

  it("empty: returns [] when STUDIO_MOTION_ATTR is absent", () => {
    expect(buildMotionPatches(div())).toEqual([]);
  });

  it("clear: always nulls all four motion attrs regardless of element state", () => {
    const expected = [
      { type: "attribute", property: STUDIO_MOTION_ATTR, value: null },
      { type: "attribute", property: STUDIO_MOTION_ORIGINAL_TRANSFORM_ATTR, value: null },
      { type: "attribute", property: STUDIO_MOTION_ORIGINAL_OPACITY_ATTR, value: null },
      { type: "attribute", property: STUDIO_MOTION_ORIGINAL_VISIBILITY_ATTR, value: null },
    ];
    expect(buildClearMotionPatches(div())).toEqual(expected);
    expect(buildClearMotionPatches(populatedMotionEl())).toEqual(expected);
  });

  it("build/clear symmetry: clear addresses every {type,property} key that build emits", () => {
    const e = populatedMotionEl();
    assertClearCoversKeys(buildMotionPatches(e), buildClearMotionPatches(e));
  });
});
