import {
  ChangeDetectionStrategy,
  Component,
  computed,
  effect,
  input,
  output,
  signal,
  untracked,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import {
  CatalogItemVariant,
  InventoryPolicy,
  ProductAttribute,
  ProductAttributeOption,
} from '../../../../core/services/catalog/catalog.interface';

@Component({
  selector: 'app-product-attribute-selector',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './product-attribute-selector.component.html',
  styleUrl: './product-attribute-selector.component.scss',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProductAttributeSelectorComponent {
  public attributes = input<ProductAttribute[]>([]);
  public variants = input<CatalogItemVariant[]>([]);
  public selectedVariant = input<CatalogItemVariant | null>(null);
  public inventoryPolicy = input.required<InventoryPolicy>();

  public variantChange = output<CatalogItemVariant | null>();

  protected readonly visibleAttributes = computed(() =>
    this.attributes().filter((attribute) => attribute.show_in_selector !== false),
  );

  protected readonly selectedAttributeOptions = signal<Record<string, number[]>>({});

  protected readonly availableOptions = computed(() => {
    const selections = this.selectedAttributeOptions();
    const variants = this.variants();
    const attributes = this.visibleAttributes();

    const availability: Record<string, Record<number, boolean>> = {};

    for (const attribute of attributes) {
      availability[attribute.codigo] = {};
      for (const option of attribute.options) {
        const optionNormalized = this.normalizeText(option.value || option.label);
        const selectedForAttribute = selections[attribute.codigo] ?? [];

        if (
          !attribute.allow_multi_select &&
          selectedForAttribute.length >= 1 &&
          !selectedForAttribute.includes(option.id)
        ) {
          availability[attribute.codigo][option.id] = false;
          continue;
        }

        const isAvailable = variants.some((variant) => {
          if (!this.isVariantAvailable(variant)) return false;

          const variantAttrValues = this.getVariantAttributeValues(attribute, variant.values);
          const desiredOptionIds = selectedForAttribute.includes(option.id)
            ? selectedForAttribute
            : [...selectedForAttribute, option.id];
          const desiredValues = desiredOptionIds
            .map((id) => attribute.options.find((candidate) => candidate.id === id))
            .filter((candidate): candidate is ProductAttributeOption => candidate !== undefined)
            .map((candidate) => this.normalizeText(candidate.value || candidate.label));

          if (
            !variantAttrValues.includes(optionNormalized) ||
            !desiredValues.every((value) => variantAttrValues.includes(value))
          ) {
            return false;
          }

          for (const otherAttr of attributes) {
            if (otherAttr.codigo === attribute.codigo) continue;
            const selectedOptionIds = selections[otherAttr.codigo] ?? [];
            const selectedValues = selectedOptionIds
              .map((id) => otherAttr.options.find((candidate) => candidate.id === id))
              .filter((candidate): candidate is ProductAttributeOption => candidate !== undefined)
              .map((candidate) => this.normalizeText(candidate.value || candidate.label));
            const variantValues = this.getVariantAttributeValues(otherAttr, variant.values);
            if (!selectedValues.every((value) => variantValues.includes(value))) {
              return false;
            }
          }
          return true;
        });

        availability[attribute.codigo][option.id] = isAvailable;
      }
    }

    return availability;
  });

  public reset(): void {
    this.initializeSelections(this.attributes(), this.selectedVariant());
  }

  constructor() {
    effect(() => {
      const attributes = this.visibleAttributes();
      const defaultVariant = this.selectedVariant();

      untracked(() => {
        this.initializeSelections(attributes, defaultVariant);
      });
    });

    effect(() => {
      const selections = this.selectedAttributeOptions();
      const variants = this.variants();
      const attributes = this.visibleAttributes();

      untracked(() => {
        this.emitMatchingVariant(selections, variants, attributes);
      });
    });
  }

  protected hasSelectedOption(
    attribute: ProductAttribute,
    option: ProductAttributeOption,
  ): boolean {
    return (this.selectedAttributeOptions()[attribute.codigo] ?? []).includes(option.id);
  }

  protected selectAttributeOption(
    attribute: ProductAttribute,
    option: ProductAttributeOption,
  ): void {
    this.selectedAttributeOptions.update((current) => {
      const selected = current[attribute.codigo] ?? [];
      const isMultiple = attribute.allow_multi_select ?? false;
      let next: number[];

      if (!isMultiple) {
        next = [option.id];
      } else if (selected.includes(option.id)) {
        next = selected.filter((id) => id !== option.id);
      } else {
        next = [...selected, option.id];
      }

      return { ...current, [attribute.codigo]: next };
    });
  }

  protected isColorOption(option: ProductAttributeOption): boolean {
    return this.findFirstHexValue(option.metadata) !== null;
  }

  protected getOptionSwatchColor(option: ProductAttributeOption): string {
    return this.findFirstHexValue(option.metadata) ?? '#D9D9D9';
  }

  private initializeSelections(
    attributes: ProductAttribute[],
    defaultVariant: CatalogItemVariant | null,
  ): void {
    const selections: Record<string, number[]> = {};

    for (const attribute of attributes) {
      const optionIds = this.findDefaultOptionIds(attribute, defaultVariant);
      if (optionIds.length > 0) {
        selections[attribute.codigo] = optionIds;
      }
    }

    this.selectedAttributeOptions.set(selections);
  }

  private findDefaultOptionIds(
    attribute: ProductAttribute,
    variant: CatalogItemVariant | null,
  ): number[] {
    if (!variant) {
      return [];
    }

    const defaultValues = this.getVariantAttributeValues(attribute, variant.values);
    if (defaultValues.length === 0) {
      return [];
    }

    return attribute.options
      .filter(
        (option) =>
          defaultValues.includes(this.normalizeText(option.value)) ||
          defaultValues.includes(this.normalizeText(option.label)),
      )
      .map((option) => option.id);
  }

  private getVariantAttributeValues(
    attribute: ProductAttribute,
    variantAttributes: Record<string, string | string[]>,
  ): string[] {
    const normalizedCodigo = this.normalizeText(attribute.codigo);
    const normalizedNombre = this.normalizeText(attribute.nombre);

    for (const [key, value] of Object.entries(variantAttributes)) {
      const normalizedKey = this.normalizeText(key);

      if (normalizedKey === normalizedCodigo || normalizedKey === normalizedNombre) {
        return (Array.isArray(value) ? value : [value]).map((item) => this.normalizeText(item));
      }
    }

    return [];
  }

  private normalizeText(value: string | null | undefined): string {
    return (value ?? '')
      .normalize('NFD')
      .replace(/[\u0300-\u036f]/g, '')
      .trim()
      .toLowerCase();
  }

  private isVariantAvailable(variant: CatalogItemVariant): boolean {
    return variant.maximum_addable_quantity !== 0;
  }

  private findFirstHexValue(value: unknown): string | null {
    if (typeof value === 'string') {
      const match = value.match(/#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})\b/);
      return match ? match[0] : null;
    }

    if (Array.isArray(value)) {
      for (const item of value) {
        const found = this.findFirstHexValue(item);
        if (found) {
          return found;
        }
      }
      return null;
    }

    if (value && typeof value === 'object') {
      for (const item of Object.values(value)) {
        const found = this.findFirstHexValue(item);
        if (found) {
          return found;
        }
      }
    }

    return null;
  }

  private emitMatchingVariant(
    selections: Record<string, number[]>,
    variants: CatalogItemVariant[],
    attributes: ProductAttribute[],
  ): void {
    if (attributes.length === 0 || variants.length === 0) {
      this.variantChange.emit(null);
      return;
    }

    const selectedValuesByCode: Record<string, string[]> = {};
    let allSelected = true;
    for (const attr of attributes) {
      const selectedOptionIds = selections[attr.codigo] ?? [];
      if (
        selectedOptionIds.length === 0 ||
        (!attr.allow_multi_select && selectedOptionIds.length !== 1)
      ) {
        allSelected = false;
        break;
      }
      selectedValuesByCode[attr.codigo] = selectedOptionIds
        .map((id) => attr.options.find((option) => option.id === id))
        .filter((option): option is ProductAttributeOption => option !== undefined)
        .map((option) => this.normalizeText(option.value || option.label))
        .sort();
    }

    if (!allSelected) {
      this.variantChange.emit(null);
      return;
    }

    const matchingVariant = variants.find((variant) => {
      return attributes.every((attr) => {
        const selectedValues = selectedValuesByCode[attr.codigo];
        const variantValues = this.getVariantAttributeValues(attr, variant.values).sort();
        return (
          selectedValues.length === variantValues.length &&
          selectedValues.every((value, index) => value === variantValues[index])
        );
      });
    });

    this.variantChange.emit(matchingVariant || null);
  }
}
