import { computed, makeObservable, observable } from 'mobx';
import { FiltersMap, KeyedItem } from '@wix/bex-core';
import { TableState } from '../../state';
import { GroupBy, RenderSection, Section } from '../CollectionSectionHeader';

const SECTION_PREFIX = '$$$wix-patterns-sections$$$-';
const INVISIBLE_ROW_ID = `${SECTION_PREFIX}invisible`;

export class TableSectionsState<T, F extends FiltersMap> {
  table: TableState<T, F>;
  groupBy: GroupBy<T>;
  renderSection: RenderSection<T>;
  collapsible: boolean;
  onToggleCallback?: (sectionId: string, isCollapsed: boolean) => void;

  constructor(
    table: TableState<T, F>,
    groupBy: GroupBy<T>,
    renderSection: RenderSection<T>,
    collapsible?: boolean,
  ) {
    this.table = table;
    this.groupBy = groupBy;
    this.renderSection = renderSection;
    this.collapsible = collapsible ?? false;
    makeObservable(this, {
      renderSection: observable.ref,
      sectionsKeyedItems: computed,
      sectionCounts: computed,
    });
  }

  toggleSection = (sectionId: string) => {
    const currentCollapsed = this.isSectionCollapsed(sectionId);
    this.onToggleCallback?.(sectionId, !currentCollapsed);
  };

  private isSectionCollapsed(sectionId: string): boolean {
    const sectionItems = this.getSectionItems(sectionId);
    const section = this.renderSection(sectionId, sectionItems);
    return section.collapsed ?? false;
  }

  private getSectionItems(sectionId: string): T[] {
    const items: T[] = [];
    for (const keyedItem of this.table.keyedItems) {
      if (this.groupBy(keyedItem.item) === sectionId) {
        items.push(keyedItem.item);
      }
    }
    return items;
  }

  private generateSectionId(firstItemId: string): string {
    return `${SECTION_PREFIX}${firstItemId}`;
  }

  private get computeSectionData(): {
    counts: Map<string, { count: number; isComplete: boolean }>;
    items: Map<string, T[]>;
    indices: Map<string, number>;
    keyedItems: KeyedItem<T>[];
  } {
    const counts = new Map<string, { count: number; isComplete: boolean }>();
    const items = new Map<string, T[]>();
    const indices = new Map<string, number>();
    const keyedItems: KeyedItem<T>[] = [];

    if (this.table.keyedItems.length === 0) {
      return { counts, items, indices, keyedItems };
    }

    // Add invisible row for design purposes
    keyedItems.push({
      id: INVISIBLE_ROW_ID,
      key: INVISIBLE_ROW_ID,
      item: this.table.keyedItems[0].item,
      index: 0,
      pageIndex: 0,
      indexWithinPage: 0,
    });

    let currentSectionId: string | null = null;
    let currentSectionIndex: number = 0;
    let currentHeaderId: string | null = null;
    let count = 0;
    let isCurrentSectionCollapsed = false;
    let currentSectionItems: T[] = [];

    for (const keyedItem of this.table.keyedItems) {
      const sectionId = this.groupBy(keyedItem.item);

      if (sectionId !== currentSectionId) {
        // Store count and items for previous section
        if (currentHeaderId) {
          counts.set(currentHeaderId, { count, isComplete: true });
          items.set(currentHeaderId, currentSectionItems);
        }

        // Start new section
        currentSectionId = sectionId;
        currentHeaderId = this.generateSectionId(
          `${sectionId}@${currentSectionIndex}`,
        );
        indices.set(currentHeaderId, currentSectionIndex);
        currentSectionIndex++;
        count = 1;
        currentSectionItems = [keyedItem.item];

        // Determine if section is collapsed from renderSection
        const section = this.renderSection(
          sectionId,
          this.getSectionItems(sectionId),
        );
        isCurrentSectionCollapsed =
          this.collapsible && (section.collapsed ?? false);

        // Add section header to keyed items
        const sectionKeyedItem: KeyedItem<T> = {
          id: currentHeaderId,
          key: currentHeaderId,
          item: keyedItem.item,
          index: keyedItems.length,
          pageIndex: 0,
          indexWithinPage: keyedItems.length,
        };
        keyedItems.push(sectionKeyedItem);
      } else {
        count++;
        currentSectionItems.push(keyedItem.item);
      }

      // Add the actual data item only if section is not collapsed
      if (!isCurrentSectionCollapsed) {
        keyedItems.push(keyedItem);
      }
    }

    // Handle last section
    if (currentHeaderId) {
      const isLastSectionComplete = this.table.collection.fetchedLastPage;
      counts.set(currentHeaderId, { count, isComplete: isLastSectionComplete });
      items.set(currentHeaderId, currentSectionItems);
    }

    return { counts, items, indices, keyedItems };
  }

  get sectionCounts(): Map<string, { count: number; isComplete: boolean }> {
    return this.computeSectionData.counts;
  }

  get sectionItems(): Map<string, T[]> {
    return this.computeSectionData.items;
  }

  get sectionIndices(): Map<string, number> {
    return this.computeSectionData.indices;
  }

  get sectionsKeyedItems(): KeyedItem<T>[] {
    return this.computeSectionData.keyedItems;
  }

  getSectionWithCount(sectionKeyedItem: KeyedItem<T>): {
    sectionId: string;
    sectionIndex: number;
    section: Section & {
      count: number;
      hasMore: boolean;
      items: T[];
      collapsed: boolean;
    };
    collapsible: boolean;
  } {
    const headerId = sectionKeyedItem.id;
    const sectionData = headerId
      ? this.sectionCounts.get(headerId) || { count: 0, isComplete: true }
      : { count: 0, isComplete: true };
    const sectionItemsList = headerId
      ? this.sectionItems.get(headerId) || []
      : [];
    const sectionIndex = headerId ? this.sectionIndices.get(headerId) ?? 0 : 0;

    const sectionId = this.groupBy(sectionKeyedItem.item);
    const section = this.renderSection(sectionId, sectionItemsList);

    return {
      sectionId,
      sectionIndex,
      section: {
        ...section,
        count: sectionData.count,
        hasMore: !sectionData.isComplete,
        items: sectionItemsList,
        collapsed: this.collapsible && (section.collapsed ?? false),
      },
      collapsible: this.collapsible,
    };
  }

  isSectionRow(keyedItem: KeyedItem<T>): boolean {
    return keyedItem.id.startsWith(SECTION_PREFIX);
  }

  isInvisibleRow(keyedItem: KeyedItem<T>): boolean {
    return keyedItem.id === INVISIBLE_ROW_ID;
  }
}
