import { Component, Element, Event, h, Listen, Method, Prop, Watch } from '@stencil/core';
import { removeQueryArgs } from '@wordpress/url';
import { parseFormData } from '../../../functions/form-data';
import { clearOrder, getOrder, setOrder } from '../../../store/checkouts';
import { createOrUpdateOrder, finalizeSession } from '../../../services/session';
import { getSessionId, getURLCoupon, getURLLineItems, removeSessionId } from './helpers/session';
export class ScSessionProvider {
  constructor() {
    /** 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)));
  }
  static get is() { return "sc-session-provider"; }
  static get encapsulation() { return "shadow"; }
  static get properties() { return {
    "groupId": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "string",
        "resolved": "string",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "Group id"
      },
      "attribute": "group-id",
      "reflect": false
    },
    "mode": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "'test' | 'live'",
        "resolved": "\"live\" | \"test\"",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "Are we in test or live mode."
      },
      "attribute": "mode",
      "reflect": false,
      "defaultValue": "'live'"
    },
    "formId": {
      "type": "number",
      "mutable": false,
      "complexType": {
        "original": "number",
        "resolved": "number",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "The checkout form id"
      },
      "attribute": "form-id",
      "reflect": false
    },
    "modified": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "string",
        "resolved": "string",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "Whent the post was modified."
      },
      "attribute": "modified",
      "reflect": false
    },
    "prices": {
      "type": "unknown",
      "mutable": false,
      "complexType": {
        "original": "Array<PriceChoice>",
        "resolved": "PriceChoice[]",
        "references": {
          "Array": {
            "location": "global"
          },
          "PriceChoice": {
            "location": "import",
            "path": "../../../types"
          }
        }
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "An array of prices to pre-fill in the form."
      },
      "defaultValue": "[]"
    },
    "currencyCode": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "string",
        "resolved": "string",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "Currency Code"
      },
      "attribute": "currency-code",
      "reflect": false,
      "defaultValue": "'usd'"
    },
    "persist": {
      "type": "boolean",
      "mutable": false,
      "complexType": {
        "original": "boolean",
        "resolved": "boolean",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "Should we persist the session."
      },
      "attribute": "persist",
      "reflect": false
    },
    "setState": {
      "type": "unknown",
      "mutable": false,
      "complexType": {
        "original": "(state: string) => void",
        "resolved": "(state: string) => void",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "Set the checkout state"
      }
    },
    "processor": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "ProcessorName",
        "resolved": "\"paypal\" | \"paypal-card\" | \"stripe\"",
        "references": {
          "ProcessorName": {
            "location": "import",
            "path": "../../../types"
          }
        }
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "The processor."
      },
      "attribute": "processor",
      "reflect": false,
      "defaultValue": "'stripe'"
    },
    "successUrl": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "string",
        "resolved": "string",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "Url to redirect upon success."
      },
      "attribute": "success-url",
      "reflect": false
    },
    "paymentIntents": {
      "type": "unknown",
      "mutable": false,
      "complexType": {
        "original": "PaymentIntents",
        "resolved": "PaymentIntents",
        "references": {
          "PaymentIntents": {
            "location": "import",
            "path": "../../../types"
          }
        }
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "Holds all available payment intents."
      }
    },
    "stripePaymentElement": {
      "type": "boolean",
      "mutable": false,
      "complexType": {
        "original": "boolean",
        "resolved": "boolean",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "Are we using the Stripe payment element?"
      },
      "attribute": "stripe-payment-element",
      "reflect": false
    }
  }; }
  static get events() { return [{
      "method": "scUpdateOrderState",
      "name": "scUpdateOrderState",
      "bubbles": true,
      "cancelable": true,
      "composed": true,
      "docs": {
        "tags": [],
        "text": "Update line items event"
      },
      "complexType": {
        "original": "Order",
        "resolved": "Order",
        "references": {
          "Order": {
            "location": "import",
            "path": "../../../types"
          }
        }
      }
    }, {
      "method": "scUpdateDraftState",
      "name": "scUpdateDraftState",
      "bubbles": true,
      "cancelable": true,
      "composed": true,
      "docs": {
        "tags": [],
        "text": "Update line items event"
      },
      "complexType": {
        "original": "Order",
        "resolved": "Order",
        "references": {
          "Order": {
            "location": "import",
            "path": "../../../types"
          }
        }
      }
    }, {
      "method": "scPaid",
      "name": "scPaid",
      "bubbles": true,
      "cancelable": true,
      "composed": true,
      "docs": {
        "tags": [],
        "text": ""
      },
      "complexType": {
        "original": "void",
        "resolved": "void",
        "references": {}
      }
    }, {
      "method": "scError",
      "name": "scError",
      "bubbles": true,
      "cancelable": true,
      "composed": true,
      "docs": {
        "tags": [],
        "text": "Error event"
      },
      "complexType": {
        "original": "{ message: string; code?: string; data?: any; additional_errors?: any } | {}",
        "resolved": "{ message: string; code?: string; data?: any; additional_errors?: any; } | {}",
        "references": {}
      }
    }, {
      "method": "scSetState",
      "name": "scSetState",
      "bubbles": true,
      "cancelable": true,
      "composed": true,
      "docs": {
        "tags": [],
        "text": "Set the state"
      },
      "complexType": {
        "original": "FormStateSetter",
        "resolved": "\"EXPIRE\" | \"FETCH\" | \"FINALIZE\" | \"PAID\" | \"REJECT\" | \"RESOLVE\"",
        "references": {
          "FormStateSetter": {
            "location": "import",
            "path": "../../../types"
          }
        }
      }
    }]; }
  static get methods() { return {
    "finalize": {
      "complexType": {
        "signature": "() => Promise<any>",
        "parameters": [],
        "references": {
          "Promise": {
            "location": "global"
          }
        },
        "return": "Promise<any>"
      },
      "docs": {
        "text": "Finalize the order.",
        "tags": [{
            "name": "returns",
            "text": undefined
          }]
      }
    }
  }; }
  static get elementRef() { return "el"; }
  static get watchers() { return [{
      "propName": "prices",
      "methodName": "handlePricesChange"
    }]; }
  static get listeners() { return [{
      "name": "scUpdateOrder",
      "method": "handleUpdateSession",
      "target": undefined,
      "capture": false,
      "passive": false
    }, {
      "name": "scFormSubmit",
      "method": "handleFormSubmit",
      "target": undefined,
      "capture": false,
      "passive": false
    }, {
      "name": "scPaid",
      "method": "handlePaid",
      "target": undefined,
      "capture": false,
      "passive": false
    }, {
      "name": "scPayError",
      "method": "handlePayError",
      "target": undefined,
      "capture": false,
      "passive": false
    }, {
      "name": "scApplyCoupon",
      "method": "handleCouponApply",
      "target": undefined,
      "capture": false,
      "passive": false
    }]; }
}
