import {ActionTrust_Enum} from '#core/constants/action-constants';

import {Services} from '#service';

import {dev, user, userAssert} from '#utils/log';

import {
  SOURCE_ORIGIN_PARAM,
  assertHttpsUrl,
  checkCorsUrl,
  isProxyOrigin,
} from './url';

/**
 * @param {!./service/ampdoc-impl.AmpDoc} ampdoc
 * @return {!Promise}
 */
export function installGlobalSubmitListenerForDoc(ampdoc) {
  // Register global submit event listener only if the amp-form
  // extension is used. Allowing the usage of native forms, otherwise.
  return ampdoc.whenExtensionsKnown().then(() => {
    if (ampdoc.declaresExtension('amp-form')) {
      ampdoc
        .getRootNode()
        .addEventListener('submit', onDocumentFormSubmit_, true);
    }
  });
}

/**
 * Intercept any submit on the current document and prevent invalid submits from
 * going through.
 *
 * @param {!Event} e
 */
export function onDocumentFormSubmit_(e) {
  if (e.defaultPrevented) {
    return;
  }

  const form = dev().assertElement(e.target);
  if (!form || form.tagName != 'FORM') {
    return;
  }

  // amp-form extension will add novalidate to all forms to manually trigger
  // validation. In that case `novalidate` doesn't have the same meaning.
  const isAmpFormMarked = form.classList.contains('i-amphtml-form');
  let shouldValidate;
  if (isAmpFormMarked) {
    shouldValidate = !form.hasAttribute('amp-novalidate');
  } else {
    shouldValidate = !form.hasAttribute('novalidate');
  }

  // Safari does not trigger validation check on submission, hence we
  // trigger it manually. In other browsers this would never execute since
  // the submit event wouldn't be fired if the form is invalid.
  if (shouldValidate && form.checkValidity && !form.checkValidity()) {
    e.preventDefault();
  }

  const inputs = form.elements;
  for (let i = 0; i < inputs.length; i++) {
    userAssert(
      !inputs[i].name || inputs[i].name != SOURCE_ORIGIN_PARAM,
      'Illegal input name, %s found: %s',
      SOURCE_ORIGIN_PARAM,
      inputs[i]
    );
  }

  const action = form.getAttribute('action');
  const actionXhr = form.getAttribute('action-xhr');
  const method = (form.getAttribute('method') || 'GET').toUpperCase();

  if (actionXhr) {
    assertHttpsUrl(actionXhr, form, 'action-xhr');
    userAssert(
      !isProxyOrigin(actionXhr),
      'form action-xhr should not be on AMP CDN: %s',
      form
    );
    checkCorsUrl(actionXhr);
  }
  if (action) {
    assertHttpsUrl(action, form, 'action');
    userAssert(
      !isProxyOrigin(action),
      'form action should not be on AMP CDN: %s',
      form
    );
    checkCorsUrl(action);
  }

  if (method == 'GET') {
    userAssert(
      actionXhr || action,
      'form action-xhr or action attribute is required for method=GET: %s',
      form
    );
  } else if (method == 'POST') {
    if (action) {
      const TAG = 'form';
      user().error(
        TAG,
        'action attribute is invalid for method=POST: %s',
        form
      );
    }

    if (!actionXhr) {
      e.preventDefault();
      userAssert(
        false,
        'Only XHR based (via action-xhr attribute) submissions are support ' +
          'for POST requests. %s',
        form
      );
    }
  }

  const target = form.getAttribute('target');
  if (target) {
    userAssert(
      target == '_blank' || target == '_top',
      'form target=%s is invalid can only be _blank or _top: %s',
      target,
      form
    );
  } else {
    form.setAttribute('target', '_top');
  }

  // For xhr submissions relay the submission event through action service to
  // allow us to wait for amp-form (and possibly its dependencies) to execute
  // the actual submission. For non-XHR GET we let the submission go through
  // to allow _blank target to work.
  if (actionXhr) {
    e.preventDefault();

    // It's important to stop propagation of the submission to avoid double
    // handling of the event in cases were we are delegating to action service
    // to deliver the submission event.
    e.stopImmediatePropagation();

    const actions = Services.actionServiceForDoc(form);
    actions.execute(
      form,
      'submit',
      /*args*/ null,
      /*source*/ form,
      /*caller*/ form,
      e,
      ActionTrust_Enum.HIGH
    );
  }
}
