import toggle from "../../scripts/modules/toggleUtil";

interface TableConfig {
  expandButtonSelector: string;
  scrollableContainerSelector: string;
  responsiveTableSelector: string;
}

export const defaultConfig: TableConfig = {
  expandButtonSelector: "[data-subrow-toggle]",
  scrollableContainerSelector: ".table-scrollable",
  responsiveTableSelector: `[data-table="responsive"]`,
};

export default class Table {
  private element: HTMLElement;
  private config: TableConfig;
  private expandButtons: HTMLElement[];
  private scrollableContainer: HTMLElement | null;
  private resizeObserver: ResizeObserver;

  constructor(element: HTMLElement, config?: Partial<TableConfig>) {
    this.element = element;
    this.config = { ...defaultConfig, ...config };

    this.addRole = this.addRole.bind(this);
    this.addARIA = this.addARIA.bind(this);
    this.onExpandClick = this.onExpandClick.bind(this);

    this.expandButtons = [];
    this.scrollableContainer = null;

    (this.element as any).ODS_Table = this;

    this.resizeObserver = new ResizeObserver((entries) => {
      if (!this.scrollableContainer) return;

      const containerRect = this.scrollableContainer.getBoundingClientRect();

      entries.forEach((entry) => {
        if (entry.contentRect.width > containerRect.width) {
          this.scrollableContainer?.setAttribute("tabindex", "0");
        } else {
          this.scrollableContainer?.removeAttribute("tabindex");
        }
      });
    });

    this.init();

    return this;
  }

  private addRole(selector: string, role: string): void {
    const items = Array.from(this.element.querySelectorAll(selector));

    items.forEach((item) => {
      item.setAttribute("role", role);
    });
  }

  private addARIA(): void {
    try {
      this.element.setAttribute("role", "table");
      this.addRole("thead, tbody, tfoot", "rowgroup");
      this.addRole("tr", "row");
      this.addRole("td", "cell");
      this.addRole("th", "columnheader");
      this.addRole("th[scope=row]", "rowheader");
      // caption role not needed as it is not a real role and
      // browsers do not dump their own role with display block
    } catch (e) {
      console.log("Table.addARIA(): " + e);
    }
  }

  private onExpandClick(e: Event): void {
    const button = e.currentTarget as HTMLElement;
    const ariaControls = button.getAttribute("aria-controls") ?? "";
    const controlRows = this.element.querySelectorAll(
      ariaControls
        .split(" ")
        .map((id) => `#${id}`)
        .join(","),
    );

    toggle({
      element: button,
      attribute: "aria-expanded",
    });

    controlRows.forEach((row) => {
      toggle({
        element: row as HTMLElement,
        attribute: "hidden",
      });
    });
  }

  private init(): void {
    this.expandButtons = Array.from(
      this.element.querySelectorAll<HTMLElement>(
        this.config.expandButtonSelector,
      ),
    );

    this.expandButtons.forEach((button) => {
      button.addEventListener("click", this.onExpandClick);
    });

    if (this.element.matches(this.config.responsiveTableSelector)) {
      this.addARIA();
    }
    this.maybeInitScrollable();
  }

  private maybeInitScrollable(): boolean | undefined {
    this.scrollableContainer = this.element.closest<HTMLElement>(
      this.config.scrollableContainerSelector,
    );
    if (this.scrollableContainer) {
      this.resizeObserver.observe(this.element);
      return true;
    }
    return undefined;
  }

  public destroy(): void {
    this.expandButtons.forEach((button) => {
      button.removeEventListener("click", this.onExpandClick);
    });

    this.resizeObserver.disconnect();

    this.expandButtons = [];
  }

  public update(): void {
    this.destroy();
    this.init();
  }

  static getInstance(el: HTMLElement): Table | null {
    return el && (el as any).ODS_Table ? (el as any).ODS_Table : null;
  }
}
