"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var toMatchSnapshot_exports = {};
__export(toMatchSnapshot_exports, {
  toHaveScreenshot: () => toHaveScreenshot,
  toHaveScreenshotStepTitle: () => toHaveScreenshotStepTitle,
  toMatchSnapshot: () => toMatchSnapshot
});
module.exports = __toCommonJS(toMatchSnapshot_exports);
var import_fs = __toESM(require("fs"));
var import_path = __toESM(require("path"));
var import_utils = require("playwright-core/lib/utils");
var import_utils2 = require("playwright-core/lib/utils");
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
var import_util = require("../util");
var import_matcherHint = require("./matcherHint");
var import_globals = require("../common/globals");
const NonConfigProperties = [
  "clip",
  "fullPage",
  "mask",
  "maskColor",
  "omitBackground",
  "timeout"
];
class SnapshotHelper {
  constructor(testInfo, matcherName, locator, anonymousSnapshotExtension, configOptions, nameOrOptions, optOptions) {
    let name;
    if (Array.isArray(nameOrOptions) || typeof nameOrOptions === "string") {
      name = nameOrOptions;
      this.options = { ...optOptions };
    } else {
      const { name: nameFromOptions, ...options } = nameOrOptions;
      this.options = options;
      name = nameFromOptions;
    }
    this.name = Array.isArray(name) ? name.join(import_path.default.sep) : name || "";
    const resolvedPaths = testInfo._resolveSnapshotPaths(matcherName === "toHaveScreenshot" ? "screenshot" : "snapshot", name, "updateSnapshotIndex", anonymousSnapshotExtension);
    this.expectedPath = resolvedPaths.absoluteSnapshotPath;
    this.attachmentBaseName = resolvedPaths.relativeOutputPath;
    const outputBasePath = testInfo._getOutputPath(resolvedPaths.relativeOutputPath);
    this.legacyExpectedPath = (0, import_util.addSuffixToFilePath)(outputBasePath, "-expected");
    this.previousPath = (0, import_util.addSuffixToFilePath)(outputBasePath, "-previous");
    this.actualPath = (0, import_util.addSuffixToFilePath)(outputBasePath, "-actual");
    this.diffPath = (0, import_util.addSuffixToFilePath)(outputBasePath, "-diff");
    const filteredConfigOptions = { ...configOptions };
    for (const prop of NonConfigProperties)
      delete filteredConfigOptions[prop];
    this.options = {
      ...filteredConfigOptions,
      ...this.options
    };
    if (this.options._comparator) {
      this.options.comparator = this.options._comparator;
      delete this.options._comparator;
    }
    if (this.options.maxDiffPixels !== void 0 && this.options.maxDiffPixels < 0)
      throw new Error("`maxDiffPixels` option value must be non-negative integer");
    if (this.options.maxDiffPixelRatio !== void 0 && (this.options.maxDiffPixelRatio < 0 || this.options.maxDiffPixelRatio > 1))
      throw new Error("`maxDiffPixelRatio` option value must be between 0 and 1");
    this.matcherName = matcherName;
    this.locator = locator;
    this.updateSnapshots = testInfo.config.updateSnapshots;
    this.mimeType = import_utilsBundle.mime.getType(import_path.default.basename(this.expectedPath)) ?? "application/octet-stream";
    this.comparator = (0, import_utils.getComparator)(this.mimeType);
    this.testInfo = testInfo;
    this.kind = this.mimeType.startsWith("image/") ? "Screenshot" : "Snapshot";
  }
  createMatcherResult(message, pass, log) {
    const unfiltered = {
      name: this.matcherName,
      expected: this.expectedPath,
      actual: this.actualPath,
      diff: this.diffPath,
      pass,
      message: () => message,
      log
    };
    return Object.fromEntries(Object.entries(unfiltered).filter(([_, v]) => v !== void 0));
  }
  handleMissingNegated() {
    const isWriteMissingMode = this.updateSnapshots !== "none";
    const message = `A snapshot doesn't exist at ${this.expectedPath}${isWriteMissingMode ? `, matchers using ".not" won't write them automatically.` : "."}`;
    return this.createMatcherResult(message, true);
  }
  handleDifferentNegated() {
    return this.createMatcherResult("", false);
  }
  handleMatchingNegated() {
    const message = [
      import_utils2.colors.red(`${this.kind} comparison failed:`),
      "",
      indent("Expected result should be different from the actual one.", "  ")
    ].join("\n");
    return this.createMatcherResult(message, true);
  }
  handleMissing(actual, step) {
    const isWriteMissingMode = this.updateSnapshots !== "none";
    if (isWriteMissingMode)
      writeFileSync(this.expectedPath, actual);
    step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-expected"), contentType: this.mimeType, path: this.expectedPath });
    writeFileSync(this.actualPath, actual);
    step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-actual"), contentType: this.mimeType, path: this.actualPath });
    const message = `A snapshot doesn't exist at ${this.expectedPath}${isWriteMissingMode ? ", writing actual." : "."}`;
    if (this.updateSnapshots === "all" || this.updateSnapshots === "changed") {
      console.log(message);
      return this.createMatcherResult(message, true);
    }
    if (this.updateSnapshots === "missing") {
      this.testInfo._hasNonRetriableError = true;
      this.testInfo._failWithError(new Error(message));
      return this.createMatcherResult("", true);
    }
    return this.createMatcherResult(message, false);
  }
  handleDifferent(actual, expected, previous, diff, header, diffError, log, step) {
    const output = [`${header}${indent(diffError, "  ")}`];
    if (this.name) {
      output.push("");
      output.push(`  Snapshot: ${this.name}`);
    }
    if (expected !== void 0) {
      writeFileSync(this.legacyExpectedPath, expected);
      step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-expected"), contentType: this.mimeType, path: this.expectedPath });
    }
    if (previous !== void 0) {
      writeFileSync(this.previousPath, previous);
      step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-previous"), contentType: this.mimeType, path: this.previousPath });
    }
    if (actual !== void 0) {
      writeFileSync(this.actualPath, actual);
      step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-actual"), contentType: this.mimeType, path: this.actualPath });
    }
    if (diff !== void 0) {
      writeFileSync(this.diffPath, diff);
      step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-diff"), contentType: this.mimeType, path: this.diffPath });
    }
    if (log?.length)
      output.push((0, import_util.callLogText)(log));
    else
      output.push("");
    return this.createMatcherResult(output.join("\n"), false, log);
  }
  handleMatching() {
    return this.createMatcherResult("", true);
  }
}
function toMatchSnapshot(received, nameOrOptions = {}, optOptions = {}) {
  const testInfo = (0, import_globals.currentTestInfo)();
  if (!testInfo)
    throw new Error(`toMatchSnapshot() must be called during the test`);
  if (received instanceof Promise)
    throw new Error("An unresolved Promise was passed to toMatchSnapshot(), make sure to resolve it by adding await to it.");
  if (testInfo._projectInternal.ignoreSnapshots)
    return { pass: !this.isNot, message: () => "", name: "toMatchSnapshot", expected: nameOrOptions };
  const configOptions = testInfo._projectInternal.expect?.toMatchSnapshot || {};
  const helper = new SnapshotHelper(
    testInfo,
    "toMatchSnapshot",
    void 0,
    "." + determineFileExtension(received),
    configOptions,
    nameOrOptions,
    optOptions
  );
  if (this.isNot) {
    if (!import_fs.default.existsSync(helper.expectedPath))
      return helper.handleMissingNegated();
    const isDifferent = !!helper.comparator(received, import_fs.default.readFileSync(helper.expectedPath), helper.options);
    return isDifferent ? helper.handleDifferentNegated() : helper.handleMatchingNegated();
  }
  if (!import_fs.default.existsSync(helper.expectedPath))
    return helper.handleMissing(received, this._stepInfo);
  const expected = import_fs.default.readFileSync(helper.expectedPath);
  if (helper.updateSnapshots === "all") {
    if (!(0, import_utils.compareBuffersOrStrings)(received, expected))
      return helper.handleMatching();
    writeFileSync(helper.expectedPath, received);
    console.log(helper.expectedPath + " is not the same, writing actual.");
    return helper.createMatcherResult(helper.expectedPath + " running with --update-snapshots, writing actual.", true);
  }
  if (helper.updateSnapshots === "changed") {
    const result2 = helper.comparator(received, expected, helper.options);
    if (!result2)
      return helper.handleMatching();
    writeFileSync(helper.expectedPath, received);
    console.log(helper.expectedPath + " does not match, writing actual.");
    return helper.createMatcherResult(helper.expectedPath + " running with --update-snapshots, writing actual.", true);
  }
  const result = helper.comparator(received, expected, helper.options);
  if (!result)
    return helper.handleMatching();
  const receiver = (0, import_utils.isString)(received) ? "string" : "Buffer";
  const header = (0, import_matcherHint.matcherHint)(this, void 0, "toMatchSnapshot", receiver, void 0, void 0, void 0);
  return helper.handleDifferent(received, expected, void 0, result.diff, header, result.errorMessage, void 0, this._stepInfo);
}
function toHaveScreenshotStepTitle(nameOrOptions = {}, optOptions = {}) {
  let name;
  if (typeof nameOrOptions === "object" && !Array.isArray(nameOrOptions))
    name = nameOrOptions.name;
  else
    name = nameOrOptions;
  return Array.isArray(name) ? name.join(import_path.default.sep) : name || "";
}
async function toHaveScreenshot(pageOrLocator, nameOrOptions = {}, optOptions = {}) {
  const testInfo = (0, import_globals.currentTestInfo)();
  if (!testInfo)
    throw new Error(`toHaveScreenshot() must be called during the test`);
  if (testInfo._projectInternal.ignoreSnapshots)
    return { pass: !this.isNot, message: () => "", name: "toHaveScreenshot", expected: nameOrOptions };
  (0, import_util.expectTypes)(pageOrLocator, ["Page", "Locator"], "toHaveScreenshot");
  const [page, locator] = pageOrLocator.constructor.name === "Page" ? [pageOrLocator, void 0] : [pageOrLocator.page(), pageOrLocator];
  const configOptions = testInfo._projectInternal.expect?.toHaveScreenshot || {};
  const helper = new SnapshotHelper(testInfo, "toHaveScreenshot", locator, void 0, configOptions, nameOrOptions, optOptions);
  if (!helper.expectedPath.toLowerCase().endsWith(".png"))
    throw new Error(`Screenshot name "${import_path.default.basename(helper.expectedPath)}" must have '.png' extension`);
  (0, import_util.expectTypes)(pageOrLocator, ["Page", "Locator"], "toHaveScreenshot");
  const style = await loadScreenshotStyles(helper.options.stylePath);
  const timeout = helper.options.timeout ?? this.timeout;
  const expectScreenshotOptions = {
    locator,
    animations: helper.options.animations ?? "disabled",
    caret: helper.options.caret ?? "hide",
    clip: helper.options.clip,
    fullPage: helper.options.fullPage,
    mask: helper.options.mask,
    maskColor: helper.options.maskColor,
    omitBackground: helper.options.omitBackground,
    scale: helper.options.scale ?? "css",
    style,
    isNot: !!this.isNot,
    timeout,
    comparator: helper.options.comparator,
    maxDiffPixels: helper.options.maxDiffPixels,
    maxDiffPixelRatio: helper.options.maxDiffPixelRatio,
    threshold: helper.options.threshold
  };
  const hasSnapshot = import_fs.default.existsSync(helper.expectedPath);
  if (this.isNot) {
    if (!hasSnapshot)
      return helper.handleMissingNegated();
    expectScreenshotOptions.expected = await import_fs.default.promises.readFile(helper.expectedPath);
    const isDifferent = !(await page._expectScreenshot(expectScreenshotOptions)).errorMessage;
    return isDifferent ? helper.handleDifferentNegated() : helper.handleMatchingNegated();
  }
  if (helper.updateSnapshots === "none" && !hasSnapshot)
    return helper.createMatcherResult(`A snapshot doesn't exist at ${helper.expectedPath}.`, false);
  const receiver = locator ? "locator" : "page";
  if (!hasSnapshot) {
    const { actual: actual2, previous: previous2, diff: diff2, errorMessage: errorMessage2, log: log2, timedOut: timedOut2 } = await page._expectScreenshot(expectScreenshotOptions);
    if (errorMessage2) {
      const header2 = (0, import_matcherHint.matcherHint)(this, locator, "toHaveScreenshot", receiver, void 0, void 0, timedOut2 ? timeout : void 0);
      return helper.handleDifferent(actual2, void 0, previous2, diff2, header2, errorMessage2, log2, this._stepInfo);
    }
    return helper.handleMissing(actual2, this._stepInfo);
  }
  const expected = await import_fs.default.promises.readFile(helper.expectedPath);
  expectScreenshotOptions.expected = helper.updateSnapshots === "all" ? void 0 : expected;
  const { actual, previous, diff, errorMessage, log, timedOut } = await page._expectScreenshot(expectScreenshotOptions);
  const writeFiles = () => {
    writeFileSync(helper.expectedPath, actual);
    writeFileSync(helper.actualPath, actual);
    console.log(helper.expectedPath + " is re-generated, writing actual.");
    return helper.createMatcherResult(helper.expectedPath + " running with --update-snapshots, writing actual.", true);
  };
  if (!errorMessage) {
    if (helper.updateSnapshots === "all" && actual && (0, import_utils.compareBuffersOrStrings)(actual, expected)) {
      console.log(helper.expectedPath + " is re-generated, writing actual.");
      return writeFiles();
    }
    return helper.handleMatching();
  }
  if (helper.updateSnapshots === "changed" || helper.updateSnapshots === "all")
    return writeFiles();
  const header = (0, import_matcherHint.matcherHint)(this, void 0, "toHaveScreenshot", receiver, void 0, void 0, timedOut ? timeout : void 0);
  return helper.handleDifferent(actual, expectScreenshotOptions.expected, previous, diff, header, errorMessage, log, this._stepInfo);
}
function writeFileSync(aPath, content) {
  import_fs.default.mkdirSync(import_path.default.dirname(aPath), { recursive: true });
  import_fs.default.writeFileSync(aPath, content);
}
function indent(lines, tab) {
  return lines.replace(/^(?=.+$)/gm, tab);
}
function determineFileExtension(file) {
  if (typeof file === "string")
    return "txt";
  if (compareMagicBytes(file, [137, 80, 78, 71, 13, 10, 26, 10]))
    return "png";
  if (compareMagicBytes(file, [255, 216, 255]))
    return "jpg";
  return "dat";
}
function compareMagicBytes(file, magicBytes) {
  return Buffer.compare(Buffer.from(magicBytes), file.slice(0, magicBytes.length)) === 0;
}
async function loadScreenshotStyles(stylePath) {
  if (!stylePath)
    return;
  const stylePaths = Array.isArray(stylePath) ? stylePath : [stylePath];
  const styles = await Promise.all(stylePaths.map(async (stylePath2) => {
    const text = await import_fs.default.promises.readFile(stylePath2, "utf8");
    return text.trim();
  }));
  return styles.join("\n").trim() || void 0;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  toHaveScreenshot,
  toHaveScreenshotStepTitle,
  toMatchSnapshot
});
