import type {
  Product,
  ProductImage,
  ProductPrice,
  ProductVariant,
} from '@commerce/types/product'
import type { ProductAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Product'
import type { RelationType } from '@spree/storefront-api-v2-sdk/types/interfaces/Relationships'
import { jsonApi } from '@spree/storefront-api-v2-sdk'
import { JsonApiDocument } from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
import { requireConfigValue } from '../../isomorphic-config'
import createGetAbsoluteImageUrl from '../create-get-absolute-image-url'
import expandOptions from '../expand-options'
import getMediaGallery from '../get-media-gallery'
import getProductPath from '../get-product-path'
import MissingPrimaryVariantError from '../../errors/MissingPrimaryVariantError'
import MissingOptionValueError from '../../errors/MissingOptionValueError'
import type {
  ExpandedProductOption,
  SpreeSdkResponse,
  VariantAttr,
} from '../../types'

const placeholderImage = requireConfigValue('productPlaceholderImageUrl') as
  | string
  | false

const imagesOptionFilter = requireConfigValue('imagesOptionFilter') as
  | string
  | false

const normalizeProduct = (
  spreeSuccessResponse: SpreeSdkResponse,
  spreeProduct: ProductAttr
): Product => {
  const spreePrimaryVariant =
    jsonApi.findSingleRelationshipDocument<VariantAttr>(
      spreeSuccessResponse,
      spreeProduct,
      'primary_variant'
    )

  if (spreePrimaryVariant === null) {
    throw new MissingPrimaryVariantError(
      `Couldn't find primary variant for product with id ${spreeProduct.id}.`
    )
  }

  const sku = spreePrimaryVariant.attributes.sku

  const price: ProductPrice = {
    value: parseFloat(spreeProduct.attributes.price),
    currencyCode: spreeProduct.attributes.currency,
  }

  const hasNonMasterVariants =
    (spreeProduct.relationships.variants.data as RelationType[]).length > 1

  const showOptions =
    (requireConfigValue('showSingleVariantOptions') as boolean) ||
    hasNonMasterVariants

  let options: ExpandedProductOption[] = []

  const spreeVariantRecords = jsonApi.findRelationshipDocuments(
    spreeSuccessResponse,
    spreeProduct,
    'variants'
  )

  // Use variants with option values if available. Fall back to
  // Spree primary_variant if no explicit variants are present.
  const spreeOptionsVariantsOrPrimary =
    spreeVariantRecords.length === 0
      ? [spreePrimaryVariant]
      : spreeVariantRecords

  const variants: ProductVariant[] = spreeOptionsVariantsOrPrimary.map(
    (spreeVariantRecord) => {
      let variantOptions: ExpandedProductOption[] = []

      if (showOptions) {
        const spreeOptionValues = jsonApi.findRelationshipDocuments(
          spreeSuccessResponse,
          spreeVariantRecord,
          'option_values'
        )

        // Only include options which are used by variants.

        spreeOptionValues.forEach((spreeOptionValue) => {
          variantOptions = expandOptions(
            spreeSuccessResponse,
            spreeOptionValue,
            variantOptions
          )

          options = expandOptions(
            spreeSuccessResponse,
            spreeOptionValue,
            options
          )
        })
      }

      return {
        id: spreeVariantRecord.id,
        options: variantOptions,
      }
    }
  )

  const spreePrimaryVariantImageRecords = jsonApi.findRelationshipDocuments(
    spreeSuccessResponse,
    spreePrimaryVariant,
    'images'
  )

  let spreeVariantImageRecords: JsonApiDocument[]

  if (imagesOptionFilter === false) {
    spreeVariantImageRecords = spreeVariantRecords.reduce<JsonApiDocument[]>(
      (accumulatedImageRecords, spreeVariantRecord) => {
        return [
          ...accumulatedImageRecords,
          ...jsonApi.findRelationshipDocuments(
            spreeSuccessResponse,
            spreeVariantRecord,
            'images'
          ),
        ]
      },
      []
    )
  } else {
    const spreeOptionTypes = jsonApi.findRelationshipDocuments(
      spreeSuccessResponse,
      spreeProduct,
      'option_types'
    )

    const imagesFilterOptionType = spreeOptionTypes.find(
      (spreeOptionType) =>
        spreeOptionType.attributes.name === imagesOptionFilter
    )

    if (!imagesFilterOptionType) {
      console.warn(
        `Couldn't find option type having name ${imagesOptionFilter} for product with id ${spreeProduct.id}.` +
          ' Showing no images for this product.'
      )

      spreeVariantImageRecords = []
    } else {
      const imagesOptionTypeFilterId = imagesFilterOptionType.id
      const includedOptionValuesImagesIds: string[] = []

      spreeVariantImageRecords = spreeVariantRecords.reduce<JsonApiDocument[]>(
        (accumulatedImageRecords, spreeVariantRecord) => {
          const spreeVariantOptionValuesIdentifiers: RelationType[] =
            spreeVariantRecord.relationships.option_values.data

          const spreeOptionValueOfFilterTypeIdentifier =
            spreeVariantOptionValuesIdentifiers.find(
              (spreeVariantOptionValuesIdentifier: RelationType) =>
                imagesFilterOptionType.relationships.option_values.data.some(
                  (filterOptionTypeValueIdentifier: RelationType) =>
                    filterOptionTypeValueIdentifier.id ===
                    spreeVariantOptionValuesIdentifier.id
                )
            )

          if (!spreeOptionValueOfFilterTypeIdentifier) {
            throw new MissingOptionValueError(
              `Couldn't find option value related to option type with id ${imagesOptionTypeFilterId}.`
            )
          }

          const optionValueImagesAlreadyIncluded =
            includedOptionValuesImagesIds.includes(
              spreeOptionValueOfFilterTypeIdentifier.id
            )

          if (optionValueImagesAlreadyIncluded) {
            return accumulatedImageRecords
          }

          includedOptionValuesImagesIds.push(
            spreeOptionValueOfFilterTypeIdentifier.id
          )

          return [
            ...accumulatedImageRecords,
            ...jsonApi.findRelationshipDocuments(
              spreeSuccessResponse,
              spreeVariantRecord,
              'images'
            ),
          ]
        },
        []
      )
    }
  }

  const spreeImageRecords = [
    ...spreePrimaryVariantImageRecords,
    ...spreeVariantImageRecords,
  ]

  const productImages = getMediaGallery(
    spreeImageRecords,
    createGetAbsoluteImageUrl(requireConfigValue('imageHost') as string)
  )

  const images: ProductImage[] =
    productImages.length === 0
      ? placeholderImage === false
        ? []
        : [{ url: placeholderImage }]
      : productImages

  const slug = spreeProduct.attributes.slug
  const path = getProductPath(spreeProduct)

  return {
    id: spreeProduct.id,
    name: spreeProduct.attributes.name,
    description: spreeProduct.attributes.description,
    images,
    variants,
    options,
    price,
    slug,
    path,
    sku,
  }
}

export default normalizeProduct
