import { injectStyle } from '@descope-ui/common/components-helpers';
import { draggableMixin } from '../../mixins';
import { createBaseClass } from '../../baseClasses/createBaseClass';
import { compose } from '../../helpers';
import { getComponentName, observeChildren } from '../../helpers/componentHelpers';

export const componentName = getComponentName('recaptcha');

const observedAttributes = ['enabled', 'site-key', 'action', 'enterprise'];

const BaseClass = createBaseClass({
  componentName,
  baseSelector: ':host > div',
});
class RawRecaptcha extends BaseClass {
  static get observedAttributes() {
    return observedAttributes.concat(BaseClass.observedAttributes || []);
  }

  attributeChangedCallback(attrName, oldValue, newValue) {
    super.attributeChangedCallback?.(attrName, oldValue, newValue);
    if (oldValue !== newValue) {
      if (attrName === 'enabled') {
        this.#toggleRecaptcha(newValue === 'true');
      }
    }
  }

  renderRecaptcha(enabled) {
    if (this.children.length) {
      this.updatePreview();
      return;
    }

    this.toggleRecaptchaEles(enabled);
  }

  toggleRecaptchaEles(enabled) {
    if (enabled) {
      this.recaptchaEle.style.display = '';
      this.mockRecaptchaEle.style.display = 'none';
    } else {
      this.recaptchaEle.style.display = 'none';
      this.mockRecaptchaEle.style.display = '';
    }
  }

  constructor() {
    super();

    this.attachShadow({ mode: 'open' }).innerHTML = `
    <div class="badge">
      <span id="recaptcha"></span>
      <img src="https://imgs.descope.com/connectors/templates/recaptcha/recaptcha-big.png" alt="recaptcha"/>
    </div>
    <slot></slot>
	`;

    injectStyle(
      `
      :host {
        display: inline-flex;
      }
      :host > div:not(.hidden) {
        display: flex;
      }
      :host #recaptcha .grecaptcha-badge {
        box-shadow: none !important;
      }
      :host #recaptcha .grecaptcha-logo {
        height: 100%;
      }
      :host #recaptcha {
				width: 100%;
        height: 100%;
			}
      :host img {
        width: 256px;
      }
      :host([full-width="true"]) {
        width: 100%;
      }
      .hidden {
        display: none;
      }
        `,
      this
    );

    this.recaptchaEle = this.baseElement.querySelector('#recaptcha');
    this.mockRecaptchaEle = this.baseElement.querySelector('img');
    this.badge = this.shadowRoot.querySelector('.badge');
  }

  init() {
    super.init?.();

    observeChildren(this, this.updatePreview.bind(this));
  }

  updatePreview() {
    if (this.children.length) {
      this.recaptchaEle.style.display = 'none';
      this.mockRecaptchaEle.style.display = 'none';
      this.badge.classList.add('hidden');
    } else {
      this.toggleRecaptchaEles(this.enabled);
      this.badge.classList.remove('hidden');
    }
  }

  get enterprise() {
    return this.getAttribute('enterprise') === 'true';
  }

  get siteKey() {
    return this.getAttribute('site-key');
  }

  get action() {
    return this.getAttribute('action') || 'load';
  }

  get enabled() {
    return this.getAttribute('enabled') === 'true';
  }

  get scriptURL() {
    const url = new URL('https://www.google.com/recaptcha/');
    url.pathname += `${this.enterprise ? 'enterprise' : 'api'}.js`;
    url.searchParams.append('onload', 'onRecaptchaLoadCallback');
    url.searchParams.append('render', 'explicit');
    return url.toString();
  }

  #toggleRecaptcha(enabled) {
    this.renderRecaptcha(enabled);
    if (enabled) {
      this.#createOnLoadScript();
      if (!document.getElementById('recaptcha-script')) {
        this.#loadRecaptchaScript();
      } else {
        window.onRecaptchaLoadCallback();
      }
    }
  }

  #loadRecaptchaScript() {
    const script = document.createElement('script');
    script.src = this.scriptURL;
    script.async = true;
    script.id = 'recaptcha-script';
    script.defer = true;
    document.body.appendChild(script);
  }

  get grecaptchaInstance() {
    return this.enterprise ? window.grecaptcha?.enterprise : window.grecaptcha;
  }

  #getNewToken(grecaptchaInstance, currentNode, recaptchaWidgetId) {
    if (!this.isConnected) {
      return;
    }

    grecaptchaInstance.ready(() => {
      // clone the node and append it to the body so that it can be used by the grepcaptcha script
      const cloneNode = currentNode
        .querySelector('textarea[name^="g-recaptcha-response"]')
        ?.cloneNode();
      if (cloneNode) {
        cloneNode.style.display = 'none';
        document.body.appendChild(cloneNode);
      }

      // cleaning up the recaptcha element we added to the body
      const removeCloneNode = () => {
        cloneNode.remove();
      };

      if (!this.siteKey) {
        return;
      }
      // we should pass recaptchaWidgetId, but this does not allow us to run execute multiple times
      // also calling grecaptchaInstance.reset() does not work
      const exec = grecaptchaInstance?.execute(recaptchaWidgetId, { action: this.action });
      exec.then((token, e) => {
        if (e) {
          // eslint-disable-next-line no-console
          console.warn('could not execute recaptcha', e);
        } else {
          this.updateComponentsContext({
            risktoken: token,
            riskaction: this.action,
          });
          // if the component is still connected, we should try to get a new token before the token expires (2 minutes)
          if (this.isConnected) {
            setTimeout(() => {
              this.#getNewToken(grecaptchaInstance, currentNode, recaptchaWidgetId);
            }, 110000);
          }
        }
        removeCloneNode();
      });
    });
  }

  #createOnLoadScript() {
    window.onRecaptchaLoadCallback = () => {
      const currentNode = this.recaptchaEle;

      // if there are child nodes, it means that the recaptcha was already rendered
      if (currentNode.hasChildNodes()) {
        return;
      }

      const { grecaptchaInstance } = this;

      if (!grecaptchaInstance) {
        return;
      }

      setTimeout(() => {
        const recaptchaWidgetId = grecaptchaInstance.render(currentNode, {
          sitekey: this.siteKey,
          badge: 'inline',
          size: 'invisible',
        });
        this.#getNewToken(grecaptchaInstance, currentNode, recaptchaWidgetId);
      }, 0);
    };
  }
}

export const RecaptchaClass = compose(draggableMixin)(RawRecaptcha);
