import {
  validateLiquidTemplateContent,
  validateMobilePushContent,
  validateInAppContent,
  getChannelData,
  extractContent,
  addBaseToTemplate,
  validateCarouselCards,
  hasPersonalizationTags,
  checkForPersonalizationTokens,
  isValidEmail,
  isValidMobile,
  formatPhoneNumber,
  getMessageForDevice,
  getTitleForDevice,
} from "../commonUtils";
import { skipTags } from "../tagValidations";
import { SMS_TRAI_VAR } from '../../v2Containers/SmsTrai/Edit/constants';
import { ANDROID, IOS } from '../../v2Containers/CreativesContainer/constants';

describe("validateLiquidTemplateContent", () => {
  const formatMessage = jest.fn((msg, vars) =>
    vars ? `${msg.id}:${vars.unsupportedTags}` : msg.id
  );
  const messages = {
    emailBodyEmptyError: { id: "empty" },
    somethingWentWrong: { id: "wrong" },
    unsupportedTagsValidationError: { id: "unsupported" }
  };
  const eventContextTags = [{ tagName: "bar" }];
  const onError = jest.fn();
  const onSuccess = jest.fn();

  beforeEach(() => {
    jest.clearAllMocks();
    formatMessage.mockImplementation((msg, vars) =>
      vars && vars.unsupportedTags != null
        ? `${msg?.id}:${vars.unsupportedTags}`
        : (msg?.id ?? "unsupported")
    );
  });

  it("calls onError for empty content", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ errors: [], data: [] })
    );
    await validateLiquidTemplateContent("", {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onError).toHaveBeenCalledWith({
      standardErrors: ["empty"],
      liquidErrors: [],
      tabType: undefined
    });
    expect(onSuccess).not.toHaveBeenCalled();
  });

  it("when onError is not provided, it should not throw an error", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ errors: [], data: [] })
    );
    await validateLiquidTemplateContent("", {
      getLiquidTags,
      formatMessage,
      messages,
      eventContextTags
    });
    expect(onError).not.toHaveBeenCalled();
    expect(onSuccess).not.toHaveBeenCalled();
  });

  it("calls onError for API errors", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [{ message: "API error" }], data: [] }, isError: true })
    );
    await validateLiquidTemplateContent("foo", {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onError).toHaveBeenCalledWith({
      standardErrors: [],
      liquidErrors: ["API error"],
      tabType: undefined
    });
    expect(onSuccess).not.toHaveBeenCalled();
  });

  it("categorizes errors as liquidErrors when isError=true with no result.errors (line 194-197)", async () => {
    // isError=true + empty errors array → else-if (isError) branch → liquidErrors = [somethingWrongMsg]
    // Use a plain function (not jest.fn) to avoid resetMocks:true wiping the implementation
    const localFormatMessage = (msg) => msg?.id;
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [] }, isError: true })
    );
    await validateLiquidTemplateContent("foo", {
      getLiquidTags,
      formatMessage: localFormatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onError).toHaveBeenCalledWith({
      standardErrors: [],
      liquidErrors: ["wrong"],
      tabType: undefined
    });
    expect(onSuccess).not.toHaveBeenCalled();
  });

  it("falls back to somethingWentWrong when error.message is not a string (line 189-191)", async () => {
    // error.message is an object, not a string → typeof check fails → returns somethingWrongMsg
    const localFormatMessage = (msg) => msg?.id;
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [{ message: { code: 123 } }], data: [] }, isError: false })
    );
    await validateLiquidTemplateContent("foo", {
      getLiquidTags,
      formatMessage: localFormatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onError).toHaveBeenCalledWith({
      standardErrors: [],
      liquidErrors: ["wrong"],
      tabType: undefined
    });
  });

  it("falls back to somethingWentWrong when error.message is null (line 189-191)", async () => {
    const localFormatMessage = (msg) => msg?.id;
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [{ message: null }], data: [] }, isError: false })
    );
    await validateLiquidTemplateContent("foo", {
      getLiquidTags,
      formatMessage: localFormatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onError).toHaveBeenCalledWith({
      standardErrors: [],
      liquidErrors: ["wrong"],
      tabType: undefined
    });
  });

  it("falls back to somethingWentWrong when error.message is undefined (line 189-191)", async () => {
    const localFormatMessage = (msg) => msg?.id;
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [{}], data: [] }, isError: false })
    );
    await validateLiquidTemplateContent("foo", {
      getLiquidTags,
      formatMessage: localFormatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onError).toHaveBeenCalledWith({
      standardErrors: [],
      liquidErrors: ["wrong"],
      tabType: undefined
    });
  });

  it("maps multiple errors with mixed string/non-string messages (line 188-193)", async () => {
    const localFormatMessage = (msg) => msg?.id;
    const getLiquidTags = jest.fn((content, cb) =>
      cb({
        askAiraResponse: {
          errors: [
            { message: "First error" },
            { message: null },
            { message: "Third error" },
          ],
          data: [],
        },
        isError: false,
      })
    );
    await validateLiquidTemplateContent("foo", {
      getLiquidTags,
      formatMessage: localFormatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onError).toHaveBeenCalledWith({
      standardErrors: [],
      liquidErrors: ["First error", "wrong", "Third error"],
      tabType: undefined
    });
  });

  it("prefers result.errors over isError when both are present (result.errors takes the if-branch)", async () => {
    // Both errors array AND isError=true → the if-branch (line 186) wins over else-if (line 194)
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [{ message: "from errors array" }], data: [] }, isError: true })
    );
    await validateLiquidTemplateContent("foo", {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onError).toHaveBeenCalledWith({
      standardErrors: [],
      liquidErrors: ["from errors array"],
      tabType: undefined
    });
  });

  it("calls onError when API returns success but response.errors has validation errors (e.g. unsupported tag)", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({
        askAiraResponse: {
          errors: [{ message: "Unsupported tag: custom_tag" }],
          data: []
        },
        isError: false
      })
    );
    await validateLiquidTemplateContent("foo", {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onError).toHaveBeenCalledWith({
      standardErrors: [],
      liquidErrors: ["Unsupported tag: custom_tag"],
      tabType: undefined
    });
    expect(onSuccess).not.toHaveBeenCalled();
  });

  it("calls onSuccess when API returns no errors and a single extracted tag (extracted tags are not validated)", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [{ name: "foo" }] }, isError: false })
    );
    await validateLiquidTemplateContent("foo", {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
    expect(onError).not.toHaveBeenCalled();
  });

  it("calls onSuccess for valid content when API returns multiple extracted tags but no errors", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [{ name: "foo" }, { name: "bar" }] }, isError: false })
    );
    await validateLiquidTemplateContent("foo", {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
  });

  it("should skip referral_unique_code tags and not trigger unsupported tag error", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [{ name: "referral_unique_code_C6SOE_userid" }] }, isError: false })
    );
    await validateLiquidTemplateContent("foo", {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      skipTags
    });
    expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
    expect(onError).not.toHaveBeenCalled();
  });

  it("should skip referral_unique_url tags and not trigger unsupported tag error", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [{ name: "referral_unique_url_C6SOE_userid" }] }, isError: false })
    );
    await validateLiquidTemplateContent("foo", {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      skipTags
    });
    expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
    expect(onError).not.toHaveBeenCalled();
  });

  it("should skip referral_unique_code tags with different codes", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [{ name: "referral_unique_code_ABC123_userid" }] }, isError: false })
    );
    await validateLiquidTemplateContent("foo", {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      skipTags
    });
    expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
    expect(onError).not.toHaveBeenCalled();
  });

  it("should skip referral_unique_url tags with different codes", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [{ name: "referral_unique_url_ABC123_userid" }] }, isError: false })
    );
    await validateLiquidTemplateContent("foo", {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      skipTags
    });
    expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
    expect(onError).not.toHaveBeenCalled();
  });

  it("should skip referral_unique_code tags with alphanumeric codes", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [{ name: "referral_unique_code_XYZ789_userid" }] }, isError: false })
    );
    await validateLiquidTemplateContent("foo", {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      skipTags
    });
    expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
    expect(onError).not.toHaveBeenCalled();
  });

  it("should skip referral_unique_url tags with alphanumeric codes", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [{ name: "referral_unique_url_XYZ789_userid" }] }, isError: false })
    );
    await validateLiquidTemplateContent("foo", {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      skipTags
    });
    expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
    expect(onError).not.toHaveBeenCalled();
  });

  it("should skip both referral_unique_code and referral_unique_url tags in the same content", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [{ name: "referral_unique_code_C6SOE_userid" }, { name: "referral_unique_url_C6SOE_userid" }] }, isError: false })
    );
    await validateLiquidTemplateContent("foo", {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      skipTags
    });
    expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
    expect(onError).not.toHaveBeenCalled();
  });

  it("should skip referral_unique_code_632L7_userid tag (real-world example)", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [{ name: "referral_unique_code_632L7_userid" }] }, isError: false })
    );
    await validateLiquidTemplateContent("foo", {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      skipTags
    });
    expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
    expect(onError).not.toHaveBeenCalled();
  });

  it("calls onError with emailBodyEmptyError when validString is falsy", async () => {
    const getLiquidTags = jest.fn((content, cb) => cb({ askAiraResponse: { errors: [], data: [] }, isError: false }));
    const formatMessage = jest.fn((msg) => msg.id);
    const messages = {
      emailBodyEmptyError: { id: 'empty' },
      somethingWentWrong: { id: 'wrong' },
      unsupportedTagsValidationError: { id: 'unsupported' },
    };
    const onError = jest.fn();
    const onSuccess = jest.fn();
    const eventContextTags = [];
    await validateLiquidTemplateContent('', {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
    });
    expect(formatMessage).toHaveBeenCalledWith(messages.emailBodyEmptyError);
    expect(onError).toHaveBeenCalledWith({
      standardErrors: ['empty'],
      liquidErrors: [],
      tabType: undefined,
    });
    expect(onSuccess).not.toHaveBeenCalled();
  });

  it("calls onSuccess when API returns extracted tags from {% for %} template but no errors (extracted tags are not validated)", async () => {
    const content = '{% for item in order.items %} Hello {% endfor %}';
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [{ name: "order.items" }] }, isError: false })
    );
    await validateLiquidTemplateContent(content, {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onSuccess).toHaveBeenCalledWith(content, undefined);
    expect(onError).not.toHaveBeenCalled();
  });

  it("calls onSuccess when API returns extracted tags in {% %} blocks but no errors", async () => {
    const content = '{% for item in order.items %} {{ item.name }} - {{ item.quantity }} {% endfor %}';
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [{ name: "item.name" }, { name: "item.quantity" }] }, isError: false })
    );
    await validateLiquidTemplateContent(content, {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onSuccess).toHaveBeenCalledWith(content, undefined);
    expect(onError).not.toHaveBeenCalled();
  });

  it("calls onSuccess when API returns extracted tags not in content but no errors", async () => {
    const content = 'Some content without the tag';
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [{ name: "extractedTag" }] }, isError: false })
    );
    await validateLiquidTemplateContent(content, {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onSuccess).toHaveBeenCalledWith(content, undefined);
    expect(onError).not.toHaveBeenCalled();
  });

  it("calls onSuccess when API returns tags outside {% %} but no errors", async () => {
    const content = '{{ outsideTag }} {% for item in order.items %} {{ item.name }} {% endfor %}';
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [{ name: "outsideTag" }, { name: "item.name" }] }, isError: false })
    );
    await validateLiquidTemplateContent(content, {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onSuccess).toHaveBeenCalledWith(content, undefined);
    expect(onError).not.toHaveBeenCalled();
  });

  it("calls onSuccess when API returns tag in {{ }} and no errors", async () => {
    const content = 'Hello {{ unsupportedTag }} world';
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [{ name: "unsupportedTag" }] }, isError: false })
    );
    await validateLiquidTemplateContent(content, {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onSuccess).toHaveBeenCalledWith(content, undefined);
    expect(onError).not.toHaveBeenCalled();
  });

  it("calls onSuccess when API returns tag with spacing variants and no errors", async () => {
    const content = '{{ tag}} and {{tag }} and {{ tag }}';
    const getLiquidTags = jest.fn((content, cb) =>
      cb({
        askAiraResponse: {
          errors: [],
          data: [{ name: "tag" }]
        },
        isError: false
      })
    );
    await validateLiquidTemplateContent(content, {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onSuccess).toHaveBeenCalledWith(content, undefined);
    expect(onError).not.toHaveBeenCalled();
  });

  it("calls onSuccess when API returns tag inside {% %} blocks but no errors", async () => {
    const content = '{% for x in some.unsupported %} {{ x }} {% endfor %}';
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [{ name: "some.unsupported" }] }, isError: false })
    );
    await validateLiquidTemplateContent(content, {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onSuccess).toHaveBeenCalledWith(content, undefined);
    expect(onError).not.toHaveBeenCalled();
  });

  it("calls onSuccess when API returns tags with dots in {% %} syntax but no errors", async () => {
    const content = '{% assign myVar = order.items %} Some text';
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [{ name: "order.items" }] }, isError: false })
    );
    await validateLiquidTemplateContent(content, {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onSuccess).toHaveBeenCalledWith(content, undefined);
    expect(onError).not.toHaveBeenCalled();
  });

  describe("addBaseToTemplate - subject property logic", () => {
    it("should use template.versions.base.subject if it exists", () => {
      const template = {
        versions: {
          base: { subject: "base-subject" },
          history: [{ subject: "history-subject" }],
        },
      };
      const result = addBaseToTemplate(template);
      expect(result.versions.base.subject).toBe("base-subject");
    });

    it("should use history[0].subject if template.versions.base.subject does not exist", () => {
      const template = {
        versions: {
          history: [{ subject: "history-subject" }],
        },
      };
      const result = addBaseToTemplate(template);
      expect(result.versions.base.subject).toBe("history-subject");
    });

    it("should set subject as undefined if neither exists", () => {
      const template = {
        versions: {
          history: [{}],
        },
      };
      const result = addBaseToTemplate(template);
      expect(result.versions.base.subject).toBeUndefined();
    });

    it("should return the original template if history is empty", () => {
      const template = {
        versions: {
          history: [],
        },
      };
      expect(addBaseToTemplate(template)).toEqual(template);
    });
  });
});

describe("isValidEmail", () => {
  it("returns true for valid email addresses", () => {
    expect(isValidEmail("user@example.com")).toBe(true);
    expect(isValidEmail("test.user@domain.co")).toBe(true);
    expect(isValidEmail("a@b.co")).toBe(true);
  });

  it("returns false for invalid email addresses", () => {
    expect(isValidEmail("")).toBe(false);
    expect(isValidEmail("invalid")).toBe(false);
    expect(isValidEmail("@nodomain.com")).toBe(false);
    expect(isValidEmail("noatsign.com")).toBe(false);
    expect(isValidEmail("missingtld@domain")).toBe(false);
    expect(isValidEmail("  user@example.com  ")).toBe(false);
  });

  it("returns true for edge case single-char local part", () => {
    expect(isValidEmail("x@y.co")).toBe(true);
  });
});

describe("isValidMobile", () => {
  it("returns true for valid mobile numbers (8-15 digits, no leading zero)", () => {
    expect(isValidMobile("12345678")).toBe(true);
    expect(isValidMobile("9123456789")).toBe(true);
    expect(isValidMobile("123456789012345")).toBe(true);
  });

  it("returns false for invalid mobile numbers", () => {
    expect(isValidMobile("")).toBe(false);
    expect(isValidMobile("01234567")).toBe(false);
    expect(isValidMobile("1234567")).toBe(false);
    expect(isValidMobile("1234567890123456")).toBe(false);
    expect(isValidMobile("abc12345678")).toBe(false);
    expect(isValidMobile("12345 67890")).toBe(false);
  });

  it("returns true for exactly 8 and exactly 15 digits", () => {
    expect(isValidMobile("12345678")).toBe(true);
    expect(isValidMobile("123456789012345")).toBe(true);
  });
});

describe("formatPhoneNumber", () => {
  it("returns empty string for falsy input", () => {
    expect(formatPhoneNumber("")).toBe("");
    expect(formatPhoneNumber(null)).toBe("");
    expect(formatPhoneNumber(undefined)).toBe("");
  });

  it("strips non-digit characters", () => {
    expect(formatPhoneNumber("91 234 567 890")).toBe("91234567890");
    expect(formatPhoneNumber("+91-234567890")).toBe("91234567890");
    expect(formatPhoneNumber("(123) 456-7890")).toBe("1234567890");
  });

  it("returns digits-only string unchanged", () => {
    expect(formatPhoneNumber("9123456789")).toBe("9123456789");
  });

  it("returns empty string for whitespace-only input", () => {
    expect(formatPhoneNumber("   ")).toBe("");
  });
});

describe("hasPersonalizationTags", () => {
  it("returns true when text has liquid tags {{ }}", () => {
    expect(hasPersonalizationTags("Hello {{name}}")).toBe(true);
    expect(hasPersonalizationTags("{{foo}}")).toBe(true);
  });

  it("returns true when text has bracket tags [ ]", () => {
    expect(hasPersonalizationTags("Hello [event.name]")).toBe(true);
    expect(hasPersonalizationTags("[tag]")).toBe(true);
  });

  it("returns false for empty or no tags", () => {
    expect(hasPersonalizationTags("")).toBeFalsy();
    expect(hasPersonalizationTags()).toBeFalsy();
    expect(hasPersonalizationTags("plain text")).toBe(false);
    expect(hasPersonalizationTags("only {{ open")).toBe(false);
  });

  it("returns false when only [ or only ] present without matching pair", () => {
    expect(hasPersonalizationTags("only [ open")).toBe(false);
    expect(hasPersonalizationTags("only ] close")).toBe(false);
  });

  it("returns true when liquid tags have content with spaces", () => {
    expect(hasPersonalizationTags("Hello {{ customer.name }}")).toBe(true);
  });
});

describe("getMessageForDevice", () => {
  it("returns message for device from templateData", () => {
    const templateData = {
      versions: {
        android: { base: { expandableDetails: { message: "Android msg" } } },
        ios: { base: { expandableDetails: { message: "iOS msg" } } },
      },
    };
    expect(getMessageForDevice(templateData, "android")).toBe("Android msg");
    expect(getMessageForDevice(templateData, "ios")).toBe("iOS msg");
  });

  it("returns undefined for missing path", () => {
    expect(getMessageForDevice(null, "android")).toBeUndefined();
    expect(getMessageForDevice({}, "android")).toBeUndefined();
    expect(getMessageForDevice({ versions: {} }, "android")).toBeUndefined();
  });

  it("returns undefined when base exists but expandableDetails is missing", () => {
    expect(getMessageForDevice({ versions: { android: { base: {} } } }, "android")).toBeUndefined();
  });
});

describe("getTitleForDevice", () => {
  it("returns title for device from templateData", () => {
    const templateData = {
      versions: {
        android: { base: { title: "Android title" } },
        ios: { base: { title: "iOS title" } },
      },
    };
    expect(getTitleForDevice(templateData, "android")).toBe("Android title");
    expect(getTitleForDevice(templateData, "ios")).toBe("iOS title");
  });

  it("returns empty string for missing title", () => {
    expect(getTitleForDevice(null, "android")).toBe("");
    expect(getTitleForDevice({}, "android")).toBe("");
    expect(getTitleForDevice({ versions: { android: { base: {} } } }, "android")).toBe("");
  });

  it("returns empty string when base.title is undefined", () => {
    expect(getTitleForDevice({ versions: { android: { base: { title: undefined } } } }, "android")).toBe("");
  });
});

describe("checkForPersonalizationTokens", () => {
  it("returns true when formData contains liquid or bracket tokens", () => {
    expect(checkForPersonalizationTokens({ 0: { content: "Hi {{name}}" } })).toBe(true);
    expect(checkForPersonalizationTokens({ tab1: { message: "Hello [event.id]" } })).toBe(true);
  });

  it("returns false for empty or no tokens", () => {
    expect(checkForPersonalizationTokens(null)).toBe(false);
    expect(checkForPersonalizationTokens({})).toBe(false);
    expect(checkForPersonalizationTokens({ 0: { content: "plain" } })).toBe(false);
  });

  it("returns false for non-object formData", () => {
    expect(checkForPersonalizationTokens("string")).toBe(false);
  });

  it("returns true for two-level nested object containing token", () => {
    expect(checkForPersonalizationTokens({ tab: { body: "Hi {{name}}" } })).toBe(true);
  });

  it("returns false for formData with array value (no string tokens)", () => {
    expect(checkForPersonalizationTokens({ 0: { items: ["a", "b"] } })).toBe(false);
  });
});

describe("validateMobilePushContent", () => {
  const formatMessage = jest.fn(msg => msg.id);
  const messages = {
    emailBodyEmptyError: { id: "empty" },
    somethingWentWrong: { id: "wrong" },
    unsupportedTagsValidationError: { id: "unsupported" }
  };
  const eventContextTags = [{ tagName: "foo" }];
  const onError = jest.fn();
  const onSuccess = jest.fn();

  beforeEach(() => {
    jest.clearAllMocks();
  });

  it("calls onError for empty formData (validateMobilePushContent)", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [] }, isError: false })
    );
    await validateMobilePushContent(
      {},
      {
        getLiquidTags,
        formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
        currentTab: 1
      }
    );
    expect(onError).toHaveBeenCalled();
  });

  it("calls onSuccess for valid android and ios content (validateMobilePushContent)", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [] }, isError: false })
    );
    const formData = [{ foo: "bar" }, { baz: "qux" }];
    await validateMobilePushContent(formData, {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      currentTab: 1
    });
    expect(onSuccess).toHaveBeenCalled();
  });

  it("calls onSuccess with android content when tab is 1", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [] }, isError: false })
    );
    const formData = [{ android: "content" }, { ios: "content" }];
    await validateMobilePushContent(formData, {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      currentTab: 1
    });
    expect(onSuccess).toHaveBeenCalledWith(JSON.stringify(formData[0]), "android");
  });

  it("calls onSuccess with ios content when tab is 2", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [] }, isError: false })
    );
    const formData = [{ android: "content" }, { ios: "content" }];
    await validateMobilePushContent(formData, {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      currentTab: 2
    });
    expect(onSuccess).toHaveBeenCalledWith(JSON.stringify(formData[1]), "ios");
  });

  it("falls back to android content when no tab selected and android exists", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [] }, isError: false })
    );
    const formData = [{ android: "content" }, null];
    await validateMobilePushContent(formData, {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onSuccess).toHaveBeenCalledWith(JSON.stringify(formData[0]), "android");
  });

  it("falls back to ios content when no tab selected and only ios exists", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ errors: [], data: [] })
    );
    const formData = [null, { ios: "content" }];
    await validateMobilePushContent(formData, {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags
    });
    expect(onSuccess).toHaveBeenCalledWith("null", "android");
  });

  it("calls onError for null formData", async () => {
    const getLiquidTags = jest.fn((content, cb) => cb({ askAiraResponse: { errors: [], data: [] }, isError: false }));
    await validateMobilePushContent(
      null,
      {
        getLiquidTags,
        formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
        currentTab: 1,
      },
    );
    expect(onError).toHaveBeenCalled();
  });

  it("calls onError for undefined formData", async () => {
    const getLiquidTags = jest.fn((content, cb) => cb({ askAiraResponse: { errors: [], data: [] }, isError: false }));
    await validateMobilePushContent(
      undefined,
      {
        getLiquidTags,
        formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
        currentTab: 1,
      },
    );
    expect(onError).toHaveBeenCalled();
  });

  it("calls onError for empty string formData", async () => {
    const getLiquidTags = jest.fn((content, cb) => cb({ askAiraResponse: { errors: [], data: [] }, isError: false }));
    await validateMobilePushContent(
      '',
      {
        getLiquidTags,
        formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
        currentTab: 1,
      },
    );
    expect(onError).toHaveBeenCalled();
  });

  it("calls onError for empty array formData", async () => {
    const getLiquidTags = jest.fn((content, cb) => cb({ askAiraResponse: { errors: [], data: [] }, isError: false }));
    await validateMobilePushContent(
      [],
      {
        getLiquidTags,
        formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
        currentTab: 1,
      },
    );
    expect(onError).toHaveBeenCalled();
  });

  it("calls onError for both android and ios missing", async () => {
    const getLiquidTags = jest.fn((content, cb) => cb({ askAiraResponse: { errors: [], data: [] }, isError: false }));
    await validateMobilePushContent(
      [{}, {}],
      {
        getLiquidTags,
        formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      },
    );
    expect(onError).toHaveBeenCalled();
  });

  it("calls onError for both android and ios null", async () => {
    const getLiquidTags = jest.fn((content, cb) => cb({ askAiraResponse: { errors: [], data: [] }, isError: false }));
    await validateMobilePushContent(
      [null, null],
      {
        getLiquidTags,
        formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      },
    );
    expect(onError).toHaveBeenCalled();
  });

  it("calls onError for android and ios as empty strings", async () => {
    const getLiquidTags = jest.fn((content, cb) => cb({ askAiraResponse: { errors: [], data: [] }, isError: false }));
    await validateMobilePushContent(
      [{ android: '' }, { ios: '' }],
      {
        getLiquidTags,
        formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      },
    );
    expect(onError).toHaveBeenCalled();
  });

  it("calls onError for non-object types in formData", async () => {
    const getLiquidTags = jest.fn((content, cb) => cb({ askAiraResponse: { errors: [], data: [] }, isError: false }));
    await validateMobilePushContent(
      [123, 456],
      {
        getLiquidTags,
        formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      },
    );
    expect(onError).toHaveBeenCalled();
  });

  it("calls onError for string types in formData", async () => {
    const getLiquidTags = jest.fn((content, cb) => cb({ askAiraResponse: { errors: [], data: [] }, isError: false }));
    await validateMobilePushContent(
      ['', ''],
      {
        getLiquidTags,
        formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      },
    );
    expect(onError).toHaveBeenCalled();
  });
});

describe("validateInAppContent", () => {
  const formatMessage = jest.fn(msg => msg.id);
  const messages = {
    emailBodyEmptyError: { id: "empty" },
    somethingWentWrong: { id: "wrong" },
    unsupportedTagsValidationError: { id: "unsupported" }
  };
  const eventContextTags = [{ tagName: "foo" }];
  const onError = jest.fn();
  const onSuccess = jest.fn();

  beforeEach(() => {
    jest.clearAllMocks();
  });

  it("calls onError for empty formData (validateInAppContent)", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [] }, isError: false })
    );
    await validateInAppContent(
      {},
      {
        getLiquidTags,
        formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      }
    );
    expect(onError).toHaveBeenCalled();
  });

  it("calls onSuccess for valid android and ios content (validateInAppContent)", async () => {
    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [] }, isError: false })
    );
    const formData = {
      versions: {
        base: {
          content: {
            ANDROID: { title: "t", message: "m", ctas: [{ text: "c" }] },
            IOS: { title: "t2", message: "m2", ctas: [{ text: "c2" }] }
          }
        }
      }
    };
    await validateInAppContent(formData, {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      singleTab: ANDROID,
    });
    expect(onSuccess).toHaveBeenCalled();
  });
  it("calls onSuccess for valid ios content", async () => {

    const getLiquidTags = jest.fn((content, cb) =>
      cb({ askAiraResponse: { errors: [], data: [] }, isError: false })
    );
    const formData = {
      versions: {
        base: {
          content: {
            ANDROID: { title: "t", message: "m", ctas: [{ text: "c" }] },
            IOS: { title: "t2", message: "m2", ctas: [{ text: "c2" }] }
          }
        }
      }
    };
    await validateInAppContent(formData, {
      getLiquidTags,
      formatMessage,
      messages,
      onError,
      onSuccess,
      eventContextTags,
      singleTab: IOS,
    });
    expect(onSuccess).toHaveBeenCalled();
  });
});

describe("getChannelData", () => {
  it("returns converted email content for EMAIL channel with template-content", () => {
    const formData = { base: { en: { "template-content": "<h1>Hello</h1>" } } };
    // Mock convert to just return the input string for test
    jest.mock("html-to-text", () => ({ convert: (str) => str }));
    expect(getChannelData("email", formData)).toBe("");
  });

  it("returns empty string for EMAIL channel with missing template-content", () => {
    const formData = { base: { en: {} } };
    jest.mock("html-to-text", () => ({ convert: (str) => str }));
    expect(getChannelData("email", formData)).toBe("");
  });

  it("returns SMS editor and template name for SMS channel", () => {
    const formData = { base: { "sms-editor": "Hi" }, "template-name": "Test" };
    expect(getChannelData("SMS", formData)).toBe("Hi Test");
  });

  it("returns string with undefineds for SMS when base and template-name are empty or undefined", () => {
    const formData = { base: {}, "template-name": undefined };
    expect(getChannelData("SMS", formData)).toBe("undefined undefined");
  });
  it("returns string with undefineds for SMS when formData is empty string", () => {
    expect(getChannelData("SMS", "")).toBe("undefined undefined");
  });

  it("returns JSON string for unknown channel", () => {
    const formData = { foo: "bar" };
    expect(getChannelData("unknown", formData)).toBe(JSON.stringify(formData));
  });

  it("returns JSON string for null formData", () => {
    expect(getChannelData("SMS", null)).toBe("undefined undefined");
  });

  it("returns JSON string for undefined formData", () => {
    expect(getChannelData("SMS", undefined)).toBe("undefined undefined");
  });

  it("handles missing base/en gracefully for EMAIL", () => {
    expect(getChannelData("email", {})).toBe("");
    expect(getChannelData("email", { base: {} })).toBe("");
    expect(getChannelData("email", { base: { en: null } })).toBe("");
  });
  it("returns empty string for SMS_TRAI_VAR channel with missing fields", () => {
    expect(getChannelData(SMS_TRAI_VAR, {})).toBe("");
    expect(getChannelData(SMS_TRAI_VAR, { versions: {} })).toBe("");
    expect(getChannelData(SMS_TRAI_VAR, { versions: { base: { "updated-sms-editor": [] } } })).toBe("");
  });
});

describe("extractContent", () => {
  it("returns empty string for null or undefined", () => {
    expect(extractContent(null)).toBe("");
    expect(extractContent(undefined)).toBe("");
  });
  it("returns empty string for empty object", () => {
    expect(extractContent({})).toBe("");
  });
  it("returns only title if only title is present", () => {
    expect(extractContent({ title: "Title" })).toBe("Title");
  });
  it("returns only message if only message is present", () => {
    expect(extractContent({ message: "Message" })).toBe("Message");
  });
  it("returns only ctas text if only ctas with text is present", () => {
    expect(extractContent({ ctas: [{ text: "CTA1" }, { text: "CTA2" }] })).toBe("CTA1 CTA2");
  });
  it("returns only ctas actionLink if only ctas with actionLink is present", () => {
    expect(extractContent({ ctas: [{ actionLink: "/link1" }, { actionLink: "/link2" }] })).toBe("/link1 /link2");
  });
  it("returns ctas text if both text and actionLink are present (text preferred)", () => {
    expect(extractContent({ ctas: [{ text: "CTA1", actionLink: "/link1" }, { text: "CTA2", actionLink: "/link2" }] })).toBe("CTA1 CTA2");
  });
  it("returns actionLink if text is missing", () => {
    expect(extractContent({ ctas: [{ actionLink: "/link1" }, { text: null, actionLink: "/link2" }] })).toBe("/link1 /link2");
  });
  it("returns empty string for ctas with neither text nor actionLink", () => {
    expect(extractContent({ ctas: [{}, { text: null }] })).toBe("");
  });
  it("returns all fields joined if all present", () => {
    expect(extractContent({ title: "T", message: "M", ctas: [{ text: "C1" }, { actionLink: "C2" }] })).toBe("T M C1 C2");
  });
  it("handles ctas as empty array", () => {
    expect(extractContent({ title: "T", message: "M", ctas: [] })).toBe("T M");
  });
  it("handles ctas as undefined", () => {
    expect(extractContent({ title: "T", message: "M" })).toBe("T M");
  });
});

describe("validateCarouselCards", () => {
  describe("Valid carousel cards", () => {
    it("should return isValid true for valid image carousel cards", () => {
      const carouselData = [
        {
          bodyText: "Card 1 text",
          imageSrc: "https://example.com/image1.jpg",
          buttons: [{ isSaved: true }],
        },
        {
          bodyText: "Card 2 text",
          imageSrc: "https://example.com/image2.jpg",
          buttons: [{ isSaved: true }, { isSaved: true }],
        },
      ];

      const result = validateCarouselCards(carouselData, "image", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(true);
      expect(result.reasons).toEqual([]);
    });

    it("should return isValid true for valid video carousel cards", () => {
      const carouselData = [
        {
          bodyText: "Card 1 text",
          videoSrc: "https://example.com/video1.mp4",
          buttons: [{ isSaved: true }],
        },
        {
          bodyText: "Card 2 text",
          videoSrc: "https://example.com/video2.mp4",
          buttons: [{ isSaved: true }],
        },
      ];

      const result = validateCarouselCards(carouselData, "video", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(true);
      expect(result.reasons).toEqual([]);
    });

    it("should return isValid true for cards without buttons", () => {
      const carouselData = [
        {
          bodyText: "Card 1 text",
          imageSrc: "https://example.com/image1.jpg",
        },
        {
          bodyText: "Card 2 text",
          imageSrc: "https://example.com/image2.jpg",
        },
      ];

      const result = validateCarouselCards(carouselData, "image", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(true);
      expect(result.reasons).toEqual([]);
    });
  });

  describe("Body errors", () => {
    it("should detect body errors in carousel cards", () => {
      const carouselData = [
        {
          bodyText: "Card 1 text",
          bodyError: "Invalid characters",
          imageSrc: "https://example.com/image1.jpg",
          buttons: [{ isSaved: true }],
        },
        {
          bodyText: "Card 2 text",
          imageSrc: "https://example.com/image2.jpg",
          buttons: [{ isSaved: true }],
        },
      ];

      const result = validateCarouselCards(carouselData, "image", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(false);
      expect(result.reasons).toContain("card 1: bodyError=Invalid characters");
    });

    it("should detect body errors in multiple cards", () => {
      const carouselData = [
        {
          bodyText: "Card 1 text",
          bodyError: "Error 1",
          imageSrc: "https://example.com/image1.jpg",
        },
        {
          bodyText: "Card 2 text",
          bodyError: "Error 2",
          imageSrc: "https://example.com/image2.jpg",
        },
      ];

      const result = validateCarouselCards(carouselData, "image", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(false);
      expect(result.reasons).toContain("card 1: bodyError=Error 1");
      expect(result.reasons).toContain("card 2: bodyError=Error 2");
    });
  });

  describe("Empty body text", () => {
    it("should detect empty body text", () => {
      const carouselData = [
        {
          bodyText: "",
          imageSrc: "https://example.com/image1.jpg",
          buttons: [{ isSaved: true }],
        },
        {
          bodyText: "Card 2 text",
          imageSrc: "https://example.com/image2.jpg",
          buttons: [{ isSaved: true }],
        },
      ];

      const result = validateCarouselCards(carouselData, "image", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(false);
      expect(result.reasons).toContain("card 1: bodyText empty");
    });

    it("should detect missing body text", () => {
      const carouselData = [
        {
          imageSrc: "https://example.com/image1.jpg",
          buttons: [{ isSaved: true }],
        },
        {
          bodyText: "Card 2 text",
          imageSrc: "https://example.com/image2.jpg",
          buttons: [{ isSaved: true }],
        },
      ];

      const result = validateCarouselCards(carouselData, "image", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(false);
      expect(result.reasons).toContain("card 1: bodyText empty");
    });
  });

  describe("Missing image source", () => {
    it("should detect missing imageSrc for image carousel", () => {
      const carouselData = [
        {
          bodyText: "Card 1 text",
          imageSrc: "https://example.com/image1.jpg",
          buttons: [{ isSaved: true }],
        },
        {
          bodyText: "Card 2 text",
          buttons: [{ isSaved: true }],
        },
      ];

      const result = validateCarouselCards(carouselData, "image", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(false);
      expect(result.reasons).toContain("card 2: imageSrc missing");
    });

    it("should not check imageSrc for video carousel", () => {
      const carouselData = [
        {
          bodyText: "Card 1 text",
          videoSrc: "https://example.com/video1.mp4",
          buttons: [{ isSaved: true }],
        },
        {
          bodyText: "Card 2 text",
          videoSrc: "https://example.com/video2.mp4",
          buttons: [{ isSaved: true }],
        },
      ];

      const result = validateCarouselCards(carouselData, "video", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(true);
      expect(result.reasons).not.toContain(expect.stringContaining("imageSrc"));
    });
  });

  describe("Missing video source", () => {
    it("should detect missing videoSrc for video carousel", () => {
      const carouselData = [
        {
          bodyText: "Card 1 text",
          videoSrc: "https://example.com/video1.mp4",
          buttons: [{ isSaved: true }],
        },
        {
          bodyText: "Card 2 text",
          buttons: [{ isSaved: true }],
        },
      ];

      const result = validateCarouselCards(carouselData, "video", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(false);
      expect(result.reasons).toContain("card 2: videoSrc missing");
    });

    it("should not check videoSrc for image carousel", () => {
      const carouselData = [
        {
          bodyText: "Card 1 text",
          imageSrc: "https://example.com/image1.jpg",
          buttons: [{ isSaved: true }],
        },
        {
          bodyText: "Card 2 text",
          imageSrc: "https://example.com/image2.jpg",
          buttons: [{ isSaved: true }],
        },
      ];

      const result = validateCarouselCards(carouselData, "image", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(true);
      expect(result.reasons).not.toContain(expect.stringContaining("videoSrc"));
    });
  });

  describe("Unsaved buttons", () => {
    it("should detect unsaved buttons", () => {
      const carouselData = [
        {
          bodyText: "Card 1 text",
          imageSrc: "https://example.com/image1.jpg",
          buttons: [{ isSaved: false }],
        },
        {
          bodyText: "Card 2 text",
          imageSrc: "https://example.com/image2.jpg",
          buttons: [{ isSaved: true }],
        },
      ];

      const result = validateCarouselCards(carouselData, "image", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(false);
      expect(result.reasons).toContain("card 1: some buttons not saved");
    });

    it("should detect when some buttons are saved and some are not", () => {
      const carouselData = [
        {
          bodyText: "Card 1 text",
          imageSrc: "https://example.com/image1.jpg",
          buttons: [{ isSaved: true }, { isSaved: false }],
        },
      ];

      const result = validateCarouselCards(carouselData, "image", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(false);
      expect(result.reasons).toContain("card 1: some buttons not saved");
    });

    it("should pass when all buttons are saved", () => {
      const carouselData = [
        {
          bodyText: "Card 1 text",
          imageSrc: "https://example.com/image1.jpg",
          buttons: [{ isSaved: true }, { isSaved: true }],
        },
      ];

      const result = validateCarouselCards(carouselData, "image", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(true);
      expect(result.reasons).not.toContain(expect.stringContaining("buttons"));
    });
  });

  describe("Multiple errors", () => {
    it("should collect all errors from a single card", () => {
      const carouselData = [
        {
          bodyError: "Error message",
          imageSrc: "https://example.com/image1.jpg",
          buttons: [{ isSaved: false }],
        },
      ];

      const result = validateCarouselCards(carouselData, "image", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(false);
      expect(result.reasons).toContain("card 1: bodyError=Error message");
      expect(result.reasons).toContain("card 1: bodyText empty");
      expect(result.reasons).toContain("card 1: some buttons not saved");
    });

    it("should collect errors from multiple cards", () => {
      const carouselData = [
        {
          bodyError: "Error 1",
          imageSrc: "https://example.com/image1.jpg",
        },
        {
          bodyText: "Card 2 text",
          buttons: [{ isSaved: false }],
        },
      ];

      const result = validateCarouselCards(carouselData, "image", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(false);
      expect(result.reasons.length).toBeGreaterThan(1);
      expect(result.reasons).toContain("card 1: bodyError=Error 1");
      expect(result.reasons).toContain("card 1: bodyText empty");
      expect(result.reasons).toContain("card 2: imageSrc missing");
      expect(result.reasons).toContain("card 2: some buttons not saved");
    });
  });

  describe("Case insensitivity", () => {
    it("should handle uppercase media type", () => {
      const carouselData = [
        {
          bodyText: "Card 1 text",
          imageSrc: "https://example.com/image1.jpg",
          buttons: [{ isSaved: true }],
        },
      ];

      const result = validateCarouselCards(carouselData, "IMAGE", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(true);
    });

    it("should handle mixed case media type", () => {
      const carouselData = [
        {
          bodyText: "Card 1 text",
          videoSrc: "https://example.com/video1.mp4",
          buttons: [{ isSaved: true }],
        },
      ];

      const result = validateCarouselCards(carouselData, "ViDeO", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(true);
    });
  });

  describe("Edge cases", () => {
    it("should handle empty array", () => {
      const result = validateCarouselCards([], "image", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(true);
      expect(result.reasons).toEqual([]);
    });

    it("should handle null carouselData", () => {
      const result = validateCarouselCards(null, "image", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(true);
      expect(result.reasons).toEqual([]);
    });

    it("should handle undefined carouselData", () => {
      const result = validateCarouselCards(undefined, "image", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      expect(result.isValid).toBe(true);
      expect(result.reasons).toEqual([]);
    });

    it("should handle empty media type", () => {
      const carouselData = [
        {
          bodyText: "Card 1 text",
          imageSrc: "https://example.com/image1.jpg",
          buttons: [{ isSaved: true }],
        },
      ];

      const result = validateCarouselCards(carouselData, "", {
        imageConstant: "IMAGE",
        videoConstant: "VIDEO",
      });

      // Should not check for imageSrc or videoSrc when mediaType is empty
      expect(result.isValid).toBe(true);
    });

    it("should use default constants when options are not provided", () => {
      const carouselData = [
        {
          bodyText: "Card 1 text",
          imageSrc: "https://example.com/image1.jpg",
          buttons: [{ isSaved: true }],
        },
      ];

      const result = validateCarouselCards(carouselData, "image");

      expect(result.isValid).toBe(true);
    });
  });

  describe('extractContent BEE Editor Logic (L404-L412)', () => {
    it('extracts content from beeHtml as string', () => {
      const platformData = {
        title: 'Test Title',
        isBEEeditor: true,
        beeHtml: '<p>BEE HTML Content</p>',
        ctas: [
          { text: 'Click Here', actionLink: 'https://example.com' },
        ],
      };

      const result = extractContent(platformData);

      expect(result).toContain(platformData.title);
      expect(result).toContain(platformData.beeHtml);
      expect(result).toContain(platformData.ctas[0].text);
    });

    it('extracts content from beeHtml as object with value property', () => {
      const platformData = {
        title: 'Test Title',
        isBEEeditor: true,
        beeHtml: { value: '<p>BEE HTML from Object</p>' },
        ctas: [
          { text: 'Button Text' },
        ],
      };

      const result = extractContent(platformData);

      expect(result).toContain(platformData.title);
      expect(result).toContain(platformData.beeHtml.value);
      expect(result).toContain(platformData.ctas[0].text);
    });

    it('handles beeHtml as object without value property', () => {
      const platformData = {
        title: 'Test Title',
        isBEEeditor: true,
        beeHtml: { someOtherProperty: 'data' },
        ctas: [],
      };

      const result = extractContent(platformData);

      // Should extract title and empty beeHtml (since value is undefined)
      expect(result).toContain('Test Title');
      expect(result).not.toContain('someOtherProperty');
    });

    it('extracts ctas with text property', () => {
      const platformData = {
        title: 'Title',
        isBEEeditor: true,
        beeHtml: '<p>Content</p>',
        ctas: [
          { text: 'CTA Text 1' },
          { text: 'CTA Text 2' },
        ],
      };

      const result = extractContent(platformData);

      expect(result).toContain('CTA Text 1');
      expect(result).toContain('CTA Text 2');
    });

    it('extracts ctas with actionLink when text is missing', () => {
      const platformData = {
        title: 'Title',
        isBEEeditor: true,
        beeHtml: '<p>Content</p>',
        ctas: [
          { actionLink: 'https://link1.com' },
          { actionLink: 'https://link2.com' },
        ],
      };

      const result = extractContent(platformData);

      expect(result).toContain('https://link1.com');
      expect(result).toContain('https://link2.com');
    });

    it('filters out falsy values with filter(Boolean)', () => {
      const platformData = {
        title: '',
        isBEEeditor: true,
        beeHtml: null,
        ctas: [
          { text: null, actionLink: null },
          { text: 'Valid Text' },
        ],
      };

      const result = extractContent(platformData);

      // Should only contain 'Valid Text' after filtering
      expect(result).toBe('Valid Text');
    });

    it('handles empty ctas array', () => {
      const platformData = {
        title: 'Title Only',
        isBEEeditor: true,
        beeHtml: '<p>BEE Content</p>',
        ctas: [],
      };

      const result = extractContent(platformData);

      expect(result).toContain('Title Only');
      expect(result).toContain('<p>BEE Content</p>');
    });

    it('handles undefined ctas', () => {
      const platformData = {
        title: 'Title',
        isBEEeditor: true,
        beeHtml: '<p>Content</p>',
        ctas: undefined,
      };

      const result = extractContent(platformData);

      expect(result).toContain('Title');
      expect(result).toContain('<p>Content</p>');
    });

    it('joins all content with spaces', () => {
      const platformData = {
        title: 'Title',
        isBEEeditor: true,
        beeHtml: 'HTML',
        ctas: [
          { text: 'CTA1' },
          { text: 'CTA2' },
        ],
      };

      const result = extractContent(platformData);

      expect(result).toBe('Title HTML CTA1 CTA2');
    });

    it('falls back to regular content when not BEE editor', () => {
      const platformData = {
        title: 'Title',
        message: 'Message',
        isBEEeditor: false,
        beeHtml: '<p>Should be ignored</p>',
        ctas: [
          { text: 'CTA' },
        ],
      };

      const result = extractContent(platformData);

      expect(result).toContain('Title');
      expect(result).toContain('Message');
      expect(result).toContain('CTA');
      expect(result).not.toContain('<p>Should be ignored</p>');
    });

    it('handles null beeHtml', () => {
      const platformData = {
        title: 'Title',
        isBEEeditor: true,
        beeHtml: null,
        ctas: [{ text: 'CTA' }],
      };

      const result = extractContent(platformData);

      expect(result).toContain('Title');
      expect(result).toContain('CTA');
    });

    it('handles undefined beeHtml', () => {
      const platformData = {
        title: 'Title',
        isBEEeditor: true,
        beeHtml: undefined,
        ctas: [],
      };

      const result = extractContent(platformData);

      expect(result).toBe('Title');
    });

    it('handles complex ctas with both text and actionLink', () => {
      const platformData = {
        title: 'Title',
        isBEEeditor: true,
        beeHtml: 'Content',
        ctas: [
          { text: 'CTA Text', actionLink: 'https://example.com' },
        ],
      };

      const result = extractContent(platformData);

      // Should prefer text over actionLink
      expect(result).toContain('CTA Text');
      expect(result).toContain('Title');
      expect(result).toContain('Content');
    });

    it('handles empty string beeHtml', () => {
      const platformData = {
        title: 'Title',
        isBEEeditor: true,
        beeHtml: '',
        ctas: [{ text: 'CTA' }],
      };

      const result = extractContent(platformData);

      expect(result).toBe('Title CTA');
    });
  });
});

describe('hasPersonalizationTags', () => {
  describe('liquid tags {{ }}', () => {
    it('returns true when text contains a liquid tag', () => {
      expect(hasPersonalizationTags('Hello {{user.name}}')).toBe(true);
    });

    it('returns true for text with only opening and closing braces', () => {
      expect(hasPersonalizationTags('{{}}')).toBe(true);
    });

    it('returns false when only {{ is present without }}', () => {
      expect(hasPersonalizationTags('Hello {{user.name')).toBeFalsy();
    });

    it('returns false when only }} is present without {{', () => {
      expect(hasPersonalizationTags('Hello user.name}}')).toBeFalsy();
    });
  });

  describe('event context tags [ ]', () => {
    it('returns true when text contains square bracket tags', () => {
      expect(hasPersonalizationTags('Hello [first_name]')).toBe(true);
    });

    it('returns true for text with only [ ]', () => {
      expect(hasPersonalizationTags('[]')).toBe(true);
    });

    it('returns false when only [ is present without ]', () => {
      expect(hasPersonalizationTags('Hello [first_name')).toBeFalsy();
    });

    it('returns false when only ] is present without [', () => {
      expect(hasPersonalizationTags('Hello first_name]')).toBeFalsy();
    });
  });

  describe('both tag types', () => {
    it('returns true when text contains both liquid and square bracket tags', () => {
      expect(hasPersonalizationTags('{{title}} and [body]')).toBe(true);
    });
  });

  describe('edge cases', () => {
    it('returns falsy for empty string (default parameter)', () => {
      expect(hasPersonalizationTags()).toBeFalsy();
    });

    it('returns falsy for empty string argument', () => {
      expect(hasPersonalizationTags('')).toBeFalsy();
    });

    it('returns falsy for plain text with no tokens', () => {
      expect(hasPersonalizationTags('Hello World')).toBeFalsy();
    });

    it('returns falsy for null', () => {
      expect(hasPersonalizationTags(null)).toBeFalsy();
    });

    it('returns falsy for undefined', () => {
      expect(hasPersonalizationTags(undefined)).toBeFalsy();
    });
  });
});

describe('checkForPersonalizationTokens', () => {
  describe('liquid tags {{ }}', () => {
    it('returns true when a field contains a liquid tag', () => {
      const formData = {
        tab1: { title: 'Hello {{user.name}}' },
      };
      expect(checkForPersonalizationTokens(formData)).toBe(true);
    });

    it('returns true for multiline liquid tag spanning field value', () => {
      const formData = {
        tab1: { body: '{{user\n.name}}' },
      };
      expect(checkForPersonalizationTokens(formData)).toBe(true);
    });
  });

  describe('event context tags [ ]', () => {
    it('returns true when a field contains a square bracket token', () => {
      const formData = {
        tab1: { message: 'Hi [first_name]' },
      };
      expect(checkForPersonalizationTokens(formData)).toBe(true);
    });

    it('returns true for multiline square bracket token', () => {
      const formData = {
        tab1: { body: '[first\nname]' },
      };
      expect(checkForPersonalizationTokens(formData)).toBe(true);
    });
  });

  describe('multiple tabs / fields', () => {
    it('returns true when token is in a nested tab other than the first', () => {
      const formData = {
        tab1: { title: 'plain text' },
        tab2: { body: 'Hello {{name}}' },
      };
      expect(checkForPersonalizationTokens(formData)).toBe(true);
    });

    it('returns true when token is in a field other than the first in a tab', () => {
      const formData = {
        tab1: { title: 'plain text', body: '[event_tag]' },
      };
      expect(checkForPersonalizationTokens(formData)).toBe(true);
    });

    it('returns false when no field in any tab has a token', () => {
      const formData = {
        tab1: { title: 'Hello World', body: 'No tokens here' },
        tab2: { title: 'Also plain', body: 'Still plain' },
      };
      expect(checkForPersonalizationTokens(formData)).toBe(false);
    });
  });

  describe('non-string field values', () => {
    it('ignores numeric field values', () => {
      const formData = {
        tab1: { count: 42 },
      };
      expect(checkForPersonalizationTokens(formData)).toBe(false);
    });

    it('ignores boolean field values', () => {
      const formData = {
        tab1: { enabled: true },
      };
      expect(checkForPersonalizationTokens(formData)).toBe(false);
    });

    it('ignores null field values', () => {
      const formData = {
        tab1: { title: null },
      };
      expect(checkForPersonalizationTokens(formData)).toBe(false);
    });

    it('ignores nested object field values (only one level deep)', () => {
      const formData = {
        tab1: { nested: { title: '{{token}}' } },
      };
      expect(checkForPersonalizationTokens(formData)).toBe(false);
    });
  });

  describe('edge cases', () => {
    it('returns false for null input', () => {
      expect(checkForPersonalizationTokens(null)).toBe(false);
    });

    it('returns false for undefined input', () => {
      expect(checkForPersonalizationTokens(undefined)).toBe(false);
    });

    it('returns false for empty object', () => {
      expect(checkForPersonalizationTokens({})).toBe(false);
    });

    it('returns false for non-object input (string)', () => {
      expect(checkForPersonalizationTokens('{{token}}')).toBe(false);
    });

    it('returns false when tab value is not an object', () => {
      const formData = { tab1: '{{token}}' };
      expect(checkForPersonalizationTokens(formData)).toBe(false);
    });
  });
});

