import { compose } from '../../helpers';
import { getComponentName } from '../../helpers/componentHelpers';
import {
  componentNameValidationMixin,
  createProxy,
  createStyleMixin,
  draggableMixin,
} from '../../mixins';

import { defaultRowDetailsRenderer, isValidDataType } from './helpers';

export const componentName = getComponentName('grid');

const GridMixin = (superclass) =>
  class GridMixinClass extends superclass {
    #columns;

    init() {
      super.init?.();
      this.handleColumns();
      this.forwardSelectedItemsChange();

      // disable the grid sort
      this.baseElement._mapSorters = () => {};

      // there is an issue in vaadin that on some cases when trying to render the details panel, the row is null and this fn throwing
      // so we override it to prevent the error
      const origUpdateDetailsCellHeight = this.baseElement._updateDetailsCellHeight;
      this.baseElement._updateDetailsCellHeight = (row) => {
        if (!row) return;
        origUpdateDetailsCellHeight.call(this.baseElement, row);
      };

      this.baseElement.rowDetailsRenderer = this.#rowDetailsRenderer.bind(this);
    }

    // this renders the details panel content
    // in order to open it, the descope-grid-item-details-column should be used
    #rowDetailsRenderer = async (cell, _col, model) => {
      // prevent details panel from being scrolled into view when clicked
      cell.addEventListener('mousedown', (e) => e.stopImmediatePropagation(), true);

      const template = this.getRowDetailsTemplate(model.item);

      // eslint-disable-next-line no-param-reassign
      cell.innerHTML = '';
      cell.append(template.content.cloneNode(true));
    };

    getRowDetailsTemplate(item) {
      const itemLabelsMapping = this.#columns.reduce(
        (acc, { path, header }) => (!path || !header ? acc : { ...acc, [path]: header }),
        {}
      );
      const template =
        this.rowDetailsRenderer?.(item, itemLabelsMapping) ??
        defaultRowDetailsRenderer(item, itemLabelsMapping);

      switch (true) {
        case template instanceof HTMLTemplateElement:
          return template;
        case typeof template === 'string':
          return Object.assign(document.createElement('template'), { innerHTML: template });
        default:
          throw new Error('rowDetailsRenderer should return a string or a template');
      }
    }

    forwardSelectedItemsChange() {
      this.baseElement.addEventListener('selected-items-changed', (e) => {
        this.dispatchEvent(
          new CustomEvent('selected-items-changed', {
            bubbles: true,
            composed: true,
            detail: e.detail,
          })
        );
      });
    }

    attributeChangedCallback(attrName, oldValue, newValue) {
      super.attributeChangedCallback?.(attrName, oldValue, newValue);

      if (attrName === 'columns') {
        this.setColumnsDataFromAttr();
      }
    }

    handleColumns() {
      if (this.columnsAttr) {
        this.setColumnsDataFromAttr();
      } else if (this.children.length) {
        this.setColumnsFromChildren();
      }
    }

    setColumnsFromChildren() {
      this.#columns = Array.from(this.children).map((child) => {
        return {
          path: child.getAttribute('path'),
          header: child.getAttribute('header'),
          type: child.localName.match('^descope-grid-(\\w+)-column$')?.[1] || 'text',

          attrs: ['frozen', 'resizable', 'auto-width', 'status'].reduce((acc, attrName) => {
            const attrVal = child.getAttribute(attrName);

            if (attrVal) {
              acc[attrName] = attrVal;
            }

            return acc;
          }, {}),
        };
      });
    }

    get columnsAttr() {
      return this.getAttribute('columns');
    }

    setColumnsDataFromAttr() {
      try {
        const data = JSON.parse(this.columnsAttr);
        if (isValidDataType(data)) {
          this.columns = data;
        }
      } catch (e) {
        // eslint-disable-next-line no-console
        console.error('could not parse data string from attribute "columns" -', e.message);
      }
    }

    // eslint-disable-next-line class-methods-use-this
    #renderColumn = ({ path, header, type, attrs }) => {
      const colAttrs = Object.entries(attrs)
        .map(([key, value]) => `${key}="${value}"`)
        .join(' ');

      const tagName = `descope-grid-${type}-column`;

      return `<${tagName} header="${header}" path="${path}" ${colAttrs}></${tagName}>`;
    };

    get renderColumn() {
      return this.#renderColumn;
    }

    set renderColumn(renderFn) {
      this.#renderColumn = renderFn;
      this.renderColumns();
    }

    getColumnsTemplate() {
      return this.#columns?.reduce?.(
        (acc, item) => acc + (this.renderColumn?.(item || {}) || ''),
        ''
      );
    }

    // there is an issue in vaadin-grid, when rowDetailsRenderer is set, it renders an empty details panel
    reassignRowDetailsRenderer() {
      this.baseElement.rowDetailsRenderer = null;
      setTimeout(() => {
        this.baseElement.rowDetailsRenderer = this.#rowDetailsRenderer.bind(this);
      }, 0);
    }

    renderColumns() {
      const template = this.getColumnsTemplate();
      if (template) {
        this.reassignRowDetailsRenderer();
        this.innerHTML = template;
      }
    }

    get grid() {
      return this.shadowRoot.querySelector('vaadin-grid');
    }

    get data() {
      return this.grid.items;
    }

    set data(data) {
      if (isValidDataType(data) && this.grid.items !== data) {
        this.grid.items = data;

        if (this.grid.selectedItems) {
          const itemsIds = new Set(
            this.grid.items.map((item) => item[this.uniqueColumnId] ?? item)
          );

          const newSelectedItems = this.grid.selectedItems.filter((selectedItem) =>
            itemsIds.has(selectedItem[this.uniqueColumnId] ?? selectedItem)
          );

          // we want to update the selected items only if there is a reason,
          // to prevent redundant notifications
          if (this.grid.selectedItems.length !== newSelectedItems.length) {
            this.grid.selectedItems = newSelectedItems;
          }
        }
      }
    }

    get columns() {
      return this.#columns;
    }

    set columns(data) {
      this.#columns = data;
      this.renderColumns();
    }

    get paths() {
      return this.columns.map((col) => col.path);
    }

    get uniqueColumnId() {
      return this.getAttribute('unique-column-id');
    }
  };

const {
  host,
  headerRow,
  headerRowCell,
  contentRow,
  firstRow,
  sortIndicators,
  activeSortIndicator,
  selectedRow,
  rowSeparator,
  resizeHandle,
  toggleDetailsPanelButton,
  toggleDetailsPanelButtonOpened,
  toggleDetailsPanelButtonClosed,
  detailsPanel,
  detailsPanelLabels,
  selectedRowCell,
  detailsPanelContent,
} = {
  host: { selector: () => 'vaadin-grid' },
  headerRow: { selector: () => '::part(header-cell)' },
  headerRowCell: { selector: () => 'vaadin-grid::part(header-cell)' },
  contentRow: { selector: () => '::part(cell)' },
  firstRow: { selector: () => '::part(first-header-row-cell)' },
  selectedRow: { selector: () => '::part(selected-row)' },
  selectedRowCell: { selector: () => '::part(selected-row-cell)' },
  sortIndicators: { selector: () => 'vaadin-grid-sorter::part(indicators)' },
  activeSortIndicator: { selector: () => 'vaadin-grid-sorter[direction]' },
  rowSeparator: { selector: () => 'vaadin-grid::part(body-cell)' },
  resizeHandle: { selector: () => '::part(resize-handle)' },
  toggleDetailsPanelButton: { selector: () => 'vaadin-grid vaadin-icon.toggle-details-button' },
  toggleDetailsPanelButtonOpened: {
    selector: () => 'vaadin-grid vaadin-icon.toggle-details-button.opened',
  },
  toggleDetailsPanelButtonClosed: {
    selector: () => 'vaadin-grid vaadin-icon.toggle-details-button.closed',
  },
  detailsPanel: { selector: () => 'vaadin-grid::part(details-cell)' },
  detailsPanelLabels: { selector: () => 'vaadin-grid .row-details__label' },
  detailsPanelContent: { selector: () => 'vaadin-grid .row-details' },
};

export const GridClass = compose(
  createStyleMixin({ componentNameOverride: getComponentName('input-wrapper') }),
  createStyleMixin({
    mappings: {
      hostWidth: { selector: () => ':host', property: 'width' },
      hostHeight: { selector: () => ':host', property: 'height' },
      hostMinHeight: { selector: () => ':host', property: 'min-height' },
      fontFamily: [{ ...headerRow }, { ...contentRow }],
      fontSize: [{ ...headerRow }, { ...contentRow }],
      fontWeight: { ...contentRow },
      valueTextColor: { ...contentRow, property: 'color' },
      backgroundColor: [
        { ...host, property: 'background-color' },
        { ...contentRow, property: 'background-color' },
      ],
      sortIndicatorsColor: { ...sortIndicators, property: 'color' },
      activeSortIndicator: { ...activeSortIndicator, property: 'color' },
      borderColor: { ...host, property: 'border-color' },
      borderWidth: { ...host, property: 'border-width' },
      borderStyle: { ...host, property: 'border-style' },
      borderRadius: { ...host, property: 'border-radius' },
      selectedBackgroundColor: [
        { ...selectedRow, property: 'background-color' },
        { ...selectedRowCell, property: 'background-color' },
      ],
      headerRowTextColor: { ...headerRowCell, property: 'color' },
      separatorColor: [
        { ...firstRow, property: 'border-bottom-color' },
        { ...rowSeparator, property: 'border-top-color' },
      ],
      resizeHandleColor: { ...resizeHandle, property: 'background-color' },
      hostDirection: { ...host, property: 'direction', fallback: 'ltr' },
      toggleDetailsPanelButtonSize: [
        { ...toggleDetailsPanelButton, property: 'width' },
        { ...toggleDetailsPanelButton, property: 'height' },
      ],
      toggleDetailsPanelButtonOpenedColor: { ...toggleDetailsPanelButtonOpened, property: 'color' },
      toggleDetailsPanelButtonClosedColor: { ...toggleDetailsPanelButtonClosed, property: 'color' },
      toggleDetailsPanelButtonCursor: { ...toggleDetailsPanelButton, property: 'cursor' },
      detailsPanelBackgroundColor: { ...detailsPanel, property: 'background-color' },
      detailsPanelBorderTopColor: { ...detailsPanel, property: 'border-top-color' },
      detailsPanelLabelsColor: { ...detailsPanelLabels, property: 'color' },
      detailsPanelLabelsFontSize: { ...detailsPanelLabels, property: 'font-size' },
      detailsPanelItemsGap: { ...detailsPanelContent, property: 'grid-gap' },
      detailsPanelPadding: { ...detailsPanelContent, property: 'padding' },
    },
  }),
  draggableMixin,
  componentNameValidationMixin,
  GridMixin
)(
  createProxy({
    delegatesFocus: false,
    slots: [''],
    wrappedEleName: 'vaadin-grid',
    style: () => `
      /*css*/
      vaadin-grid {
        overflow: hidden;
        height: 100%;
        min-height: 300px;
      }
      vaadin-grid-cell-content {
        display: flex;
      }
      vaadin-grid::part(selected-row-cell) {
        background-image: none;
        box-shadow: none;
        background-color: inherit;
      }
      vaadin-grid::part(details-cell) {
        border-top-style: dashed;
        border-top-width: 1px;
      }
      vaadin-grid .row-details {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(max(200px, calc(100%/4 - var(${GridClass.cssVarList.detailsPanelItemsGap}))), 1fr));
        width: 100%;
      }
      vaadin-grid .row-details__item:has(.row-details__value.code) {
        grid-column: 1 / -1;
        order: 2;
      }
      vaadin-grid .row-details__value.text {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: pre;
      }
      vaadin-grid .row-details__value.code {
        margin-top: 5px;
        max-height: 120px;
        overflow: scroll;
        font-size: 0.85em;
      }
      vaadin-grid vaadin-icon.toggle-details-button {
        margin: auto;
      }
    /*!css*/
		`,
    excludeAttrsSync: ['columns', 'tabindex', 'style'],
    componentName,
  })
);
