import { proxyCustomElement, HTMLElement, createEvent, h } from '@stencil/core/internal/client';
import { p as parseFormData } from './form-data.js';
import { g as getOrder, s as setOrder, c as clearOrder } from './checkouts.js';
import { f as finalizeSession, c as createOrUpdateOrder } from './index3.js';
import { g as getQueryArg } from './fetch.js';
import { d as defineCustomElement$1 } from './sc-line-items-provider2.js';
import { g as getQueryArgs, b as buildQueryString } from './add-query-args.js';

/**
 * Internal dependencies
 */
/**
 * Removes arguments from the query string of the url
 *
 * @param {string}    url  URL.
 * @param {...string} args Query Args.
 *
 * @example
 * ```js
 * const newUrl = removeQueryArgs( 'https://wordpress.org?foo=bar&bar=baz&baz=foobar', 'foo', 'bar' ); // https://wordpress.org?baz=foobar
 * ```
 *
 * @return {string} Updated URL.
 */

function removeQueryArgs(url) {
  const queryStringIndex = url.indexOf('?');

  if (queryStringIndex === -1) {
    return url;
  }

  const query = getQueryArgs(url);
  const baseURL = url.substr(0, queryStringIndex);

  for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
    args[_key - 1] = arguments[_key];
  }

  args.forEach(arg => delete query[arg]);
  const queryString = buildQueryString(query);
  return queryString ? baseURL + '?' + queryString : baseURL;
}

const getURLLineItems = () => {
  // check the url query first
  return getQueryArg(window.location.href, 'line_items');
};
const getURLCoupon = () => {
  // check the url query first
  return (getQueryArg(window.location.href, 'coupon') || '').toUpperCase();
};
/**
 * Attempt to get the session id
 *
 * @param formId The form id.
 * @param order The order
 * @param refresh Should we refresh?
 *
 * @returns string
 */
const getSessionId = (formId, order, modified) => {
  // if we already have an ID set, return that:
  if (order === null || order === void 0 ? void 0 : order.id) {
    return order.id;
  }
  // check the url query first
  const urlId = getQueryArg(window.location.href, 'order');
  if (urlId) {
    return urlId;
  }
  const savedModification = localStorage.getItem(`${formId}-modified`);
  // nothing saved.
  if (!savedModification) {
    return window.localStorage.getItem(formId);
  }
  // this has not been modified, so return the saved session id.
  if (modified && savedModification === modified) {
    return window.localStorage.getItem(formId);
  }
  return '';
};
const removeSessionId = formId => {
  // check id in localstorage
  return window.localStorage.removeItem(formId);
};

const ScSessionProvider = /*@__PURE__*/ proxyCustomElement(class extends HTMLElement {
  constructor() {
    super();
    this.__registerHost();
    this.__attachShadow();
    this.scUpdateOrderState = createEvent(this, "scUpdateOrderState", 7);
    this.scUpdateDraftState = createEvent(this, "scUpdateDraftState", 7);
    this.scPaid = createEvent(this, "scPaid", 7);
    this.scError = createEvent(this, "scError", 7);
    this.scSetState = createEvent(this, "scSetState", 7);
    /** Are we in test or live mode. */
    this.mode = 'live';
    /** An array of prices to pre-fill in the form. */
    this.prices = [];
    /** Currency Code */
    this.currencyCode = 'usd';
    /** The processor. */
    this.processor = 'stripe';
  }
  handleUpdateSession(e) {
    const { data, options } = e.detail;
    if (options === null || options === void 0 ? void 0 : options.silent) {
      this.update({ ...this.defaultFormData(), ...data });
    }
    else {
      this.loadUpdate(data);
    }
  }
  handlePricesChange() {
    let line_items = this.addInitialPrices() || [];
    line_items = this.addPriceChoices(line_items);
    if (!(line_items === null || line_items === void 0 ? void 0 : line_items.length)) {
      return;
    }
    return this.loadUpdate({ line_items });
  }
  /**
   * Finalize the order.
   *
   * @returns {Promise<Order>}
   */
  async finalize() {
    return await this.handleFormSubmit();
  }
  /** Get the order from the store. */
  order() {
    return getOrder(this === null || this === void 0 ? void 0 : this.formId, this.mode);
  }
  getProcessor() {
    switch (this.processor) {
      case 'paypal':
      case 'paypal-card':
        return 'paypal';
      default:
        return 'stripe';
    }
  }
  getPaymentIntent() {
    var _a;
    const processor_type = this.getProcessor();
    return (_a = this.paymentIntents) === null || _a === void 0 ? void 0 : _a[processor_type];
  }
  /**
   * Handles the form submission.
   * @param e
   */
  async handleFormSubmit() {
    var _a, _b, _c, _d, _e, _f, _g, _h, _j;
    this.scError.emit({});
    const payment_intent = this.getPaymentIntent();
    this.scSetState.emit('FINALIZE');
    // Get current form state.
    const json = await this.el.querySelector('sc-form').getFormJson();
    let data = parseFormData(json);
    // first lets make sure the session is updated before we process it.
    try {
      await this.update({ ...this.defaultFormData(), ...data });
    }
    catch (e) {
      console.error(e);
      this.scSetState.emit('REJECT');
      this.handleErrorResponse(e);
    }
    // Important: Stripe needs a payment intent ahead of time, or the
    // order will not be attached to the payment.
    if (((_a = this.order()) === null || _a === void 0 ? void 0 : _a.total_amount) > 0 && !payment_intent && this.getProcessor() === 'stripe' && this.stripePaymentElement) {
      this.scError.emit({ message: 'Something went wrong. Please try again.' });
      console.error('No payment intent found.');
      return this.scSetState.emit('REJECT');
    }
    // first validate server-side and get key
    try {
      const order = await finalizeSession({
        id: (_b = this.order()) === null || _b === void 0 ? void 0 : _b.id,
        data,
        query: {
          ...this.defaultFormQuery(),
          ...((payment_intent === null || payment_intent === void 0 ? void 0 : payment_intent.id) ? { payment_intent_id: payment_intent === null || payment_intent === void 0 ? void 0 : payment_intent.id } : {}),
        },
        processor: this.getProcessor(),
      });
      // payment intent must match what we sent to make sure it's attached to an order.
      if (((_c = this.order()) === null || _c === void 0 ? void 0 : _c.total_amount) > 0 && (payment_intent === null || payment_intent === void 0 ? void 0 : payment_intent.id) && ((_d = order === null || order === void 0 ? void 0 : order.payment_intent) === null || _d === void 0 ? void 0 : _d.id) !== (payment_intent === null || payment_intent === void 0 ? void 0 : payment_intent.id)) {
        console.error('Payment intent mismatch', payment_intent === null || payment_intent === void 0 ? void 0 : payment_intent.id, (_e = order === null || order === void 0 ? void 0 : order.payment_intent) === null || _e === void 0 ? void 0 : _e.id);
        this.scError.emit({ message: 'Something went wrong. Please try again.' });
        return this.scSetState.emit('REJECT');
      }
      // the order is paid
      if ((order === null || order === void 0 ? void 0 : order.status) === 'paid') {
        this.scPaid.emit();
      }
      setOrder(order, this.formId);
      return this.order();
    }
    catch (e) {
      // handle old price versions by refreshing.
      if (((_g = (_f = e === null || e === void 0 ? void 0 : e.additional_errors) === null || _f === void 0 ? void 0 : _f[0]) === null || _g === void 0 ? void 0 : _g.code) === 'order.line_items.old_price_versions') {
        await this.loadUpdate({
          id: (_h = this.order()) === null || _h === void 0 ? void 0 : _h.id,
          data: {
            status: 'draft',
            refresh_price_versions: true,
          },
        });
        return;
      }
      // make it a draft again and resubmit if status is incorrect.
      if (['order.invalid_status_transition'].includes(e === null || e === void 0 ? void 0 : e.code)) {
        await this.loadUpdate({
          id: (_j = this.order()) === null || _j === void 0 ? void 0 : _j.id,
          data: {
            status: 'draft',
          },
        });
        this.handleFormSubmit();
        return;
      }
      this.handleErrorResponse(e);
    }
  }
  /**
   * Handle paid event and update the
   */
  async handlePaid() {
    this.scSetState.emit('PAID');
  }
  async handlePayError() {
    this.scSetState.emit('REJECT');
  }
  /** Handles coupon updates. */
  async handleCouponApply(e) {
    const promotion_code = e.detail;
    this.scError.emit({});
    this.loadUpdate({
      discount: {
        ...(promotion_code ? { promotion_code } : {}),
      },
    });
  }
  /** Handle the error response. */
  handleErrorResponse(e) {
    // expired
    if ((e === null || e === void 0 ? void 0 : e.code) === 'rest_cookie_invalid_nonce') {
      this.scSetState.emit('EXPIRE');
      return;
    }
    // paid
    if ((e === null || e === void 0 ? void 0 : e.code) === 'readonly') {
      removeSessionId(this.groupId);
      window.location.assign(removeQueryArgs(window.location.href, 'order'));
      return;
    }
    // something went wrong
    if (e === null || e === void 0 ? void 0 : e.message) {
      this.scError.emit(e);
    }
    // handle curl timeout errors.
    if ((e === null || e === void 0 ? void 0 : e.code) === 'http_request_failed') {
      this.scError.emit({ message: 'Something went wrong. Please reload the page and try again.' });
    }
    this.scSetState.emit('REJECT');
  }
  /** Default data always sent with the session. */
  defaultFormData() {
    var _a;
    return {
      return_url: window.top.location.href,
      currency: ((_a = this.order()) === null || _a === void 0 ? void 0 : _a.currency) || this.currencyCode,
      live_mode: this.mode !== 'test',
      group_key: this.groupId,
    };
  }
  defaultFormQuery() {
    return {
      form_id: this.formId,
    };
  }
  /** Find or create session on load. */
  componentDidLoad() {
    this.findOrCreateOrder();
  }
  /** Find or create an order */
  findOrCreateOrder() {
    var _a, _b, _c;
    // get initial data from the url
    const initial_data = this.getInitialDataFromUrl();
    // remove discount from window.
    if ((_a = initial_data === null || initial_data === void 0 ? void 0 : initial_data.discount) === null || _a === void 0 ? void 0 : _a.promotion_code) {
      window.history.replaceState({}, document.title, removeQueryArgs(window.location.href, 'coupon'));
    }
    // remove line items from window.
    if ((_b = initial_data === null || initial_data === void 0 ? void 0 : initial_data.line_items) === null || _b === void 0 ? void 0 : _b.length) {
      window.history.replaceState({}, document.title, removeQueryArgs(window.location.href, 'line_items'));
    }
    // we have line items, don't load any existing session (overwrite)
    if ((_c = initial_data === null || initial_data === void 0 ? void 0 : initial_data.line_items) === null || _c === void 0 ? void 0 : _c.length) {
      clearOrder(this.formId, this.mode);
      return this.fetch(initial_data);
    }
    // check if we have an existing session.
    const id = getSessionId(this.groupId, this.order, this.modified);
    // fetch or initialize a session.
    return id && this.persist ? this.fetch(initial_data) : this.initialize(initial_data);
  }
  getInitialDataFromUrl() {
    let initial_data = {};
    // Coupon code.
    const promotion_code = getURLCoupon();
    initial_data = {
      ...initial_data,
      ...(promotion_code ? { discount: { promotion_code } } : {}),
    };
    // Line items.
    const line_items = getURLLineItems();
    initial_data = {
      ...initial_data,
      ...(line_items && (line_items === null || line_items === void 0 ? void 0 : line_items.length) ? { line_items } : {}),
    };
    return initial_data;
  }
  /** Looks through children and finds items needed for initial session. */
  async initialize(args = {}) {
    let line_items = this.addInitialPrices() || [];
    line_items = this.addPriceChoices(line_items);
    if (line_items === null || line_items === void 0 ? void 0 : line_items.length) {
      return this.loadUpdate({ line_items, ...args });
    }
    else {
      return this.loadUpdate({ ...args });
    }
  }
  /** Add prices that are passed into the component. */
  addInitialPrices() {
    var _a;
    if (!((_a = this === null || this === void 0 ? void 0 : this.prices) === null || _a === void 0 ? void 0 : _a.length))
      return [];
    // check for id
    if (this.prices.some(p => !(p === null || p === void 0 ? void 0 : p.id))) {
      return;
    }
    // add prices that are passed into this component.
    return this.prices.map(price => {
      return {
        price_id: price.id,
        quantity: price.quantity,
      };
    });
  }
  /** Add default prices that may be selected in form. */
  addPriceChoices(line_items = []) {
    const elements = this.el.querySelectorAll('[price-id]');
    elements.forEach(el => {
      // handle price choices.
      if (el.checked) {
        line_items.push({
          quantity: el.quantity || 1,
          price_id: el.priceId,
          ...(el.defaultAmount ? { ad_hoc_amount: el.defaultAmount } : {}),
        });
      }
      // handle donation default amount.
      if (el.defaultAmount) {
        line_items.push({
          quantity: el.quantity || 1,
          price_id: el.priceId,
          ad_hoc_amount: el.defaultAmount,
        });
      }
    });
    return line_items;
  }
  getSessionId() {
    var _a, _b;
    if ((_a = this.order()) === null || _a === void 0 ? void 0 : _a.id) {
      return (_b = this.order()) === null || _b === void 0 ? void 0 : _b.id;
    }
    const id = getSessionId(this.groupId, this.order, this.modified);
    if (this.persist) {
      return id;
    }
  }
  /** Fetch a session. */
  async fetch(args = {}) {
    this.loadUpdate({ status: 'draft', ...args });
  }
  /** Update a session */
  async update(data = {}, query = {}) {
    try {
      const order = (await createOrUpdateOrder({
        id: this.getSessionId(),
        data: {
          ...this.defaultFormData(),
          ...data,
        },
        query: {
          ...this.defaultFormQuery(),
          ...query,
        },
      }));
      setOrder(order, this.formId);
    }
    catch (e) {
      // reinitalize if order not found.
      if (['order.not_found'].includes(e === null || e === void 0 ? void 0 : e.code)) {
        clearOrder(this.formId, this.mode);
        return this.initialize();
      }
      console.error(e);
      throw e;
    }
  }
  /** Updates a session with loading status changes. */
  async loadUpdate(data = {}) {
    try {
      this.scSetState.emit('FETCH');
      await this.update({ ...this.defaultFormData(), ...data });
      this.scSetState.emit('RESOLVE');
    }
    catch (e) {
      this.handleErrorResponse(e);
    }
  }
  render() {
    return (h("sc-line-items-provider", { order: this.order(), onScUpdateLineItems: e => this.loadUpdate({ line_items: e.detail }) }, h("slot", null)));
  }
  get el() { return this; }
  static get watchers() { return {
    "prices": ["handlePricesChange"]
  }; }
}, [1, "sc-session-provider", {
    "groupId": [1, "group-id"],
    "mode": [1],
    "formId": [2, "form-id"],
    "modified": [1],
    "prices": [16],
    "currencyCode": [1, "currency-code"],
    "persist": [4],
    "setState": [16],
    "processor": [1],
    "successUrl": [1, "success-url"],
    "paymentIntents": [16],
    "stripePaymentElement": [4, "stripe-payment-element"],
    "finalize": [64]
  }, [[0, "scUpdateOrder", "handleUpdateSession"], [0, "scFormSubmit", "handleFormSubmit"], [0, "scPaid", "handlePaid"], [0, "scPayError", "handlePayError"], [0, "scApplyCoupon", "handleCouponApply"]]]);
function defineCustomElement() {
  if (typeof customElements === "undefined") {
    return;
  }
  const components = ["sc-session-provider", "sc-line-items-provider"];
  components.forEach(tagName => { switch (tagName) {
    case "sc-session-provider":
      if (!customElements.get(tagName)) {
        customElements.define(tagName, ScSessionProvider);
      }
      break;
    case "sc-line-items-provider":
      if (!customElements.get(tagName)) {
        defineCustomElement$1();
      }
      break;
  } });
}
defineCustomElement();

export { ScSessionProvider as S, defineCustomElement as d };
