<script setup lang="ts">
import { useQuery } from '@vue/apollo-composable';
import {
  CatalogProductTypeSettingsDocument,
  ClientProductsDocument,
  type CatalogProductTypeSettingsQuery,
  type ClientProductsQuery,
} from '~/composables/graphql/generated';
import { useClientCart } from '~/composables/useClientCart';

type ProductNode = ClientProductsQuery['clientProducts'][number];
type CatalogProductTypeSettingNode = CatalogProductTypeSettingsQuery['catalogProductTypeSettings'][number];
type ParamFieldKey = 'widthMm' | 'lengthM' | 'thicknessMicron' | 'sleeveBrand' | 'quantityPerBox' | 'colorTag' | 'labelTag';
type ParamValue = number | string;

type ParsedProduct = ProductNode & {
  productTypeLabel: string;
  quantityPerBoxOptions: string[];
  normalizedTags: string[];
  colorTags: string[];
  labelTags: string[];
};

type ProductGroup = {
  key: string;
  typeLabel: string;
  products: ParsedProduct[];
};

type GroupState = {
  widthMm: number | null;
  lengthM: number | null;
  thicknessMicron: number | null;
  sleeveBrand: string | null;
  quantityPerBox: string | null;
  colorTag: string | null;
  labelTag: string | null;
  isExpanded: boolean;
};

const COLOR_TAGS = ['прозрачный', 'коричневый', 'белый', 'черный', 'желтый', 'зеленый', 'красный', 'синий', 'оранжевый', 'красно-белый'];
const LABEL_TAGS = ['хрупкое', 'подарок', 'акция'];
const PARAM_KEYS: ParamFieldKey[] = ['widthMm', 'lengthM', 'thicknessMicron', 'sleeveBrand', 'quantityPerBox', 'colorTag', 'labelTag'];
const DEFAULT_CATALOG_PRODUCT_TYPE_SETTING: CatalogProductTypeSettingNode = {
  productType: '',
  showQuantityPerBox: false,
  allowCustomLength: false,
  customLengthMinM: null,
  customLengthMaxM: null,
  customLengthStepM: null,
  allowCustomSleeveBrand: false,
  allowCustomLabel: false,
};
const parameterFields: Array<{ key: ParamFieldKey; label: string }> = [
  { key: 'widthMm', label: 'Ширина' },
  { key: 'lengthM', label: 'Длина' },
  { key: 'thicknessMicron', label: 'Толщина' },
  { key: 'sleeveBrand', label: 'Втулка' },
  { key: 'quantityPerBox', label: 'Короб' },
  { key: 'colorTag', label: 'Цвет' },
  { key: 'labelTag', label: 'Надпись' },
];

const coverPresets = [
  ['#d9f5e6', '#9ce8c1', '#6fd09d'],
  ['#eaf9ef', '#b3e8cb', '#76c89f'],
  ['#e8f5ec', '#b2e0c6', '#7dd0a9'],
];

const productsQuery = useQuery(ClientProductsDocument);
const catalogSettingsQuery = useQuery(CatalogProductTypeSettingsDocument);
const search = ref('');
const groupStates = reactive<Record<string, GroupState>>({});
const { addProduct, getQuantity, incrementQuantity, decrementQuantity } = useClientCart();

const loading = computed(() => productsQuery.loading.value || catalogSettingsQuery.loading.value);
const error = computed(() => productsQuery.error.value || catalogSettingsQuery.error.value);

const catalogSettingsByType = computed<Record<string, CatalogProductTypeSettingNode>>(() => (
  Object.fromEntries(
    (catalogSettingsQuery.result.value?.catalogProductTypeSettings ?? [])
      .map((setting) => [setting.productType, setting]),
  )
));

function normalizeText(value: string | null | undefined) {
  return String(value ?? '').replaceAll(/\s+/g, ' ').trim();
}

function splitBoxValues(value: string | null | undefined) {
  return normalizeText(value)
    .split('/')
    .map((item) => item.trim())
    .filter(Boolean);
}

function createProductCover(name: string, sku: string) {
  const seed = `${name}${sku}`.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
  const [start, middle, finish] = coverPresets[seed % coverPresets.length];
  const firstLetter = name.trim().charAt(0).toUpperCase() || 'P';

  const svg = `
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 220">
      <defs>
        <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
          <stop offset="0%" stop-color="${start}" />
          <stop offset="55%" stop-color="${middle}" />
          <stop offset="100%" stop-color="${finish}" />
        </linearGradient>
      </defs>
      <rect width="320" height="220" fill="url(#g)" rx="22" />
      <g opacity="0.15">
        <circle cx="266" cy="45" r="55" fill="#0f7a49" />
        <circle cx="42" cy="198" r="55" fill="#0f7a49" />
      </g>
      <text x="50%" y="56%" text-anchor="middle" fill="#11412c" font-family="Manrope, sans-serif" font-size="84" font-weight="700">${firstLetter}</text>
    </svg>
  `.trim();

  return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
}

function hydrateProduct(product: ProductNode): ParsedProduct {
  const normalizedTags = product.tags.map((tag) => normalizeText(tag)).filter(Boolean).sort((a, b) => a.localeCompare(b, 'ru'));

  return {
    ...product,
    productTypeLabel: normalizeText(product.productType) || 'Без типа',
    quantityPerBoxOptions: splitBoxValues(product.quantityPerBox),
    normalizedTags,
    colorTags: normalizedTags.filter((tag) => COLOR_TAGS.includes(tag)),
    labelTags: normalizedTags.filter((tag) => LABEL_TAGS.includes(tag)),
  };
}

function productSortValue(value: number | null | undefined) {
  return value ?? Number.MAX_SAFE_INTEGER;
}

function compareProducts(a: ParsedProduct, b: ParsedProduct) {
  const byWidth = productSortValue(a.widthMm) - productSortValue(b.widthMm);
  if (byWidth !== 0) {
    return byWidth;
  }

  const byLength = productSortValue(a.lengthM) - productSortValue(b.lengthM);
  if (byLength !== 0) {
    return byLength;
  }

  const byThickness = productSortValue(a.thicknessMicron) - productSortValue(b.thicknessMicron);
  if (byThickness !== 0) {
    return byThickness;
  }

  const bySleeve = normalizeText(a.sleeveBrand).localeCompare(normalizeText(b.sleeveBrand), 'ru');
  if (bySleeve !== 0) {
    return bySleeve;
  }

  return a.sku.localeCompare(b.sku, 'ru');
}

const parsedProducts = computed<ParsedProduct[]>(() => {
  const list = productsQuery.result.value?.clientProducts ?? [];
  const query = search.value.trim().toLowerCase();

  return list
    .map(hydrateProduct)
    .filter((product) => {
      if (!query) {
        return true;
      }

      return [
        product.name,
        product.sku,
        product.productTypeLabel,
        String(product.widthMm ?? ''),
        String(product.lengthM ?? ''),
        String(product.thicknessMicron ?? ''),
        normalizeText(product.sleeveBrand),
        normalizeText(product.quantityPerBox),
        ...product.normalizedTags,
      ].some((part) => part.toLowerCase().includes(query));
    })
    .sort(compareProducts);
});

const productGroups = computed<ProductGroup[]>(() => {
  const map = new Map<string, ParsedProduct[]>();

  for (const product of parsedProducts.value) {
    const existing = map.get(product.productTypeLabel);
    if (existing) {
      existing.push(product);
    } else {
      map.set(product.productTypeLabel, [product]);
    }
  }

  return [...map.entries()]
    .sort((a, b) => a[0].localeCompare(b[0], 'ru'))
    .map(([typeLabel, products]) => ({
      key: typeLabel
        .toLowerCase()
        .replaceAll(/[^a-z0-9а-яё]+/gi, '-')
        .replaceAll(/-+/g, '-')
        .replaceAll(/^-|-$/g, ''),
      typeLabel,
      products: [...products].sort(compareProducts),
    }));
});

function sortParamValues(values: ParamValue[]) {
  return [...values].sort((a, b) => {
    if (typeof a === 'number' && typeof b === 'number') {
      return a - b;
    }
    return String(a).localeCompare(String(b), 'ru');
  });
}

function getAllFieldOptions(group: ProductGroup, field: ParamFieldKey) {
  const values = new Set<ParamValue>();

  for (const product of group.products) {
    if (field === 'quantityPerBox') {
      for (const option of product.quantityPerBoxOptions) {
        values.add(option);
      }
      continue;
    }

    if (field === 'colorTag') {
      for (const tag of product.colorTags) {
        values.add(tag);
      }
      continue;
    }

    if (field === 'labelTag') {
      for (const tag of product.labelTags) {
        values.add(tag);
      }
      continue;
    }

    const value = product[field];
    if (value !== null && value !== undefined) {
      values.add(value);
    }
  }

  return sortParamValues([...values]);
}

function groupCatalogSetting(group: ProductGroup) {
  return catalogSettingsByType.value[group.typeLabel] ?? {
    ...DEFAULT_CATALOG_PRODUCT_TYPE_SETTING,
    productType: group.typeLabel,
  };
}

function visibleFields(group: ProductGroup) {
  return parameterFields.filter((field) => {
    if (field.key === 'quantityPerBox') {
      return false;
    }

    return getAllFieldOptions(group, field.key).length > 1;
  });
}

function requiredKeys(group: ProductGroup) {
  return visibleFields(group).map((field) => field.key);
}

function createGroupState(group: ProductGroup): GroupState {
  const firstProduct = group.products[0];

  return {
    widthMm: firstProduct?.widthMm ?? null,
    lengthM: firstProduct?.lengthM ?? null,
    thicknessMicron: firstProduct?.thicknessMicron ?? null,
    sleeveBrand: firstProduct?.sleeveBrand ?? null,
    quantityPerBox: firstProduct?.quantityPerBoxOptions[0] ?? null,
    colorTag: firstProduct?.colorTags[0] ?? null,
    labelTag: firstProduct?.labelTags[0] ?? null,
    isExpanded: false,
  };
}

watch(
  productGroups,
  (groups) => {
    const keys = new Set(groups.map((group) => group.key));

    for (const key of Object.keys(groupStates)) {
      if (!keys.has(key)) {
        Reflect.deleteProperty(groupStates, key);
      }
    }

    for (const group of groups) {
      groupStates[group.key] ??= createGroupState(group);
    }
  },
  { immediate: true },
);

function getGroupState(group: ProductGroup) {
  groupStates[group.key] ??= createGroupState(group);
  return groupStates[group.key];
}

function matchesProductState(product: ParsedProduct, state: GroupState, keys: ParamFieldKey[]) {
  return keys.every((key) => {
    if (state[key] === null) {
      return true;
    }

    if (key === 'quantityPerBox') {
      return product.quantityPerBoxOptions.includes(String(state[key]));
    }

    if (key === 'colorTag') {
      return product.colorTags.includes(String(state[key]));
    }

    if (key === 'labelTag') {
      return product.labelTags.includes(String(state[key]));
    }

    return product[key] === state[key];
  });
}

function selectedProduct(group: ProductGroup) {
  const keys = requiredKeys(group);
  const state = getGroupState(group);

  if (keys.length === 0) {
    return group.products[0] ?? null;
  }

  if (keys.some((key) => state[key] === null)) {
    return null;
  }

  const matches = group.products.filter((product) => matchesProductState(product, state, keys));
  return matches[0] ?? null;
}

function formatOptionLabel(field: ParamFieldKey, value: ParamValue) {
  if (field === 'widthMm') {
    return `${value} мм`;
  }
  if (field === 'lengthM') {
    return `${value} м`;
  }
  if (field === 'thicknessMicron') {
    return `${value} мкм`;
  }
  return String(value);
}

function productHasOption(product: ParsedProduct, field: ParamFieldKey, option: ParamValue) {
  if (field === 'quantityPerBox') {
    return product.quantityPerBoxOptions.includes(String(option));
  }

  if (field === 'colorTag') {
    return product.colorTags.includes(String(option));
  }

  if (field === 'labelTag') {
    return product.labelTags.includes(String(option));
  }

  return product[field] === option;
}

function isOptionAvailable(group: ProductGroup, field: ParamFieldKey, option: ParamValue) {
  const state = getGroupState(group);
  const scopedState = {
    ...state,
    [field]: option,
  } satisfies GroupState;

  return group.products.some((product) => matchesProductState(product, scopedState, requiredKeys(group)));
}

function pickBestProductForOption(group: ProductGroup, field: ParamFieldKey, option: ParamValue) {
  const state = getGroupState(group);
  const candidates = group.products.filter((product) => productHasOption(product, field, option));

  if (candidates.length === 0) {
    return null;
  }

  return [...candidates].sort((a, b) => {
    let aScore = 0;
    let bScore = 0;

    for (const key of PARAM_KEYS) {
      if (key === field) {
        continue;
      }

      const selectedValue = state[key];
      if (selectedValue === null) {
        continue;
      }

      if (productHasOption(a, key, selectedValue)) {
        aScore += 1;
      }
      if (productHasOption(b, key, selectedValue)) {
        bScore += 1;
      }
    }

    if (aScore !== bScore) {
      return bScore - aScore;
    }

    return compareProducts(a, b);
  })[0];
}

function applyProductToState(state: GroupState, product: ParsedProduct, preferredBoxOption: ParamValue | null = null) {
  state.widthMm = product.widthMm ?? null;
  state.lengthM = product.lengthM ?? null;
  state.thicknessMicron = product.thicknessMicron ?? null;
  state.sleeveBrand = product.sleeveBrand ?? null;
  state.colorTag = product.colorTags[0] ?? null;
  state.labelTag = product.labelTags[0] ?? null;

  if (preferredBoxOption !== null && product.quantityPerBoxOptions.includes(String(preferredBoxOption))) {
    state.quantityPerBox = String(preferredBoxOption);
    return;
  }

  state.quantityPerBox = product.quantityPerBoxOptions[0] ?? null;
}

function updateField(group: ProductGroup, field: ParamFieldKey, value: ParamValue) {
  const state = getGroupState(group);
  const fallback = pickBestProductForOption(group, field, value);
  if (!fallback) {
    return;
  }

  applyProductToState(state, fallback, field === 'quantityPerBox' ? value : state.quantityPerBox);

  if (field === 'quantityPerBox') {
    state.quantityPerBox = String(value);
    return;
  }

  state[field] = value as GroupState[typeof field];
}

function articleLabel(group: ProductGroup) {
  return selectedProduct(group)?.sku ?? '—';
}

function variantCountLabel(count: number) {
  const mod10 = count % 10;
  const mod100 = count % 100;

  if (mod10 === 1 && mod100 !== 11) {
    return `${count} вариант`;
  }

  if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) {
    return `${count} варианта`;
  }

  return `${count} вариантов`;
}

function customizationNotes(group: ProductGroup) {
  const setting = groupCatalogSetting(group);
  const notes: string[] = [];

  if (setting.allowCustomLength && setting.customLengthMinM && setting.customLengthMaxM && setting.customLengthStepM) {
    notes.push(`Любая длина ${setting.customLengthMinM}-${setting.customLengthMaxM} м, шаг ${setting.customLengthStepM} м`);
  }

  if (setting.allowCustomSleeveBrand) {
    notes.push('Втулка с логотипом');
  }

  if (setting.allowCustomLabel) {
    notes.push('Нанесение надписи');
  }

  return notes;
}

function toggleExpanded(group: ProductGroup) {
  getGroupState(group).isExpanded = !getGroupState(group).isExpanded;
}

function incrementProduct(product: ProductNode) {
  if (getQuantity(product.id) === 0) {
    addProduct({
      id: product.id,
      name: product.name,
      sku: product.sku,
      isCustomizable: product.isCustomizable,
    });
    return;
  }

  incrementQuantity(product.id);
}

function decrementProduct(productId: string) {
  decrementQuantity(productId);
}

function selectedQty(group: ProductGroup) {
  const product = selectedProduct(group);
  return product ? getQuantity(product.id) : 0;
}

function incrementSelected(group: ProductGroup) {
  const product = selectedProduct(group);
  if (product) {
    incrementProduct(product);
  }
}

function decrementSelected(group: ProductGroup) {
  const product = selectedProduct(group);
  if (product) {
    decrementProduct(product.id);
  }
}
</script>

<template>
  <section class="space-y-5">
    <UiSectionSearchHero
      v-model="search"
      title="Каталог"
      search-placeholder="Поиск по артикулу, типу товара или параметрам"
    />

    <div v-if="loading" class="alert surface-card border-0">Загрузка каталога...</div>
    <div v-else-if="error" class="alert alert-error">{{ error.message }}</div>

    <div v-else-if="productGroups.length" class="space-y-4">
      <article
        v-for="group in productGroups"
        :key="group.key"
        class="surface-card rounded-3xl p-4 md:p-5"
      >
        <div class="grid gap-4 xl:grid-cols-6 xl:items-start">
          <div class="p-3 xl:col-span-1">
            <img
              :src="createProductCover(group.typeLabel, group.key)"
              :alt="`Превью группы ${group.typeLabel}`"
              class="aspect-square w-full rounded-[24px] object-cover"
              loading="lazy"
            >
          </div>

          <div class="p-4 md:p-5 xl:col-span-4">
            <div class="mb-4">
              <h2 class="text-2xl font-bold text-[#163624]">{{ group.typeLabel }}</h2>
              <div v-if="customizationNotes(group).length" class="mt-3 flex flex-wrap gap-2">
                <span
                  v-for="note in customizationNotes(group)"
                  :key="`${group.key}-${note}`"
                  class="rounded-full bg-[#eef8f1] px-3 py-1 text-xs font-semibold text-[#15613d]"
                >
                  {{ note }}
                </span>
              </div>
            </div>

            <div class="grid content-start gap-4 md:grid-cols-2 2xl:grid-cols-3">
              <div
                v-for="field in visibleFields(group)"
                :key="`${group.key}-${field.key}`"
                class="rounded-[22px] border border-base-200 bg-[#fbfcfb] p-4"
              >
                <p class="text-sm font-semibold text-[#163624]">{{ field.label }}</p>

                <div class="mt-3 flex flex-wrap gap-2">
                  <label
                    v-for="option in getAllFieldOptions(group, field.key)"
                    :key="`${group.key}-${field.key}-${option}`"
                    class="btn btn-sm rounded-full text-sm normal-case shadow-none transition"
                    :class="[
                      getGroupState(group)[field.key] === option
                        ? 'bg-neutral text-neutral-content'
                        : isOptionAvailable(group, field.key, option)
                          ? 'bg-base-100 text-base-content hover:bg-base-200'
                          : 'bg-[#eef1f4] text-[#7b8591] hover:bg-[#e3e7eb]',
                      'cursor-pointer',
                    ]"
                  >
                    <input
                      type="radio"
                      class="sr-only"
                      :name="`${group.key}-${field.key}`"
                      :checked="getGroupState(group)[field.key] === option"
                      @change="updateField(group, field.key, option)"
                    >
                    <span>{{ formatOptionLabel(field.key, option) }}</span>
                  </label>
                </div>
              </div>
            </div>
          </div>

          <aside class="p-4 md:p-5 xl:col-span-1">
            <div class="flex h-full flex-col justify-between gap-4">
              <div />

              <div class="space-y-3">
                <button
                  v-if="selectedQty(group) === 0"
                  class="btn h-11 w-full rounded-full border-0 bg-[#139957] text-sm font-semibold text-white hover:bg-[#0d854a]"
                  :disabled="!selectedProduct(group)"
                  @click="incrementSelected(group)"
                >
                  В корзину
                </button>

                <div v-else class="rounded-[22px] border border-base-300 bg-base-100 px-2 py-1">
                  <div class="flex items-center justify-between gap-2">
                    <button
                      class="btn btn-square btn-sm"
                      :disabled="selectedQty(group) === 0"
                      @click="decrementSelected(group)"
                    >
                      -
                    </button>
                    <div class="min-w-10 text-center text-lg font-semibold text-[#163624]">{{ selectedQty(group) }}</div>
                    <button class="btn btn-square btn-sm" :disabled="!selectedProduct(group)" @click="incrementSelected(group)">
                      +
                    </button>
                  </div>
                </div>

                <p class="text-center text-sm font-medium text-base-content/55">{{ articleLabel(group) }}</p>
              </div>
            </div>
          </aside>
        </div>

        <div
          v-if="getGroupState(group).isExpanded"
          class="mt-4 overflow-x-auto rounded-[28px] bg-white"
        >
          <table class="table border-separate border-spacing-0 bg-white [&_tbody_tr:hover]:bg-white [&_tbody_tr]:bg-white [&_td]:bg-white [&_th]:bg-white [&_thead_tr]:bg-white">
            <thead>
              <tr>
                <th class="border-b border-base-300">Артикул</th>
                <th class="border-b border-base-300">Ширина</th>
                <th class="border-b border-base-300">Длина</th>
                <th class="border-b border-base-300">Толщина</th>
                <th class="border-b border-base-300">Втулка</th>
                <th class="border-b border-base-300 text-right">Действие</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="product in group.products" :key="`${group.key}-${product.id}`">
                <td class="border-b border-base-200">{{ product.sku }}</td>
                <td class="border-b border-base-200">{{ product.widthMm ?? '—' }}</td>
                <td class="border-b border-base-200">{{ product.lengthM ?? '—' }}</td>
                <td class="border-b border-base-200">{{ product.thicknessMicron ?? '—' }}</td>
                <td class="border-b border-base-200">{{ product.sleeveBrand ?? '—' }}</td>
                <td class="border-b border-base-200 text-right">
                  <button
                    v-if="getQuantity(product.id) === 0"
                    class="btn h-9 rounded-full border-0 bg-[#139957] px-4 text-xs font-semibold text-white hover:bg-[#0d854a]"
                    @click="incrementProduct(product)"
                  >
                    В корзину
                  </button>
                  <div v-else class="ml-auto flex w-28 items-center justify-between rounded-xl border border-base-300 px-1 py-1">
                    <button
                      class="btn btn-xs btn-square"
                      :disabled="getQuantity(product.id) === 0"
                      @click="decrementProduct(product.id)"
                    >
                      -
                    </button>
                    <span class="min-w-8 text-center text-sm font-semibold">{{ getQuantity(product.id) }}</span>
                    <button class="btn btn-xs btn-square" @click="incrementProduct(product)">+</button>
                  </div>
                </td>
              </tr>
            </tbody>
          </table>
        </div>

        <button
          class="btn btn-ghost mt-3 w-full justify-center gap-2"
          @click="toggleExpanded(group)"
        >
          <svg
            class="h-4 w-4 transition-transform"
            :class="{ 'rotate-180': getGroupState(group).isExpanded }"
            viewBox="0 0 20 20"
            fill="none"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path
              d="M5 7.5L10 12.5L15 7.5"
              stroke="currentColor"
              stroke-width="1.8"
              stroke-linecap="round"
              stroke-linejoin="round"
            />
          </svg>
          <span>
            {{ getGroupState(group).isExpanded ? 'Свернуть все варианты' : 'Развернуть все варианты' }}
            ({{ variantCountLabel(group.products.length) }})
          </span>
        </button>
      </article>
    </div>

    <div v-else class="alert surface-card border-0">По текущему запросу товары не найдены.</div>
  </section>
</template>
