import { assert, addUrlParams, createBaseUrl, facetsToParams, ResponseError, type Type, type UrlParams } from './util/mod.ts';
import type { FullOptions } from './config.ts';
import type { FacetParams } from './mod.ts';
import type * as m from './models/mod.ts';


export class Query {
  constructor(private readonly __config: FullOptions) {}

  /**
   * Retrieve recommendations for a product that has just been added to the cart.
   *
   * @param params query parameter options to submit
   * @param body configuration options to submit
   *
   * @example
   * ```ts
   * const res = await api.query.addToCartPopup({ variantKey: 'variant-key-123' }, {
   *   recommendationLists: [{
   *     id: 'addons',
   *     algorithm: 'ADD_TO_CART_RECS'
   *   }]
   * });
   * ```
   * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/queries/add-to-cart-popup/
   */
  addToCartPopup(params: m.AddToCartPopupParams, recommendationLists: m.AddToCartPopupBody) {
    return this.__query<m.AddToCartPopup>('add-to-cart-popup', {
      params: params as Type<m.AddToCartPopupParams>,
      body: { recommendationLists }
    });
  }

  /**
   * Autocomplete based on provided query parameter, for search suggestions,
   * product suggestions, and more.
   *
   * @param params query parameter options to submit
   * @param body configuration options to submit
   *
   * @example
   * ```ts
   * const res = await api.query.autocomplete({ q: 'shirt' });
   * const sale = await api.query.autocomplete({ q: 'dress' }, {
   *   productFilter: { discount: 50 }
   * p});
   * ```
   * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/queries/autocomplete/
   */
  autocomplete(params: m.AutocompleteParams, body?: m.AutocompleteBody) {
    return this.__query<m.Autocomplete>('autocomplete', { params: params as Type<m.AutocompleteParams>, body });
  }

  /**
   * Retrieve the complete navigation tree, suitable for a top/mobile navigation of the site.
   *
   * @param params query parameter options to submit
   *
   * @example
   * ```ts
   * const tree = await api.query.navigationTree();
   * ```
   * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/queries/navigation-tree/
   */
  navigationTree(params?: m.NavigationTreeParams) {
    return this.__query<m.NavigationTree>('navigation-tree', { params: params as Type<m.NavigationTreeParams> });
  }

  /**
   * Returns a product listing with facets based on provided query, optionally
   * with related navigation included.
   *
   * @param params query parameter options to submit
   * @param body configuration options to submit
   *
   * @example
   * ```ts
   * const res = await api.query.searchPage({ q: 'shirt' });
   * const sale = await api.query.searchPage({ q: 'dress' }, {
   *   primaryList: {
   *     productFilter: { discount: 50 }
   *   },
   *   navigation: { include: true }
   * });
   * ```
   * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/queries/search-page/
   */
  searchPage(params: m.SearchPageParams, body?: m.SearchPageBody) {
    const { facets, ...p } = params;
    return this.__query<m.SearchPage>('search-page', { facets, params: { ...p }, body });
  }

  /**
   * Retrieves product information and related info suitable to show on a Product page.
   * Can be configured to show various recommendation lists based on body configuration settings.
   *
   * @param params query parameter options to submit
   * @param body configuration options to submit
   *
   * @example
   * ```ts
   * const res = await api.query.productPage({ productKey: 'p123' }, {
   *   recommendationLists: [{
   *     id: 'alts',
   *     algorithm: 'ALTERNATIVES'
   *   }]
   * });
   * ```
   * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/queries/product-page/
   */
  productPage(params: m.ProductPageParams, body?: m.ProductPageBody) {
    return this.__query<m.ProductPage>('product-page', { params: params as Type<m.ProductPageParams>, body });
  }

  /**
   * Retrieve products - and possibly recommendation lists - suitable for display
   * on a cart page.
   *
   * @param params query parameter options to submit
   * @param body configuration options to submit
   *
   * @example
   * ```ts
   * const res = await api.query.cartPage({ cart: ['p123', 'p456'] }, {
   *   recommendationLists: [{
   *     id: 'addons',
   *     algorithm: 'CART'
   *   }]
   * });
   * ```
   * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/queries/cart-page/
   */
  cartPage(params: m.CartPageParams, body?: m.CartPageBody) {
    return this.__query<m.CartPage>('cart-page', { params: params as Type<m.CartPageParams>, body });
  }

  /**
   * Request for generic landing pages, or category pages. Can return product listing with facets,
   * recommendation lists, or both. Suitable for the start/home page, intermediate category pages,
   * brand pages, and more.
   *
   * @param params query parameter options to submit
   * @param body configuration options to submit
   *
   * @example
   * ```ts
   * const res = await api.query.landingPage();
   * const sale = await api.query.landingPage({ limit: 30, skip: 0 }, {
   *   primaryList: {
   *     productFilter: { discount: 50 }
   *   },
   *   recommendationLists: [{
   *     id: 'personal',
   *     algorithm: 'PERSONAL'
   *   }]
   * });
   * ```
   * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/queries/landing-page/
   */
  landingPage(params: m.LandingPageParams = {}, body?: m.LandingPageBody) {
    const { facets, ...p } = params;
    return this.__query<m.LandingPage>('landing-page', { facets, params: { ...p }, body });
  }

  /**
   * Request for retrieving a list of sponsored products for a Page.
   *
   * @beta ⚠️ This request is currently limited to a private beta and will fail otherwise.
   *
   * @param params query parameter options to submit
   *
   * @example
   * ```ts
   * const res = await api.query.sponsoredPage({
   *   pageReference: '/women/tops'
   * });
   * ```
   * @see https://docs.apptus.com/elevate/4/integration/api/specifications/storefront/v3/queries/sponsored-page/
   */
  sponsoredPage(params: m.SponsoredPageParams) {
    return this.__query<m.SponsoredPage>('sponsored-page', { params: params as Type<m.SponsoredPageParams> });
  }

  /**
   * Mirrors the Product Page, but for Content instead of Products. Retrieves content
   * information for the provided `contentKeys` (required).
   *
   * @param params query parameter options to submit
   *
   * @example
   * ```ts
   * const res = await api.query.contentInformation({
   *   contentKeys: ['ck_123456', 'ck_234567']
   * });
   * ```
   * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/queries/content-information/
   */
  contentInformation(params: m.ContentInformationParams) {
    return this.__query<m.ContentInformation>('content-information', { params: params as Type<m.ContentInformationParams> });
  }

  /**
   * Mirrors the Search Page request, but for Content instead of Products. Returns content
   * results matching the provided query. Suiteable for searching in e.g. FAQ or customer service,
   * where no product results are required.
   *
   * @param params query parameter options to submit
   * @param body configuration options to submit
   *
   * @example
   * ```ts
   * const res = await this.contentSearchPage({ q: 'returns' });
   * ```
   * @example
   * ```ts
   * const res = await this.contentSearchPage({ q: 'shipping', skip: 20 }, {
   *   primaryList: {
   *     contentFilter: {
   *       type: 'faq'
   *     }
   *   }
   * });
   * ```
   * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/queries/content-search-page/
   */
  contentSearchPage(params: m.ContentSearchPageParams, body?: m.ContentSearchPageBody) {
    return this.__query<m.ContentSearchPage>('content-search-page', { params: params as Type<m.ContentSearchPageParams>, body });
  }

  private async __query<T>(endpoint: string, options: { params?: UrlParams, facets?: FacetParams, body?: object }) {
    const { params = {}, facets, body } = options;
    assert(typeof params === 'object', 'If provided, params must be an object');

    const url = await createBaseUrl(`queries/${endpoint}`, this.__config);
    const { locale, touchpoint } = this.__config;
    addUrlParams(url, { locale, touchpoint, ...params, ...facetsToParams(facets) });

    const init: RequestInit = !body ?
      { method: 'GET' } :
      { method: 'POST', headers: { 'Content-Type': 'text/plain' }, body: JSON.stringify(body) };
    const res = await fetch(url, init);
    const content = await res.json();

    // TODO: res.ok
    if (res.status >= 200 && res.status < 400) {
      return content as T;
    }

    throw new ResponseError(content);
  }
}
