console.log(`[qc] script loaded`)

{
  const userAgent = navigator.userAgent

  // polyfill event.path for Safari and Firefox
  if (!('path' in Event.prototype)) {
    Object.defineProperty(Event.prototype, 'path', {
      get: function () {
        var path = []
        var currentElem = this.target
        while (currentElem) {
          path.push(currentElem)
          currentElem = currentElem.parentElement
        }
        if (path.indexOf(window) === -1 && path.indexOf(document) === -1)
          path.push(document)
        if (path.indexOf(window) === -1) path.push(window)
        return path
      },
    })
  }

  // used to send logs in case of default chackout opening
  // CreateCheckoutMonitoringCaseRequestBody
  // requestBodyType = { storeId: string, reason: string, isExpected?: boolean, details?: string }
  async function sendMonitoringPostRequest(requestBody) {
    // this constructor is needed solely for PR environments
    // prod env should always have the same url
    // 'https://api.qcpg.cc/api/v2/checkouts/monitoring'

    const checkoutUrl = `https://checkout.qcpg.cc`
    const regex = /https:\/\/checkout\.(.*?)\.qcpg\.cc/
    const match = checkoutUrl.match(regex)
    let apiEnv = ''

    if (match && match[1]) {
      apiEnv = match[1] + '.'
    }

    const url = 'https://api.' + apiEnv + 'qcpg.cc/api/v2/checkouts/monitoring'

    return fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(requestBody),
    })
      .then((response) => {
        if (!response.ok) {
          throw new Error(`HTTP error! Status: ${response.status}`)
        }
        return response.json()
      })
      .catch((error) => {
        console.error('Monitoring request error:', error)
      })
  }

  const removeHashFromUrl = (url) => {
    const urlObj = new URL(url)
    urlObj.hash = ''
    return urlObj.toString()
  }

  // function to retrieve GA params from url
  const getGAUrlParams = () => {
    const searchParams = window.location.href.split('?')[1]

    const gclid = new URLSearchParams(searchParams).get('gclid')
    const utm_id = new URLSearchParams(searchParams).get('utm_id')
    const utm_campaign = new URLSearchParams(searchParams).get('utm_campaign')
    const utm_source = new URLSearchParams(searchParams).get('utm_source')
    const utm_medium = new URLSearchParams(searchParams).get('utm_medium')
    const utm_content = new URLSearchParams(searchParams).get('utm_content')
    const utm_term = new URLSearchParams(searchParams).get('utm_term')

    return {
      ...(gclid && { gclid }),
      ...(utm_id && { utm_id }),
      ...(utm_campaign && { utm_campaign }),
      ...(utm_source && { utm_source }),
      ...(utm_medium && { utm_medium }),
      ...(utm_content && { utm_content }),
      ...(utm_term && { utm_term }),
    }
  }

  const setGaParamsToCookie = (gaParams) => {
    // set cookie for each parameter of gaParams object
    // where ket is the name of the cookie and value is the value of the cookie
    for (const [key, value] of Object.entries(gaParams)) {
      document.cookie = `${key}=${value};path=/;max-age=31536000;`
    }
  }

  const getGaParamsFromCookie = () => {
    const gaParams = {}

    // get all cookies
    const cookies = document.cookie.split(';')

    // iterate through cookies
    for (let i = 0; i < cookies.length; i++) {
      const cookie = cookies[i].trim()

      // check if cookie name starts with 'utm_'
      if (cookie.startsWith('utm_') || cookie.startsWith('gclid')) {
        const cookieName = cookie.split('=')[0]
        const cookieValue = cookie.split('=')[1]

        // add cookie to gaParams object
        gaParams[cookieName] = cookieValue
      }
    }

    return gaParams
  }

  // function to convert object to string which is passed to iframe source
  const paramsObjectToString = (object) => {
    let string = ''

    if (!object) return string

    for (const [key, value] of Object.entries(object)) {
      string += `&${key}=${value}`
    }

    return string
  }

  const gaUrlParams = getGAUrlParams()
  setGaParamsToCookie(gaUrlParams)

  //func to get GA client_id from cookie directly if gtag function failed
  function getGaClientIdFromCookies() {
    const gaClientIdCookieValue = document.cookie
      .split('; ')
      .find((row) => row.startsWith('_ga'))
      ?.split('=')[1]

    const regex = /GA1\.1\.(\d+\.\d+)/
    const match = gaClientIdCookieValue?.match(regex)

    if (match && match[1]) {
      console.debug('[qc] extracted client_id directly from cookie:', match?.[1])
      return match[1] // client_id
    } else {
      console.debug('[qc] failed to get ga client_id from cookie')
      return ''
    }
  }

  //func to get GA session_id from cookie directly if gtag function failed
  function getGaSessionIdFromCookies() {
    const id = ``.slice(2)
    const pattern = new RegExp('_ga_' + id + '=GS\\d\\.\\d\\.(.+?)(?:;|$)')
    const match = document.cookie.match(pattern)
    const parts = match?.[1].split('.')

    if (!parts) {
      console.debug('[qc] failed to get ga session_id from cookie')
      return ''
    }

    console.debug('[qc] extracted ga session_id directly from cookie:', parts?.[0])
    return parts?.[0] // session id
  }

  let client_id = ''
  let session_id = ''
  //attempt to extract gaClientId with gtag function
  setTimeout(() => {
    console.debug('[qc] attempting to extract client_id and session_id with gtag')

    gtag('get', ``, 'client_id', (id) => {
      client_id = id
      console.debug('[qc] successfuly extracted client_id with gtag:', gaClientId)
    })

    gtag('get', ``, 'session_id', (id) => {
      session_id = id
      console.debug('[qc] successfuly extracted session_id with gtag:', session_id)
    })
  }, 1500)

  // helper functions

  /**
   * redirectToNativeCheckout is used to redirect to the native checkout
   * @param checkoutUrl string | undefined checkout url
   */
  const redirectToNativeCheckout = (checkoutUrl) => {
    // Default fallback to checkout page
    if (!checkoutUrl) {
      window.location.href = '/checkout'
      return
    }
    window.location.href = checkoutUrl
  }


  /**
   * getCart is used to get user cart contents
   * @return {Promise<Array<{ variantId: number, quantity: number, productId: number }>> | undefined}
   */
  const getCart = async () => {
    const resp = await fetch('/api/storefront/carts')
    const cart = await resp.json()

    try {
      if (!cart) {
        throw new Error('No cart found')
      }

      const physicalItems = cart[0].lineItems.physicalItems
      const digitalItems = cart[0].lineItems.digitalItems
      const giftCertificates = cart[0].lineItems.giftCertificates
      const customItems = cart[0].lineItems.customItems

      // check for digital products
      if (digitalItems.length !== 0 && physicalItems.length === 0) {
        sendMonitoringPostRequest({
          storeId: `cddee0f3-a33c-4c93-90cb-2d2db32779eb`,

          reason: 'cart contains only digital products',
          isExpected: true,
          details: JSON.stringify({
            products: digitalItems,
            userAgent,
          }),
        })

        throw new Error('cart contains only digital products')
      }

      // check for gift certificates
      if (giftCertificates.length !== 0) {
        sendMonitoringPostRequest({
          storeId: `cddee0f3-a33c-4c93-90cb-2d2db32779eb`,
          reason: 'order contains gift certificates',
          isExpected: true,
          details: JSON.stringify({
            products: [...physicalItems, ...digitalItems],
            userAgent,
          }),
        })

        throw new Error('gift cerificates detected')
      }

      // check for custom items
      if (customItems.length !== 0) {
        sendMonitoringPostRequest({
          storeId: `cddee0f3-a33c-4c93-90cb-2d2db32779eb`,
          reason: 'order contains custom items',
          isExpected: true,
          details: JSON.stringify({
            products: customItems,
            userAgent,
          }),
        })

        throw new Error('custom items detected')
      }

      // return line items
      return {vendorCartId: cart[0].id, lineItems: physicalItems.map((t) => ({
        variantId: t.variantId.toString(),
        productId: t.productId.toString(),
        quantity: t.quantity,
      }))}
    }
    catch (error) {
      console.error('[qc] getting cart error:', error.message)

      sendMonitoringPostRequest({
        storeId: `cddee0f3-a33c-4c93-90cb-2d2db32779eb`,
        reason: `[script]: /api/storefront/carts fetch request failed with error: ${error.message}`,
        details: JSON.stringify({ userAgent }),
      }).finally(() => redirectToNativeCheckout())


    }
  }

  /**
   * openCheckout is used to open QuickCheckout page popup
   * @param products Array<{ variantId: number, productId: number, quantity: number}> list of products
   * @param vendorCartId string vendor cart id
   * @param isBuyNow boolean whether this is a buy now flow
   */
  const openCheckout = ({products, vendorCartId, isBuyNow = false}) => {
    // create iframe
    let iframe = document.createElement('iframe')

    // remove search params from location.href and location.search to prevent parsing issues
    const hrefRaw = window.location.href.split('?')[0]
    const href = removeHashFromUrl(hrefRaw)

    // check if gaClientId and gaSessionId are already extracted with gtag
    // if not, try to extract them from cookies
    if (client_id === '') {
      client_id = getGaClientIdFromCookies() // streing value
    }

    if (session_id === '') {
      session_id = getGaSessionIdFromCookies() // streing value
    }

    const gaParams = getGaParamsFromCookie()

    // it any GA params found, pass them to iframe later
    const gaParamsString = gaParams !== '{}' ? paramsObjectToString(gaParams) : ''
    const gaClientIdString = client_id !== '' ? `&client_id=${client_id}` : ''
    const gaSessionIdString = session_id !== '' ? `&session_id=${session_id}` : ''

    const src = `https://checkout.qcpg.cc/delivery?storeId=cddee0f3-a33c-4c93-90cb-2d2db32779eb&href=${href}&products=${JSON.stringify(
      products,
    )}${gaParamsString}${gaClientIdString}${gaSessionIdString}`
    iframe.src = !vendorCartId ? src : src + '&vendorCartId=' + vendorCartId

    // check Google Analytics
    if (window.ga) {
      // get track id
      ga(function (tracker) {
        iframe.src += '&clientId=' + tracker.get('clientId')
      })

      // get Google analytics "_ga" param for cross domain tracking
      if (window.gaplugins) {
        const tracker = window.ga.getAll()[0]
        const linker = new window.gaplugins.Linker(tracker)
        iframe.src = linker.decorate(iframe.src)
      }
    }

    // set iframe styles
    iframe.style.cssText =
      'width: 100%; height:100%; position:fixed; top:0; left:0; z-index:10000000000; border:none;'
    document.head.style.display = 'block'
    document.body.style.overflow = 'hidden'

    // listen to messages from iframe
    window.addEventListener('message', (message) => {
      console.debug('received message', { message })
      const { type, reason, products, redirectUrl, errorMessage } =
        typeof message.data === 'string' ? JSON.parse(message.data) : message.data || {}

      if (type === 'close') {
        console.log('closing checkout')
        document.body.removeChild(iframe)
        document.body.style.overflow = 'auto'
      } else if (type === 'error') {
        console.error('closing checkout, because error occurred')
        document.body.removeChild(iframe)
        document.body.style.overflow = 'auto'
        console.debug('[qc] redirecting to the native checkout because of error')

        // do not send monitoring request if error is caused by create checkout request
        if (reason !== '[app] Create checkout request failed') {
          sendMonitoringPostRequest({
            storeId: `cddee0f3-a33c-4c93-90cb-2d2db32779eb`,
            reason: reason + ` => Details: ${errorMessage}`,
            isExpected: false,
            details: JSON.stringify({ products, userAgent }),
          }).finally(() => {
            if (isBuyNow && redirectUrl) {
              redirectToNativeCheckout(redirectUrl)
            } else {
              redirectToNativeCheckout()
            }
          })
        }

        if (isBuyNow && redirectUrl) {
          redirectToNativeCheckout(redirectUrl)
        } else {
          redirectToNativeCheckout()
        }
      } else if (type === 'open_default') {
        console.log('open_default', { redirectUrl })
        document.body.removeChild(iframe)
        document.body.style.overflow = 'auto'
        console.debug('[qc] redirecting to the native checkout')

        sendMonitoringPostRequest({
          storeId: `cddee0f3-a33c-4c93-90cb-2d2db32779eb`,
          reason,
          isExpected: true,
          details: JSON.stringify({ products, userAgent }),
        }).finally(() => {
          if (isBuyNow && redirectUrl) {
            redirectToNativeCheckout(redirectUrl)
          } else {
            redirectToNativeCheckout()
          }
        })
      } else if (type === 'purchase') {
        fetch(`/api/storefront/carts/${vendorCartId}`, {
          method: 'delete',
        })
      }
    })

    // add iframe to document
    document.body.appendChild(iframe)
  }


    /**
   * getProductByIdAndOptions is used to get product by id and options
   * @param productId (string | number)
   * @param optionValueIds Array<{ optionEntityId: string, valueEntityId: string }>
   * @return {Promise<{ data: { site: { product: { variants: { edges: { node: { entityId: string }[] } } } } } }>}
   */
  async function fetchProductByIdAndOptions(productId, optionValueIds) {
    // docs https://developer.bigcommerce.com/graphql-storefront/playground
    const query = `
    query productById {
      site {
        product(entityId: ${productId}, useDefaultOptionSelections: true) {
          type
          variants(optionValueIds: ${JSON.stringify(optionValueIds).replace(/["']/g, "")}) {
            edges {
              node {
                entityId
              }
            }
          }
        }
      }
    }`;

    const response = await fetch('/graphql', {
      method: 'POST',
      credentials: 'same-origin',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJjaWQiOlsxXSwiY29ycyI6WyJodHRwczovL2hleW5vd3NhZmV0eS5jb20iXSwiZWF0IjoxODk5NzM4NjI1LCJpYXQiOjE3NDE5NzIyMjUsImlzcyI6IkJDIiwic2lkIjoxMDAzMjQwODEyLCJzdWIiOiJhbXZlZ3dkdXBiOWY5cm1kN2hlYm0zdjVrZHBuNmY4Iiwic3ViX3R5cGUiOjIsInRva2VuX3R5cGUiOjF9.EQ45uSQzxSW88PB5tZrG_yPRgk5dNGqtNZS4qj3ROFXC9q589TpUPIufdpkMahG9ztPeUgrsbu-eqHGpUhWSQA`,
      },
      body: JSON.stringify({ query }),
    });

    if (!response.ok) {
      throw new Error(`GraphQL error: ${response.status} - ${response.statusText}`);
    }

    return response.json(); // returns the full JSON data
  }

  const getProductTypeFromGraphql = (res) => res.data?.site?.product?.type

  /**
   * parseProductForm is used to parse the product form
   * @param form FormData
   * @return {{ productId: string, optionValueIds: Array<{ optionEntityId: string, valueEntityId: string }>, quantity: number }>}
   */
  const parseProductFormParams = (entries) => {
    let productId;
    let optionValueIds = [];
    let quantity = 1;

    for (const [key, value] of entries) {
      if (key === 'product_id') {
        productId = value
      } else if (key.startsWith('attribute[')) {
        optionValueIds.push({
          optionEntityId: key.split('[')[1].split(']')[0],
          valueEntityId: value,
        })
      } else if (key === 'qty[]') {
        // value as integer
        quantity = parseInt(value, 10)
      }
    }

    return { productId, optionValueIds, quantity }
  }

  function parseBuyNowLinkParams(queryString) {
    // strip leading '?'
    const query = queryString[0] === '?' ? queryString.slice(1) : queryString;
    const params = new URLSearchParams(query);
    
    // We'll collect data similarly to parseProductForm
    let productId;
    let quantity = 1;
    const optionValueIds = [];
  
    for (const [key, value] of params.entries()) {
      if (key === 'product_id') {
        productId = value;
      } else if (key.startsWith('attribute[')) {
        // e.g. attribute[108] => 108
        const optionEntityId = key.match(/^attribute\[(\d+)\]$/)?.[1];
        if (optionEntityId) {
          optionValueIds.push({
            optionEntityId,
            valueEntityId: value,
          });
        }
      } else if (key === 'qty' || key === 'qty[]') {
        quantity = value;
      }
    }
  
    return { productId, optionValueIds, quantity };
  }

  function getCartForm() {
    return document.querySelector('form[action*="cart.php"]');
  }

  function getCartLink() {
    return document.querySelector('a[href*="cart.php"]');
  }

  /**
   * redirectToBuyNow is used to redirect to the native buy now flow for a specific product
   * @param productId string | number product id
   * @param quantity number quantity of the product
   * @param optionValueIds Array<{ optionEntityId: string, valueEntityId: string }> optional product options
   */
  const redirectToBuyNow = (productId, quantity = 1, optionValueIds = []) => {
    console.debug(`[qc] redirecting to native buy now for product ${productId}`)
    
    const url = new URL('/cart.php', window.location.origin)
    url.searchParams.append('action', 'buy')
    url.searchParams.append('product_id', productId)
    
    // Add option values if available
    if (optionValueIds && optionValueIds.length > 0) {
      optionValueIds.forEach(option => {
        url.searchParams.append(`attribute[${option.optionEntityId}]`, option.valueEntityId)
      })
    }
    
    url.searchParams.append('qty', quantity)
    
    window.location.href = url.toString()
  }

  /**
   * Checks if the product form is valid using native browser validation
   * @param {HTMLFormElement} form - The product form element
   * @return {boolean} - Whether the form is valid
   */
  const isProductFormValid = (form) => {
    if (!form || typeof form.checkValidity !== 'function') {
      return true;
    }
    
    return form.checkValidity();
  }

  const handleBuyNowClick = async (event) => {
    event.stopImmediatePropagation()
    event.preventDefault()

    let parsedParams;
      
    try {
      // form for selecting product options
      const cartForm = getCartForm()
      
      // Check form validity if it's a form-based buy now
      if (cartForm) {
        if (!isProductFormValid(cartForm)) {
          console.debug('[qc] product form validation failed, letting native handler take over')
          const newEvent = new MouseEvent(event.type, {
            bubbles: true,
            cancelable: true,
            view: window,
            detail: event.detail,
          });
          
          // newEvent._qcProcessed = true;
          event.target.dispatchEvent(newEvent);
          return;
        }
        
        const entries = Array.from(
          new FormData(cartForm).entries(),
        )
        parsedParams = parseProductFormParams(entries) 
      }
      else {
        const cartLink = getCartLink()
        if (cartLink){
          parsedParams = parseBuyNowLinkParams(cartLink.href)
        }
        else {
          throw new Error('No cart form or link found')
        }
      }

      // find the storefront product by id and selected options (we need it for a variant id)
      const res = await fetchProductByIdAndOptions(parsedParams.productId, parsedParams.optionValueIds)
      
      const productType = getProductTypeFromGraphql(res)
      const variantEntityId = getVariantEntityIdFromGraphql(res)
      
      if (!variantEntityId || productType !== 'Physical') {
        console.error('[qc] product is not supported for buy now flow')
        redirectToBuyNow(parsedParams.productId, parsedParams.quantity, parsedParams.optionValueIds)
        return
      }

      openCheckout({
        products: [{variantId: variantEntityId.toString(), quantity: parsedParams.quantity, properties: {
          productId: parsedParams.productId.toString(),
        }}],
        isBuyNow: true
      })
    }
    catch (error) {
      console.error('[qc] error in buy now flow:', error.message)
      
      // If we have parsed params, use them for direct checkout
      if (parsedParams?.productId) {
        redirectToBuyNow(parsedParams.productId, parsedParams.quantity, parsedParams.optionValueIds)
      } else {
        // Fallback to default checkout if we couldn't parse product info
        redirectToNativeCheckout()
      }
    }
  }

  const getVariantEntityIdFromGraphql = (res) => {
    const variants = res.data?.site?.product?.variants?.edges || []
    if (variants.length === 0) {
      return null
    }
  
    const variantEntityId = variants[0]?.node?.entityId
    if (!variantEntityId) {
      return null
    }
  
    return variantEntityId
  }

  const handleCheckoutClick = async (event) => {
    event.stopImmediatePropagation()
    event.preventDefault()

    console.debug(`[qc] attempt to open quickcheckout...`)
    try {
      const {vendorCartId, lineItems} = await getCart()
      if (!lineItems || !vendorCartId) {
        throw new Error(lineItems ? 'No vendorCartId' : 'No lineItems')
      }
      openCheckout({
        products: lineItems, 
        vendorCartId,
        isBuyNow: false
      })
    }
    catch (error) {
      console.error(`[qc] getting cart error:`, error.message)
      redirectToNativeCheckout()
    }
  }

  const handler = async (event) => {
    // prevent untrusted clicks from running the handler
    if (event.isTrusted === false) {
      return
    }

    const buyNowSelectors = `#form-action-buyNow,a[href*="cart.php"][href*="action=buy"]`;
    const checkoutSelectors = `a[href*="/checkout"]`;

    try {
      if (buyNowSelectors && event.target.matches(buyNowSelectors)) {
        await handleBuyNowClick(event);
      }
    }
    catch (error) {
      console.error(`[qc] error handling buy now click:`, error.message)
    }
    
    try {
      if (checkoutSelectors && event.target.matches(checkoutSelectors)) {
        await handleCheckoutClick(event);
      }
    }
    catch (error) {
      console.error(`[qc] error handling checkout click:`, error.message)
    }
  }

  // track every click
  window.addEventListener('touchstart', handler, true)
  window.addEventListener('click', handler, true)
}
