/**
 * Copyright (c) HashiCorp, Inc.
 * SPDX-License-Identifier: MPL-2.0
 */

/*eslint no-console: "off", ember/no-jquery: "off", ember/no-global-jquery: "off"*/

const elementNotFound = 'Element not found';
// this error comes from our pageObject `find `function
const pageObjectNotFound = 'PageObject not found';
const cannotDestructure = "Cannot destructure property 'context'";
const cannotReadContext = "Cannot read property 'context' of undefined";

// checking for existence of pageObjects is pretty difficult
// errors are thrown but we should check to make sure its the error that we
// want and not another real error
// to make things more difficult depending on how you reference the pageObject
// an error with a different message is thrown for example:

// pageObject[thing]() will give you a Element not found error

// whereas:

// const obj = pageObject[thing]; obj() will give you a 'cannot destructure error'
// and in CI it will give you a 'cannot read property' error

// the difference in CI could be a difference due to headless vs headed browser
// or difference in Chrome/browser versions

// ideally we wouldn't be checking on error messages at all, but we want to make sure
// that real errors are picked up by the tests, so if this gets unmanageable at any point
// look at checking for the instance of e being TypeError or similar
const isExpectedError = function (e) {
  return [pageObjectNotFound, elementNotFound, cannotDestructure, cannotReadContext].some((item) =>
    e.message.startsWith(item)
  );
};
const dont = `( don't| shouldn't| can't)?`;
export default function (scenario, assert, find, currentPage, $) {
  scenario
    .then(
      [`I${dont} $verb the $pageObject object`],
      async function (negative, verb, element, next) {
        let res = element[verb];
        if (typeof res === 'function') {
          res = res.call(element);
        }
        assert[negative ? 'notOk' : 'ok'](res, this.step);

        await res;

        setTimeout(() => next());
      }
    )
    .then(
      [
        `I${dont} $verb the $pageObject object with value "$value"`,
        `I${dont} $verb the $pageObject object from $yaml`,
      ],
      function (negative, verb, element, data, next) {
        assert[negative ? 'notOk' : 'ok'](element[verb](data));
        setTimeout(() => next());
      }
    )
    .then(`the $pageObject object is(n't)? $state`, function (element, negative, state, next) {
      assert[negative ? 'notOk' : 'ok'](element[state]);
      setTimeout(() => next());
    })
    .then(`I${dont} see $num of the $pageObject objects`, function (negative, num, element, next) {
      assert[negative ? 'notEqual' : 'equal'](
        element.length,
        num,
        `Expected to${negative ? ' not' : ''} see ${num} ${element.key}`
      );
      setTimeout(() => next());
    })
    .then(['I see $num of the $component object'], function (num, component) {
      assert.equal(
        currentPage()[component].length,
        num,
        `Expected to see ${num} items in the ${component} object`
      );
    })
    .then(
      'I see $property on the $component like yaml\n$yaml',
      function (property, component, yaml) {
        const _component = currentPage()[component];
        const iterator = new Array(_component.length).fill(true);
        // this will catch if we get aren't managing to select a component
        assert.ok(iterator.length > 0);
        iterator.forEach(function (item, i, arr) {
          const actual =
            typeof _component.objectAt(i)[property] === 'undefined'
              ? null
              : _component.objectAt(i)[property];

          // anything coming from the DOM is going to be text/strings
          // if the yaml has numbers, cast them to strings
          // TODO: This would get problematic for deeper objects
          // will have to look to do this recursively
          const expected = typeof yaml[i] === 'number' ? yaml[i].toString() : yaml[i];

          assert.deepEqual(
            actual,
            expected,
            `Expected to see ${property} on ${component}[${i}] as ${JSON.stringify(
              expected
            )}, was ${JSON.stringify(actual)}`
          );
        });
      }
    )
    .then(
      'I see $property on the $component vertically like yaml\n$yaml',
      function (property, component, yaml) {
        const _component = find(component);
        const iterator = new Array(_component.length).fill(true);
        assert.ok(iterator.length > 0);

        const items = _component.toArray().sort((a, b) => {
          return (
            $(a.scope).get(0).getBoundingClientRect().top -
            $(b.scope).get(0).getBoundingClientRect().top
          );
        });

        iterator.forEach(function (item, i, arr) {
          const actual = typeof items[i][property] === 'undefined' ? null : items[i][property];

          const expected = typeof yaml[i] === 'number' ? yaml[i].toString() : yaml[i];

          assert.deepEqual(
            actual,
            expected,
            `Expected to see ${property} on ${component}[${i}] as ${JSON.stringify(
              expected
            )}, was ${JSON.stringify(actual)}`
          );
        });
      }
    )
    .then(
      [`I${dont} see $property`, `I${dont} see $property on the $component`],
      function (negative, property, component) {
        const isNegative = typeof negative !== 'undefined';
        let message = `Expected to${isNegative ? ' not' : ''} see ${property}`;
        let target;
        try {
          if (typeof component === 'string') {
            property = `${component}.${property}`;
            message = `${message} on ${component}`;
          }
          target = find(property);
        } catch (e) {
          if (isNegative) {
            if (isExpectedError(e)) {
              assert.ok(true, message);
              return Promise.resolve();
            } else {
              console.error(e);
              throw e;
            }
          } else {
            console.error(e);
            throw e;
          }
        }
        if (typeof target === 'function') {
          if (isNegative) {
            assert.throws(
              function () {
                target();
              },
              function (e) {
                return isExpectedError(e);
              },
              message
            );
            return Promise.resolve();
          } else {
            try {
              target = target();
            } catch (e) {
              console.error(e);
              throw new Error(`The '${property}' page object doesn't exist`);
            }
          }
        }
        assert[isNegative ? 'notOk' : 'ok'](target, message);

        // always return promise and handle the fact that `target` could be async
        return Promise.resolve().then(() => target);
      }
    )
    .then(
      [
        `I see $property on the $component (contains|like) "$value"`,
        `I see $property on the $component (contains|like) '$value'`,
      ],
      function (property, component, containsLike, value) {
        let target;

        if (typeof component === 'string') {
          property = `${component}.${property}`;
        }
        target = find(property);

        if (containsLike === 'like') {
          assert.equal(
            target,
            value,
            `Expected to see ${property} on ${component} as ${value}, was ${target}`
          );
        } else {
          assert.ok(
            target.indexOf(value) !== -1,
            `Expected to see ${property} on ${component} within ${value}, was ${target}`
          );
        }
      }
    )
    .then(['I see $property like "$value"'], function (property, value) {
      const target = currentPage()[property];
      assert.equal(target, value, `Expected to see ${property} as ${value}, was ${target}`);
    });
}
