/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

goog.module('goog.ui.editor.BubbleTest');
goog.setTestOnly();

const Bubble = goog.require('goog.ui.editor.Bubble');
const Component = goog.require('goog.ui.Component');
const Corner = goog.require('goog.positioning.Corner');
const EventType = goog.require('goog.events.EventType');
const OverflowStatus = goog.require('goog.positioning.OverflowStatus');
const TagName = goog.require('goog.dom.TagName');
const TestHelper = goog.require('goog.testing.editor.TestHelper');
const dom = goog.require('goog.dom');
const events = goog.require('goog.events');
const googString = goog.require('goog.string');
const product = goog.require('goog.userAgent.product');
const style = goog.require('goog.style');
const testSuite = goog.require('goog.testing.testSuite');
const testingEvents = goog.require('goog.testing.events');

let testHelper;
let fieldDiv;
let bubble;
let link;
let link2;
let panelId;

/**
 * This is a helper function for setting up the target element with a
 * given direction.
 * @param {string} dir The direction of the target element, 'ltr' or 'rtl'.
 * @param {boolean=} preferTopPosition Whether to prefer placing the bubble
 *     above the element instead of below it. Defaults to preferring below.
 */
function prepareTargetWithGivenDirection(dir, preferTopPosition = undefined) {
  style.setStyle(document.body, 'direction', dir);

  fieldDiv.style.direction = dir;
  fieldDiv.innerHTML = '<a href="http://www.google.com">Google</a>';
  link = fieldDiv.firstChild;

  panelId = bubble.addPanel('A', 'Link', link, (el) => {
    el.innerHTML = '<div style="border:1px solid blue;">B</div>';
  }, preferTopPosition);
}

/**
 * This is a helper function for getting the expected position of the bubble.
 * (align to the right or the left of the target element).  Align left by
 * default and align right if alignRight is true. The expected Y is
 * unaffected by alignment.
 * @param {boolean=} alignRight Sets the expected alignment to be right.
 */
function getExpectedBubblePositionWithGivenAlignment(alignRight = undefined) {
  const targetPosition = style.getFramedPageOffset(link, window);
  const targetWidth = link.offsetWidth;
  /** @suppress {visibility} suppression added to enable type checking */
  const bubbleSize = style.getSize(bubble.bubbleContainer_);
  const expectedBubbleX = alignRight ?
      targetPosition.x + targetWidth - bubbleSize.width :
      targetPosition.x;
  /** @suppress {visibility} suppression added to enable type checking */
  const expectedBubbleY =
      link.offsetHeight + targetPosition.y + Bubble.VERTICAL_CLEARANCE_;

  return {x: expectedBubbleX, y: expectedBubbleY};
}

testSuite({
  setUpPage() {
    fieldDiv = dom.getElement('field');
    const viewportSize = dom.getViewportSize();
    // Some tests depends on enough size of viewport.
    if (viewportSize.width < 600 || viewportSize.height < 440) {
      window.moveTo(0, 0);
      window.resizeTo(640, 480);
    }
  },

  setUp() {
    testHelper = new TestHelper(fieldDiv);
    testHelper.setUpEditableElement();

    bubble = new Bubble(document.body, 999);

    fieldDiv.innerHTML = '<a href="http://www.google.com">Google</a>' +
        '<a href="http://www.google.com">Google2</a>';
    link = fieldDiv.firstChild;
    link2 = fieldDiv.lastChild;

    window.scrollTo(0, 0);
    style.setStyle(document.body, 'direction', 'ltr');
    style.setStyle(document.getElementById('field'), 'position', 'static');
  },

  tearDown() {
    if (panelId) {
      bubble.removePanel(panelId);
      panelId = null;
    }
    testHelper.tearDownEditableElement();
  },

  /** @suppress {visibility} suppression added to enable type checking */
  testCreateBubbleWithLinkPanel() {
    const id = googString.createUniqueString();
    panelId = bubble.addPanel('A', 'Link', link, (container) => {
      container.innerHTML = `<span id="${id}">Test</span>`;
    });
    assertNotNull('Bubble should be created', bubble.bubbleContents_);
    assertNotNull('Added element should be present', dom.getElement(id));
    assertTrue('Bubble should be visible', bubble.isVisible());
  },

  testCloseBubble() {
    this.testCreateBubbleWithLinkPanel();

    let count = 0;
    events.listen(bubble, Component.EventType.HIDE, () => {
      count++;
    });

    bubble.removePanel(panelId);
    panelId = null;

    assertFalse('Bubble should not be visible', bubble.isVisible());
    assertEquals('Hide event should be dispatched', 1, count);
  },

  testCloseBox() {
    this.testCreateBubbleWithLinkPanel();

    let count = 0;
    events.listen(bubble, Component.EventType.HIDE, () => {
      count++;
    });

    /** @suppress {visibility} suppression added to enable type checking */
    const closeBox = dom.getElementsByTagNameAndClass(
        TagName.DIV, 'tr_bubble_closebox', bubble.bubbleContainer_)[0];
    testingEvents.fireClickSequence(closeBox);
    panelId = null;

    assertFalse('Bubble should not be visible', bubble.isVisible());
    assertEquals('Hide event should be dispatched', 1, count);
  },

  /** @suppress {visibility} suppression added to enable type checking */
  testViewPortSizeMonitorEvent() {
    this.testCreateBubbleWithLinkPanel();

    let numCalled = 0;
    bubble.reposition = () => {
      numCalled++;
    };

    assertNotUndefined(
        'viewPortSizeMonitor_ should not be undefined',
        bubble.viewPortSizeMonitor_);
    bubble.viewPortSizeMonitor_.dispatchEvent(EventType.RESIZE);

    assertEquals('reposition not called', 1, numCalled);
  },

  testBubblePositionPreferTop() {
    let called = false;
    /** @suppress {visibility} suppression added to enable type checking */
    bubble.positionAtAnchor_ = (targetCorner, bubbleCorner, overflow) => {
      called = true;

      // Assert that the bubble is positioned below the target.
      assertEquals(Corner.TOP_START, targetCorner);
      assertEquals(Corner.BOTTOM_START, bubbleCorner);

      return OverflowStatus.NONE;
    };
    prepareTargetWithGivenDirection('ltr', true);
    assertTrue(called);
  },

  testBubblePosition() {
    panelId = bubble.addPanel('A', 'Link', link, () => {});
    /** @suppress {visibility} suppression added to enable type checking */
    const CLEARANCE = Bubble.VERTICAL_CLEARANCE_;
    /** @suppress {visibility} suppression added to enable type checking */
    const bubbleContainer = bubble.bubbleContainer_;

    // The field is at a normal place, alomost the top of the viewport, and
    // there is enough space at the bottom of the field.
    const targetPos = style.getFramedPageOffset(link, window);
    const targetSize = style.getSize(link);
    /** @suppress {checkTypes} suppression added to enable type checking */
    let pos = style.getFramedPageOffset(bubbleContainer);
    assertEquals(targetPos.y + targetSize.height + CLEARANCE, pos.y);
    assertEquals(targetPos.x, pos.x);

    // Move the target to the bottom of the viewport.
    const field = document.getElementById('field');
    const fieldPos = style.getFramedPageOffset(field, window);
    /** @suppress {visibility} suppression added to enable type checking */
    fieldPos.y += bubble.dom_.getViewportSize().height -
        (targetPos.y + targetSize.height);
    style.setStyle(field, 'position', 'absolute');
    style.setPosition(field, fieldPos);
    bubble.reposition();
    const bubbleSize = style.getSize(bubbleContainer);
    const targetPosition = style.getFramedPageOffset(link, window);
    /** @suppress {checkTypes} suppression added to enable type checking */
    pos = style.getFramedPageOffset(bubbleContainer);
    assertEquals(targetPosition.y - CLEARANCE - bubbleSize.height, pos.y);
  },

  testBubblePositionRightAligned() {
    if (product.SAFARI) {
      // TODO(user): Disabled so we can get the rest of the Closure test
      // suite running in a continuous build. Will investigate later.
      return;
    }

    prepareTargetWithGivenDirection('rtl');

    const expectedPos = getExpectedBubblePositionWithGivenAlignment(true);
    /**
     * @suppress {checkTypes,visibility} suppression added to enable type
     * checking
     */
    const pos = style.getFramedPageOffset(bubble.bubbleContainer_);
    assertRoughlyEquals(expectedPos.x, pos.x, 0.1);
    assertRoughlyEquals(expectedPos.y, pos.y, 0.1);
  },

  /**
   * Test for bug 1955511, the bubble should align to the right side
   * of the target element when the bubble is RTL, regardless of the
   * target element's directionality.
   * @suppress {visibility} suppression added to enable type checking
   */
  testBubblePositionLeftToRight() {
    if (product.SAFARI) {
      // TODO(user): Disabled so we can get the rest of the Closure test
      // suite running in a continuous build. Will investigate later.
      return;
    }

    style.setStyle(bubble.bubbleContainer_, 'direction', 'ltr');
    prepareTargetWithGivenDirection('rtl');

    const expectedPos = getExpectedBubblePositionWithGivenAlignment();
    /**
     * @suppress {checkTypes,visibility} suppression added to enable type
     * checking
     */
    const pos = style.getFramedPageOffset(bubble.bubbleContainer_);
    assertRoughlyEquals(expectedPos.x, pos.x, 0.1);
    assertRoughlyEquals(expectedPos.y, pos.y, 0.1);
  },

  /**
   * Test for bug 1955511, the bubble should align to the left side
   * of the target element when the bubble is LTR, regardless of the
   * target element's directionality.
   * @suppress {visibility} suppression added to enable type checking
   */
  testBubblePositionRightToLeft() {
    style.setStyle(bubble.bubbleContainer_, 'direction', 'rtl');
    prepareTargetWithGivenDirection('ltr');

    const expectedPos = getExpectedBubblePositionWithGivenAlignment(true);
    /**
     * @suppress {checkTypes,visibility} suppression added to enable type
     * checking
     */
    const pos = style.getFramedPageOffset(bubble.bubbleContainer_);
    assertEquals(expectedPos.x, pos.x);
    assertEquals(expectedPos.y, pos.y);
  },
});
