import { describe, it } from '@ephox/bedrock-client';
import { Fun } from '@ephox/katamari';
import { SugarElement, SugarPosition } from '@ephox/sugar';
import { assert } from 'chai';

import { type Bounds, bounds } from 'ephox/alloy/alien/Boxes';
import type { Bubble, BubbleInstance } from 'ephox/alloy/positioning/layout/Bubble';
import * as LayoutInset from 'ephox/alloy/positioning/layout/LayoutInset';
import type { AnchorBox, AnchorElement, AnchorLayout } from 'ephox/alloy/positioning/layout/LayoutTypes';
import * as Bounder from 'ephox/alloy/positioning/view/Bounder';

interface TestDecisionSpec {
  readonly layout: string;
  readonly x: number;
  readonly y: number;
  readonly candidateY?: number;
}

describe('BounderOverlappingTest', () => {
  const check = (expected: TestDecisionSpec, preference: AnchorLayout[], anchor: AnchorBox, panel: AnchorElement, bubbles: Bubble, bounds: Bounds) => {
    const placee = SugarElement.fromTag('div');
    const actual = Bounder.attempts(placee, preference, anchor, panel, bubbles, bounds);
    assert.equal(actual.layout, expected.layout, 'layout');
    assert.equal(actual.rect.x, expected.x, 'X');
    assert.equal(actual.rect.y, expected.y, 'Y');
    if (expected.candidateY !== undefined) {
      assert.equal(actual.testY, expected.candidateY, 'Candidate Y');
    }
  };

  // Layout is for boxes with a bubble pointing to a cursor position (vertically aligned to nearest side)
  const bubble = (): Bubble => {
    const northeast = (): BubbleInstance => ({
      offset: SugarPosition(1, -1),
      classesOn: [ ],
      classesOff: [ ]
    });

    const northwest = (): BubbleInstance => ({
      offset: SugarPosition(-1, -1),
      classesOn: [ ],
      classesOff: [ ]
    });

    const southeast = (): BubbleInstance => ({
      offset: SugarPosition(1, 2),
      classesOn: [ ],
      classesOff: [ ]
    });

    const southwest = (): BubbleInstance => ({
      offset: SugarPosition(-1, 2),
      classesOn: [ ],
      classesOff: [ ]
    });

    const south = (): BubbleInstance => ({
      offset: SugarPosition(-1, -1),
      classesOn: [ ],
      classesOff: [ ]
    });

    const north = (): BubbleInstance => ({
      offset: SugarPosition(-1, -1),
      classesOn: [ ],
      classesOff: [ ]
    });

    const east = (): BubbleInstance => ({
      offset: SugarPosition(1, 1),
      classesOn: [ ],
      classesOff: [ ]
    });

    const west = (): BubbleInstance => ({
      offset: SugarPosition(1, 1),
      classesOn: [ ],
      classesOff: [ ]
    });

    const notImplemented = Fun.die('Not implemented');

    return {
      northwest: notImplemented,
      northeast: notImplemented,
      southwest: notImplemented,
      southeast: notImplemented,
      south: notImplemented,
      north: notImplemented,
      east: notImplemented,
      west: notImplemented,
      insetSouthwest: southwest,
      insetSoutheast: southeast,
      insetSouth: south,
      insetNorthwest: northwest,
      insetNortheast: northeast,
      insetNorth: north,
      insetWest: west,
      insetEast: east
    };
  };

  const four = [ LayoutInset.southeast, LayoutInset.southwest, LayoutInset.northeast, LayoutInset.northwest ];
  const two = [ LayoutInset.east, LayoutInset.west ];

  const panelBox = bounds(0, 0, 15, 10);
  const bigPanel = bounds(0, 0, 100, 100);
  const view = bounds(50, 50, 350, 220);
  const bubb = bubble();

  it('an empty input array is invalid and just returns anchor coordinates', () => {
    check({
      layout: 'none',
      x: 0,
      y: 0
    }, [], bounds(0, 0, 10, 10), bounds(0, 0, 50, 50), bubble(), bounds(0, 0, 1000, 1000));

    check({
      layout: 'none',
      x: 100,
      y: 0
    }, [], bounds(100, 0, 200, 50), bounds(0, 0, 150, 25), bubble(), bounds(0, 0, 1000, 1000));
  });

  /*
   * The expected values include the calculations that layout and bounder are doing
   */

  it('southeast', () => {
    const anchor = bounds(340, 45, 80, 80);
    check({
      layout: 'layout-inset-southeast',
      x: anchor.x + 1,                            // 341
      y: anchor.bottom - panelBox.height + 2      // 117
    }, four, anchor, panelBox, bubb, view);
  });

  it('southwest', () => {
    const anchor = bounds(40, 45, 50, 50);
    check({
      layout: 'layout-inset-southwest',
      x: anchor.right - panelBox.width - 1,       // 74
      y: anchor.bottom - panelBox.height + 2      // 87
    }, four, anchor, panelBox, bubb, view);
  });

  it('northeast', () => {
    const anchor = bounds(340, 235, 80, 80);
    check({
      layout: 'layout-inset-northeast',
      x: anchor.x + 1,                            // 341
      y: anchor.y - 1                             // 234
    }, four, anchor, panelBox, bubb, view);
  });

  it('northwest', () => {
    const anchor = bounds(40, 235, 50, 50);
    check({
      layout: 'layout-inset-northwest',
      x: anchor.right - panelBox.width - 1,       // 74
      y: anchor.y - 1                             // 234
    }, four, anchor, panelBox, bubb, view);
  });

  it('all fit -> southeast because of order of preference', () => {
    const anchor = bounds(270, 100, 100, 100);
    check({
      layout: 'layout-inset-southeast',
      x: anchor.x + 1,                            // 271
      y: anchor.bottom - panelBox.height + 2      // 192
    }, four, anchor, panelBox, bubb, view);
  });

  it('none -> best fit is southeast', () => {
    const anchor = bounds(60, 40, view.width, view.height + 15);
    check({
      layout: 'layout-inset-southeast',
      x: anchor.x + 1,                            // 61
      y: view.bottom - bigPanel.height,           // 170 - Constrained within viewport
      candidateY: anchor.bottom - bigPanel.height + 2, // 177
    }, four, anchor, bigPanel, bubb, view);
  });

  it('none -> best fit is southwest', () => {
    const anchor = bounds(40, 40, view.width + 15, view.height);
    check({
      layout: 'layout-inset-southwest',
      x: view.right - bigPanel.width - 1,         // 299 - Constrained within viewport
      y: anchor.bottom - bigPanel.height + 2,     // 177
    }, four, anchor, bigPanel, bubb, view);
  });

  it('none -> best fit is northeast', () => {
    const anchor = bounds(45, 55, view.width + 15, view.height);
    check({
      layout: 'layout-inset-northeast',
      x: view.x + 1,                              // 51 - Constrained within viewport
      y: anchor.y - 1,                            // 54
    }, four, anchor, bigPanel, bubb, view);
  });

  it('none -> best fit is northwest', () => {
    const anchor = bounds(40, 45, view.width, view.height + 15);
    check({
      layout: 'layout-inset-northwest',
      x: anchor.right - bigPanel.width - 1,       // 289
      y: view.y,                                  // 50 - constrained within viewport
      candidateY: anchor.y - 1,                   // 44
    }, four, anchor, bigPanel, bubb, view);
  });

  it('southeast (1px short on x and y)', () => {
    // Note: The additional -2 here is to account for the southeast bubble y offsets
    const anchor = bounds(view.y + 1, view.bottom - 20 - 2 - 1, 20, 20);
    check({
      layout: 'layout-inset-southeast',
      x: anchor.x + 1,                            // 51
      y: anchor.bottom - panelBox.height + 2,     // 259
    }, four, anchor, panelBox, bubb, view);
  });

  it('southeast (exactly for x and y)', () => {
    // Note: The additional -2 here is to account for the southeast bubble y offsets
    const anchor = bounds(view.y, view.bottom - 20 - 2, 20, 20);
    check({
      layout: 'layout-inset-southeast',
      x: anchor.x + 1,                            // 50
      y: anchor.bottom - panelBox.height + 2,     // 260
    }, four, anchor, panelBox, bubb, view);
  });

  it('southeast -> southwest (1px too far on x)', () => {
    // Note: The additional -2 here is to account for the southeast bubble y offsets
    const anchor = bounds(view.y - 1, view.bottom - 30 - 2, 30, 30);
    check({
      layout: 'layout-inset-southwest',
      x: anchor.right - panelBox.width - 1,       // 63
      y: anchor.bottom - panelBox.height + 2,     // 260
    }, four, anchor, panelBox, bubb, view);
  });

  it('southeast -> northeast (1px too far on y)', () => {
    // Note: The additional -2 here is to account for the southeast bubble y offsets
    const anchor = bounds(view.y, view.bottom - 30 - 2 + 1, 30, 30);
    check({
      layout: 'layout-inset-northeast',
      x: anchor.x + 1,                            // 50
      y: anchor.y - 1                             // 238
    }, four, anchor, panelBox, bubb, view);
  });

  it('southeast -> northwest (1px too far on x and y)', () => {
    // Note: The additional -2 here is to account for the southeast bubble y offsets
    const anchor = bounds(view.y - 1, view.bottom - 30 - 2 + 1, 30, 30);
    check({
      layout: 'layout-inset-northwest',
      x: anchor.right - panelBox.width - 1,       // 63
      y: anchor.y - 1                             // 238
    }, four, anchor, panelBox, bubb, view);
  });

  it('east', () => {
    const anchor = bounds(55, 150, 100, 100);
    check({
      layout: 'layout-inset-east',
      x: anchor.right - panelBox.width + 1,                         // 141
      y: anchor.y + (anchor.height / 2) - (panelBox.height / 2) + 1 // 196
    }, two, anchor, panelBox, bubb, view);
  });

  it('none near bottom left -> best fit is east (limited to bottom bounds)', () => {
    const anchor = bounds(55, 240, 100, 100);
    check({
      layout: 'layout-inset-east',
      x: anchor.right - panelBox.width + 1,       // 141
      y: view.bottom - panelBox.height            // 260 - constrained within viewport
    }, two, anchor, panelBox, bubb, view);
  });

  it('none near top left -> best fit is east (limited to top bounds)', () => {
    const anchor = bounds(55, 0, 100, 100);
    check({
      layout: 'layout-inset-east',
      x: anchor.right - panelBox.width + 1,       // 141
      y: view.y,                                  // 50 - constrained within viewport
    }, two, anchor, panelBox, bubb, view);
  });

  it('west', () => {
    const anchor = bounds(320, 150, 100, 100);
    check({
      layout: 'layout-inset-west',
      x: anchor.x + 1,                                              // 321
      y: anchor.y + (anchor.height / 2) - (panelBox.height / 2) + 1 // 196
    }, two, anchor, panelBox, bubb, view);
  });

  it('none near bottom right -> best fit is west (limited to bottom bounds)', () => {
    const anchor = bounds(320, 240, 100, 100);
    check({
      layout: 'layout-inset-west',
      x: anchor.x + 1,                            // 321
      y: view.bottom - panelBox.height            // 260 - constrained within viewport
    }, two, anchor, panelBox, bubb, view);
  });

  it('none near top right -> best fit is west (limited to top bounds)', () => {
    const anchor = bounds(320, 0, 100, 100);
    check({
      layout: 'layout-inset-west',
      x: anchor.x + 1,                            // 321
      y: view.y,                                  // 50 - constrained within viewport
    }, two, anchor, panelBox, bubb, view);
  });
});
