import { matchers } from "@dash-ui/jest";
import { createStyles } from "@dash-ui/styles";
import layout from "./index";

// Add the custom matchers provided by '@dash-ui/jest'
expect.extend(matchers);

describe("box()", () => {
  it('applies the "display" prop', () => {
    expect(
      createElement(layoutStyles.box({ display: "block" }))
    ).toHaveStyleRule("display", "block");
  });

  it('applies the "position" prop', () => {
    expect(
      createElement(layoutStyles.box({ position: "relative" }))
    ).toHaveStyleRule("position", "relative");
  });

  it('applies the "width" prop', () => {
    expect(createElement(layoutStyles.box({ width: "100%" }))).toHaveStyleRule(
      "width",
      "100%"
    );
  });

  it('applies the "width" prop w/ px default', () => {
    expect(createElement(layoutStyles.box({ width: 100 }))).toHaveStyleRule(
      "width",
      "100px"
    );
  });

  it('applies the "height" prop', () => {
    expect(createElement(layoutStyles.box({ height: "100%" }))).toHaveStyleRule(
      "height",
      "100%"
    );
  });

  it('applies the "height" prop w/ px default', () => {
    expect(createElement(layoutStyles.box({ height: 100 }))).toHaveStyleRule(
      "height",
      "100px"
    );
  });

  it('applies the "size" prop', () => {
    const element = createElement(layoutStyles.box({ size: "100%" }));
    expect(element).toHaveStyleRule("width", "100%");
    expect(element).toHaveStyleRule("height", "100%");
  });

  it('applies the "size" prop w/ px default', () => {
    const element = createElement(layoutStyles.box({ size: 100 }));
    expect(element).toHaveStyleRule("width", "100px");
    expect(element).toHaveStyleRule("height", "100px");
  });

  it('applies the "min-width" prop', () => {
    expect(
      createElement(layoutStyles.box({ minWidth: "100%" }))
    ).toHaveStyleRule("min-width", "100%");
  });

  it('applies the "min-width" prop w/ px default', () => {
    expect(createElement(layoutStyles.box({ minWidth: 100 }))).toHaveStyleRule(
      "min-width",
      "100px"
    );
  });

  it('applies the "max-width" prop', () => {
    expect(
      createElement(layoutStyles.box({ maxWidth: "100%" }))
    ).toHaveStyleRule("max-width", "100%");
  });

  it('applies the "max-width" prop w/ px default', () => {
    expect(createElement(layoutStyles.box({ maxWidth: 100 }))).toHaveStyleRule(
      "max-width",
      "100px"
    );
  });

  it('applies the "max-height" prop', () => {
    expect(
      createElement(layoutStyles.box({ maxHeight: "100%" }))
    ).toHaveStyleRule("max-height", "100%");
  });

  it('applies the "max-height" prop w/ px default', () => {
    expect(createElement(layoutStyles.box({ maxHeight: 100 }))).toHaveStyleRule(
      "max-height",
      "100px"
    );
  });

  it('applies the "border" prop w/ px default', () => {
    const element = createElement(
      layoutStyles.box({ border: [["hairline", "hairline"], "green"] })
    );

    expect(element).toHaveStyleRule(
      "border-width",
      "var(--border-width-hairline) var(--border-width-hairline)"
    );
    expect(element).toHaveStyleRule("border-style", "solid");
    expect(element).toHaveStyleRule("border-color", "var(--color-green)");
  });

  it('applies the "border" prop w/ array', () => {
    const element = createElement(
      layoutStyles.box({ border: ["hairline", "green"] })
    );

    expect(element).toHaveStyleRule(
      "border-width",
      "var(--border-width-hairline)"
    );
    expect(element).toHaveStyleRule("border-style", "solid");
    expect(element).toHaveStyleRule("border-color", "var(--color-green)");
  });

  it('applies the "bg" prop', () => {
    expect(createElement(layoutStyles.box({ bg: "green" }))).toHaveStyleRule(
      "background-color",
      "var(--color-green)"
    );
  });

  it('applies the "pad" prop', () => {
    expect(createElement(layoutStyles.box({ pad: 1 }))).toHaveStyleRule(
      "padding",
      "var(--pad-1)"
    );
  });

  it('applies the "pad" prop w/ array', () => {
    expect(
      createElement(layoutStyles.box({ pad: [0, "auto"] }))
    ).toHaveStyleRule("padding", "var(--pad-0) var(--pad-auto)");
  });

  it('applies the "z" prop w/ token', () => {
    expect(createElement(layoutStyles.box({ z: "min" }))).toHaveStyleRule(
      "z-index",
      "var(--z-index-min)"
    );
  });

  it('applies the "inset" prop w/ number', () => {
    const element = createElement(layoutStyles.box({ inset: 1 }));
    expect(element).toHaveStyleRule("inset", "1px", {
      supports: "(inset: 10px)",
    });
    expect(element).toHaveStyleRule("top", "1px", {
      supports: "not (inset: 10px)",
    });
    expect(element).toHaveStyleRule("right", "1px", {
      supports: "not (inset: 10px)",
    });
    expect(element).toHaveStyleRule("bottom", "1px", {
      supports: "not (inset: 10px)",
    });
    expect(element).toHaveStyleRule("left", "1px", {
      supports: "not (inset: 10px)",
    });
  });

  it('applies the "inset" prop w/ array', () => {
    const element = createElement(layoutStyles.box({ inset: [1] }));
    expect(element).toHaveStyleRule("inset", "1px", {
      supports: "(inset: 10px)",
    });
    expect(element).toHaveStyleRule("top", "1px", {
      supports: "not (inset: 10px)",
    });
    expect(element).toHaveStyleRule("right", "1px", {
      supports: "not (inset: 10px)",
    });
    expect(element).toHaveStyleRule("bottom", "1px", {
      supports: "not (inset: 10px)",
    });
    expect(element).toHaveStyleRule("left", "1px", {
      supports: "not (inset: 10px)",
    });
  });

  it('applies the "inset" prop w/ array len 2', () => {
    const element = createElement(layoutStyles.box({ inset: [1, 2] }));
    expect(element).toHaveStyleRule("inset", "1px 2px", {
      supports: "(inset: 10px)",
    });
    expect(element).toHaveStyleRule("top", "1px", {
      supports: "not (inset: 10px)",
    });
    expect(element).toHaveStyleRule("right", "2px", {
      supports: "not (inset: 10px)",
    });
    expect(element).toHaveStyleRule("bottom", "1px", {
      supports: "not (inset: 10px)",
    });
    expect(element).toHaveStyleRule("left", "2px", {
      supports: "not (inset: 10px)",
    });
  });

  it('applies the "inset" prop w/ array len 3', () => {
    const element = createElement(layoutStyles.box({ inset: [1, 2, 3] }));
    expect(element).toHaveStyleRule("inset", "1px 2px 3px", {
      supports: "(inset: 10px)",
    });
    expect(element).toHaveStyleRule("top", "1px", {
      supports: "not (inset: 10px)",
    });
    expect(element).toHaveStyleRule("right", "2px", {
      supports: "not (inset: 10px)",
    });
    expect(element).toHaveStyleRule("bottom", "3px", {
      supports: "not (inset: 10px)",
    });
    expect(element).toHaveStyleRule("left", "2px", {
      supports: "not (inset: 10px)",
    });
  });

  it('applies the "inset" prop w/ array len 4', () => {
    const element = createElement(layoutStyles.box({ inset: [1, 2, 3, 4] }));
    expect(element).toHaveStyleRule("inset", "1px 2px 3px 4px", {
      supports: "(inset: 10px)",
    });
    expect(element).toHaveStyleRule("top", "1px", {
      supports: "not (inset: 10px)",
    });
    expect(element).toHaveStyleRule("right", "2px", {
      supports: "not (inset: 10px)",
    });
    expect(element).toHaveStyleRule("bottom", "3px", {
      supports: "not (inset: 10px)",
    });
    expect(element).toHaveStyleRule("left", "4px", {
      supports: "not (inset: 10px)",
    });
  });

  it('applies the "radius" prop', () => {
    const element = createElement(layoutStyles.box({ radius: "sm" }));
    expect(element).toHaveStyleRule("border-radius", "var(--radius-sm)");
  });

  it('applies the "radius" prop w/ array', () => {
    const element = createElement(layoutStyles.box({ radius: ["sm", "md"] }));
    expect(element).toHaveStyleRule(
      "border-radius",
      "var(--radius-sm) var(--radius-md)"
    );
  });

  it('applies the "shadow" prop', () => {
    const element = createElement(layoutStyles.box({ shadow: "low" }));
    expect(element).toHaveStyleRule("box-shadow", "var(--shadow-low)");
  });

  it("applies responsive styles", () => {
    const element = createElement(
      responsiveLayoutStyles.box({
        width: { phone: 100, tablet: 200 },
      })
    );
    expect(element).toHaveStyleRule("width", "100px", {
      media: mediaQueries.phone,
    });
    expect(element).toHaveStyleRule("width", "200px", {
      media: mediaQueries.tablet,
    });
  });
});

describe("hstack()", () => {
  it("applies default styles", () => {
    const element = createElement(layoutStyles.hstack());
    expect(element).toHaveStyleRule("display", "flex");
    expect(element).toHaveStyleRule("flex-direction", "row");
    expect(element).toHaveStyleRule("flex-shrink", "0", {
      target: ">*",
    });
  });

  it('applies the "reversed" prop', () => {
    const element = createElement(layoutStyles.hstack({ reversed: true }));
    expect(element).not.toHaveStyleRule("flex-direction", "row");
    expect(element).toHaveStyleRule("flex-direction", "row-reverse");
  });

  it('applies the "gap" prop', () => {
    const element = createElement(layoutStyles.hstack({ gap: 1 }));
    expect(element).toHaveStyleRule("gap", "var(--gap-1)", {
      supports: "(display: flex) and (gap: 1em)",
    });
    expect(element).toHaveStyleRule("margin-left", "var(--gap-1)!important", {
      target: ">* + *",
      supports: "not (display: flex) and (gap: 1em)",
    });
  });

  it('applies the "gap" prop w/ a negative margin', () => {
    const element = createElement(layoutStyles.hstack({ gap: "-1" }));
    expect(element).toHaveStyleRule("margin-left", "var(--gap--1)!important", {
      target: ">* + *",
    });
  });

  it('applies the "align" prop', () => {
    const element = createElement(layoutStyles.hstack({ align: "center" }));
    expect(element).toHaveStyleRule("align-items", "center");
  });

  it('applies the "distribute" prop', () => {
    const element = createElement(
      layoutStyles.hstack({ distribute: "center" })
    );
    expect(element).toHaveStyleRule("justify-content", "center");
  });
});

describe("vstack()", () => {
  it("applies default styles", () => {
    const element = createElement(layoutStyles.vstack());
    expect(element).toHaveStyleRule("display", "flex");
    expect(element).toHaveStyleRule("flex-direction", "column");
    expect(element).toHaveStyleRule("flex-shrink", "0", {
      target: ">*",
    });
  });

  it('applies the "reversed" prop', () => {
    const element = createElement(layoutStyles.vstack({ reversed: true }));
    expect(element).not.toHaveStyleRule("flex-direction", "column");
    expect(element).toHaveStyleRule("flex-direction", "column-reverse");
  });
  it('applies the "gap" prop', () => {
    const element = createElement(layoutStyles.vstack({ gap: 1 }));
    expect(element).toHaveStyleRule("gap", "var(--gap-1)", {
      supports: "(display: flex) and (gap: 1em)",
    });
    expect(element).toHaveStyleRule("margin-top", "var(--gap-1)!important", {
      target: ">* + *",
      supports: "not (display: flex) and (gap: 1em)",
    });
  });

  it('applies the "gap" prop w/ a negative margin', () => {
    const element = createElement(layoutStyles.vstack({ gap: "-1" }));
    expect(element).toHaveStyleRule("margin-top", "var(--gap--1)!important", {
      target: ">* + *",
    });
  });

  it('applies the "align" prop', () => {
    const element = createElement(layoutStyles.vstack({ align: "center" }));
    expect(element).toHaveStyleRule("align-items", "center");
  });

  it('applies the "distribute" prop', () => {
    const element = createElement(
      layoutStyles.vstack({ distribute: "center" })
    );
    expect(element).toHaveStyleRule("justify-content", "center");
  });
});

describe("zstack()", () => {
  it("applies default styles", () => {
    const element = createElement(layoutStyles.zstack());
    expect(element).toHaveStyleRule("display", "grid");
    expect(element).toHaveStyleRule("grid-area", "1/1/1/1", {
      target: ">*",
    });
  });

  it('applies the "inline" prop', () => {
    const element = createElement(layoutStyles.zstack({ inline: true }));
    expect(element).not.toHaveStyleRule("display", "grid");
    expect(element).toHaveStyleRule("display", "inline-grid");
  });

  it('applies the "alignX" prop', () => {
    const element = createElement(layoutStyles.zstack({ alignX: "center" }));
    expect(element).toHaveStyleRule("justify-items", "center");
  });

  it('applies the "alignY" prop', () => {
    const element = createElement(layoutStyles.zstack({ alignY: "center" }));
    expect(element).toHaveStyleRule("align-items", "center");
  });

  it('applies the "distributeX" prop', () => {
    const element = createElement(
      layoutStyles.zstack({ distributeX: "center" })
    );
    expect(element).toHaveStyleRule("justify-content", "center");
  });

  it('applies the "distributeY" prop', () => {
    const element = createElement(
      layoutStyles.zstack({ distributeY: "center" })
    );
    expect(element).toHaveStyleRule("align-content", "center");
  });

  it('applies the "center" prop', () => {
    const element = createElement(layoutStyles.zstack({ center: true }));
    expect(element).toHaveStyleRule("align-items", "center");
    expect(element).toHaveStyleRule("justify-items", "center");
  });
});

describe("inline()", () => {
  it("applies default styles", () => {
    const element = createElement(layoutStyles.inline());
    expect(element).toHaveStyleRule("display", "flex");
    expect(element).toHaveStyleRule("flex-wrap", "wrap");
    expect(element).toHaveStyleRule("justify-content", "flex-start");
    expect(element).toHaveStyleRule("flex-shrink", "0", {
      target: ">*",
    });
  });

  it('applies the "gap" prop', () => {
    const element = createElement(layoutStyles.inline({ gap: 1 }));
    expect(element).toHaveStyleRule("gap", "var(--gap-1)", {
      supports: "(display: flex) and (gap: 1em)",
    });
    expect(element).toHaveStyleRule(
      "margin-left",
      "calc(-1 * var(--gap-1))!important",
      {
        supports: "not (display: flex) and (gap: 1em)",
      }
    );
    expect(element).toHaveStyleRule(
      "margin-top",
      "calc(-1 * var(--gap-1))!important",
      {
        supports: "not (display: flex) and (gap: 1em)",
      }
    );
    expect(element).toHaveStyleRule("margin-top", "var(--gap-1)!important", {
      target: ">*",
      supports: "not (display: flex) and (gap: 1em)",
    });
    expect(element).toHaveStyleRule("margin-left", "var(--gap-1)!important", {
      target: ">*",
      supports: "not (display: flex) and (gap: 1em)",
    });
  });

  it('applies the "align" prop', () => {
    const element = createElement(layoutStyles.inline({ align: "center" }));
    expect(element).toHaveStyleRule("align-items", "center");
  });

  it('applies the "distribute" prop', () => {
    const element = createElement(
      layoutStyles.inline({ distribute: "center" })
    );
    expect(element).toHaveStyleRule("justify-content", "center");
  });
});

describe("overlay()", () => {
  it("applies default styles for each position", () => {
    const placements = [
      "center",
      "topLeft",
      "top",
      "topRight",
      "right",
      "bottomRight",
      "bottom",
      "bottomLeft",
      "left",
    ];
    for (const placement of placements) {
      const element = layoutStyles.overlay.css({ placement: placement as any });
      expect(element).toMatchSnapshot(placement);
    }
  });

  it("applies styles with offsets for each position", () => {
    const placements = [
      "topLeft",
      "top",
      "topRight",
      "right",
      "bottomRight",
      "bottom",
      "bottomLeft",
      "left",
    ] as const;
    for (const placement of placements) {
      const element = layoutStyles.overlay.css({
        placement,
        offset: 10,
      });
      expect(element).toMatchSnapshot(placement);
    }
  });
});

describe("grid()", () => {
  it("applies default styles", () => {
    const element = createElement(layoutStyles.grid());
    expect(element).toHaveStyleRule("display", "grid");
  });

  it('applies the "inline" prop', () => {
    const element = createElement(layoutStyles.grid({ inline: true }));
    expect(element).toHaveStyleRule("display", "inline-grid");
  });

  it('applies the "cols" prop w/ specific sizes', () => {
    const element = createElement(
      layoutStyles.grid({ cols: [100, "auto", "3rem"] })
    );
    expect(element).toHaveStyleRule("grid-template-columns", "100px auto 3rem");
  });

  it('applies the "cols" prop w/ fixed number and size', () => {
    const element = createElement(layoutStyles.grid({ cols: 3 }));
    expect(element).toHaveStyleRule(
      "grid-template-columns",
      "repeat(3,minmax(0,1fr))"
    );
  });

  it('applies the "rows" prop w/ specific sizes', () => {
    const element = createElement(
      layoutStyles.grid({ rows: [100, "auto", "3rem"] })
    );
    expect(element).toHaveStyleRule("grid-template-rows", "100px auto 3rem");
  });

  it('applies the "rows" prop w/ fixed number and size', () => {
    const element = createElement(layoutStyles.grid({ rows: 3 }));
    expect(element).toHaveStyleRule(
      "grid-template-rows",
      "repeat(3,minmax(0,1fr))"
    );
  });

  it('applies the "gap" prop w/ single value', () => {
    const element = createElement(layoutStyles.grid({ gap: 1 }));
    expect(element).toHaveStyleRule("grid-gap", "var(--gap-1) var(--gap-1)");
    expect(element).toHaveStyleRule("gap", "var(--gap-1) var(--gap-1)");
  });

  it('applies the "gap" prop w/ array value', () => {
    const element = createElement(layoutStyles.grid({ gap: [1, 2] }));
    expect(element).toHaveStyleRule("grid-gap", "var(--gap-1) var(--gap-2)");
    expect(element).toHaveStyleRule("gap", "var(--gap-1) var(--gap-2)");
  });

  it('applies the "alignX" prop', () => {
    const element = createElement(layoutStyles.grid({ alignX: "center" }));
    expect(element).toHaveStyleRule("justify-items", "center");
  });

  it('applies the "alignY" prop', () => {
    const element = createElement(layoutStyles.grid({ alignY: "center" }));
    expect(element).toHaveStyleRule("align-items", "center");
  });

  it('applies the "distributeX" prop', () => {
    const element = createElement(layoutStyles.grid({ distributeX: "center" }));
    expect(element).toHaveStyleRule("justify-content", "center");
  });

  it('applies the "distributeY" prop', () => {
    const element = createElement(layoutStyles.grid({ distributeY: "center" }));
    expect(element).toHaveStyleRule("align-content", "center");
  });
});

describe("gridItem()", () => {
  it('applies the "colStart" prop', () => {
    const element = createElement(layoutStyles.gridItem({ colStart: "1" }));
    expect(element).toHaveStyleRule("grid-column-start", "1");
  });

  it('applies the "colEnd" prop', () => {
    const element = createElement(layoutStyles.gridItem({ colEnd: "1" }));
    expect(element).toHaveStyleRule("grid-column-end", "1");
  });

  it('applies the "rowStart" prop', () => {
    const element = createElement(layoutStyles.gridItem({ rowStart: "1" }));
    expect(element).toHaveStyleRule("grid-row-start", "1");
  });

  it('applies the "rowEnd" prop', () => {
    const element = createElement(layoutStyles.gridItem({ rowEnd: "1" }));
    expect(element).toHaveStyleRule("grid-row-end", "1");
  });

  it('applies the "alignX" prop', () => {
    const element = createElement(
      layoutStyles.gridItem({ distribute: "center" })
    );
    expect(element).toHaveStyleRule("justify-self", "center");
  });

  it('applies the "alignY" prop', () => {
    const element = createElement(layoutStyles.gridItem({ align: "center" }));
    expect(element).toHaveStyleRule("align-self", "center");
  });
});

describe("autoGrid()", () => {
  it('applies the "itemWidth" prop w/ number', () => {
    const element = createElement(layoutStyles.autoGrid({ itemWidth: 200 }));
    expect(element).toHaveStyleRule(
      "grid-template-columns",
      "repeat(auto-fit, minmax(200px, 1fr))"
    );
  });

  it('applies the "itemWidth" prop w/ string', () => {
    const element = createElement(layoutStyles.autoGrid({ itemWidth: "100%" }));
    expect(element).toHaveStyleRule(
      "grid-template-columns",
      "repeat(auto-fit, minmax(100%, 1fr))"
    );
  });
});

describe("flexItem()", () => {
  it('applies the "align" prop', () => {
    const element = createElement(layoutStyles.flexItem({ align: "center" }));
    expect(element).toHaveStyleRule("align-self", "center");
  });

  it('applies the "distribute" prop', () => {
    const element = createElement(
      layoutStyles.flexItem({ distribute: "center" })
    );
    expect(element).toHaveStyleRule("justify-self", "center");
  });

  it('applies the "basis" prop', () => {
    const element = createElement(layoutStyles.flexItem({ basis: 100 }));
    expect(element).toHaveStyleRule("flex-basis", "100px");
  });

  it('applies the "grow" prop', () => {
    const element = createElement(layoutStyles.flexItem({ grow: 100 }));
    expect(element).toHaveStyleRule("flex-grow", "100");
  });

  it('applies the "grow" prop w/ boolean', () => {
    const element = createElement(layoutStyles.flexItem({ grow: true }));
    expect(element).toHaveStyleRule("flex-grow", "1");
  });

  it('applies the "shrink" prop', () => {
    const element = createElement(layoutStyles.flexItem({ shrink: 100 }));
    expect(element).toHaveStyleRule("flex-shrink", "100");
  });

  it('applies the "shrink" prop w/ boolean', () => {
    const element = createElement(layoutStyles.flexItem({ shrink: true }));
    expect(element).toHaveStyleRule("flex-shrink", "1");
  });

  it('applies the "order" prop', () => {
    const element = createElement(layoutStyles.flexItem({ order: 1 }));
    expect(element).toHaveStyleRule("order", "1");
  });
});

describe("bleed()", () => {
  it('applies the "amount" prop', () => {
    expect(createElement(layoutStyles.bleed({ amount: 1 }))).toHaveStyleRule(
      "margin",
      "calc(-1 * var(--pad-1))!important"
    );
  });

  it('applies the "amount" prop w/ array', () => {
    expect(
      createElement(layoutStyles.bleed({ amount: [0, 1] }))
    ).toHaveStyleRule(
      "margin",
      "calc(-1 * var(--pad-0)) calc(-1 * var(--pad-1))!important"
    );
  });
});

const styles = createStyles({
  tokens: {
    gap: {
      auto: "auto",
      "-5": "-4rem",
      "-4": "-2rem",
      "-3": "-1rem",
      "-2": "-0.5rem",
      "-1": "-0.25rem",
      0: 0,
      1: "0.25rem",
      2: "0.5rem",
      3: "1rem",
      4: "2rem",
      5: "4rem",
    },
    pad: {
      auto: "auto",
      0: 0,
      1: "0.125rem",
      2: "0.25rem",
      3: "0.5rem",
      4: "1rem",
      5: "2rem",
      6: "4rem",
    },
    color: {
      blue: "blue",
      green: "green",
    },
    shadow: {
      low: "low",
      high: "high",
    },
    radius: {
      sm: "0.125rem",
      md: "0.25rem",
    },
    borderWidth: {
      hairline: "0.5px",
    },
    zIndex: {
      min: -1,
    } as const,
  },
});

const mediaQueries = {
  phone: "only screen and (min-width: 0em)",
  tablet: "only screen and (min-width: 35em)",
  desktop: "only screen and (min-width: 80em)",
} as const;
const layoutStyles = layout(styles);
const responsiveLayoutStyles = layout(styles, mediaQueries);

function createElement(className: string) {
  const element = document.createElement("div");
  element.classList.add(...className.split(" "));
  return element;
}

afterEach(() => {
  styles.dash.sheet.flush();
  styles.dash.inserted.clear();
});
