/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 */

import {
  TableColumnOrder,
  TableColumnSizes,
  TableColumns,
  TableOnColumnResize,
  TableOnSort,
  TableRowSortOrder,
} from './types';
import {normaliseColumnWidth, isPercentage} from './utils';
import {PureComponent} from 'react';
import ContextMenu from '../ContextMenu';
import Interactive from '../Interactive';
import styled from 'react-emotion';
import {colors} from '../colors';
import FlexRow from '../FlexRow';
import invariant from 'invariant';
import {MenuItemConstructorOptions} from 'electron';
import React from 'react';

const TableHeaderArrow = styled('span')({
  float: 'right',
});
TableHeaderArrow.displayName = 'TableHead:TableHeaderArrow';

const TableHeaderColumnInteractive = styled(Interactive)({
  display: 'inline-block',
  overflow: 'hidden',
  textOverflow: 'ellipsis',
  whiteSpace: 'nowrap',
  width: '100%',
});
TableHeaderColumnInteractive.displayName =
  'TableHead:TableHeaderColumnInteractive';

const TableHeaderColumnContainer = styled('div')({
  padding: '0 8px',
});
TableHeaderColumnContainer.displayName = 'TableHead:TableHeaderColumnContainer';

const TableHeadContainer = styled(FlexRow)(
  (props: {horizontallyScrollable?: boolean}) => ({
    borderBottom: `1px solid ${colors.sectionHeaderBorder}`,
    color: colors.light50,
    flexShrink: 0,
    left: 0,
    overflow: 'hidden',
    right: 0,
    textAlign: 'left',
    top: 0,
    zIndex: 2,
    minWidth: props.horizontallyScrollable ? 'min-content' : 0,
  }),
);
TableHeadContainer.displayName = 'TableHead:TableHeadContainer';

const TableHeadColumnContainer = styled('div')(
  (props: {width: string | number}) => ({
    position: 'relative',
    backgroundColor: colors.white,
    flexShrink: props.width === 'flex' ? 1 : 0,
    height: 23,
    lineHeight: '23px',
    fontSize: '0.85em',
    fontWeight: 500,
    width: props.width === 'flex' ? '100%' : props.width,
    '&::after': {
      position: 'absolute',
      content: '""',
      right: 0,
      top: 5,
      height: 13,
      width: 1,
      background: colors.light15,
    },
    '&:last-child::after': {
      display: 'none',
    },
  }),
);
TableHeadColumnContainer.displayName = 'TableHead:TableHeadColumnContainer';

const RIGHT_RESIZABLE = {right: true};

function calculatePercentage(parentWidth: number, selfWidth: number): string {
  return `${(100 / parentWidth) * selfWidth}%`;
}

class TableHeadColumn extends PureComponent<{
  id: string;
  width: string | number;
  sortable?: boolean;
  isResizable: boolean;
  leftHasResizer: boolean;
  hasFlex: boolean;
  sortOrder?: TableRowSortOrder;
  onSort?: TableOnSort;
  columnSizes: TableColumnSizes;
  onColumnResize?: TableOnColumnResize;
  children?: React.ReactNode;
  title?: string;
  horizontallyScrollable?: boolean;
}> {
  ref: HTMLElement | undefined;

  componentDidMount() {
    if (this.props.horizontallyScrollable && this.ref) {
      // measure initial width
      this.onResize(this.ref.offsetWidth);
    }
  }

  onClick = () => {
    const {id, onSort, sortOrder} = this.props;

    const direction =
      sortOrder && sortOrder.key === id && sortOrder.direction === 'down'
        ? 'up'
        : 'down';

    if (onSort) {
      onSort({
        direction,
        key: id,
      });
    }
  };

  onResize = (newWidth: number) => {
    const {id, onColumnResize, width} = this.props;
    if (!onColumnResize) {
      return;
    }

    let normalizedWidth: number | string = newWidth;

    // normalise number to a percentage if we were originally passed a percentage
    if (isPercentage(width) && this.ref) {
      const {parentElement} = this.ref;
      invariant(parentElement, 'expected there to be parentElement');

      const parentWidth = parentElement!.clientWidth;
      const {childNodes} = parentElement!;

      const lastElem = childNodes[childNodes.length - 1];
      const right =
        lastElem instanceof HTMLElement
          ? lastElem.offsetLeft + lastElem.clientWidth + 1
          : 0;

      if (right < parentWidth) {
        normalizedWidth = calculatePercentage(parentWidth, newWidth);
      }
    }

    onColumnResize(id, normalizedWidth);
  };

  setRef = (ref: HTMLElement) => {
    this.ref = ref;
  };

  render() {
    const {isResizable, sortable, width, title} = this.props;
    let {children} = this.props;
    children = (
      <TableHeaderColumnContainer>{children}</TableHeaderColumnContainer>
    );

    if (isResizable) {
      children = (
        <TableHeaderColumnInteractive
          grow={true}
          resizable={RIGHT_RESIZABLE}
          onResize={this.onResize}
          minWidth={20}>
          {children}
        </TableHeaderColumnInteractive>
      );
    }

    return (
      <TableHeadColumnContainer
        width={width}
        title={title}
        onClick={sortable === true ? this.onClick : undefined}
        innerRef={this.setRef}>
        {children}
      </TableHeadColumnContainer>
    );
  }
}

export default class TableHead extends PureComponent<{
  columnOrder: TableColumnOrder;
  onColumnOrder?: (order: TableColumnOrder) => void;
  columns: TableColumns;
  sortOrder?: TableRowSortOrder;
  onSort?: TableOnSort;
  columnSizes: TableColumnSizes;
  onColumnResize?: TableOnColumnResize;
  horizontallyScrollable?: boolean;
}> {
  buildContextMenu = (): MenuItemConstructorOptions[] => {
    const visibles = this.props.columnOrder
      .map(c => (c.visible ? c.key : null))
      .filter(Boolean)
      .reduce((acc, cv) => {
        acc.add(cv);
        return acc;
      }, new Set());
    return Object.keys(this.props.columns).map(key => {
      const visible = visibles.has(key);
      return {
        label: this.props.columns[key].value,
        click: () => {
          const {onColumnOrder, columnOrder} = this.props;
          if (onColumnOrder) {
            const newOrder = columnOrder.slice();
            let hasVisibleItem = false;
            for (let i = 0; i < newOrder.length; i++) {
              const info = newOrder[i];
              if (info.key === key) {
                newOrder[i] = {key, visible: !visible};
              }
              hasVisibleItem = hasVisibleItem || newOrder[i].visible;
            }

            // Dont allow hiding all columns
            if (hasVisibleItem) {
              onColumnOrder(newOrder);
            }
          }
        },
        type: 'checkbox' as 'checkbox',
        checked: visible,
      };
    });
  };

  render() {
    const {
      columnOrder,
      columns,
      columnSizes,
      onColumnResize,
      onSort,
      sortOrder,
      horizontallyScrollable,
    } = this.props;
    const elems = [];

    let hasFlex = false;
    for (const column of columnOrder) {
      if (column.visible && columnSizes[column.key] === 'flex') {
        hasFlex = true;
        break;
      }
    }

    let lastResizable = true;

    const colElems: {
      [key: string]: JSX.Element;
    } = {};
    for (const column of columnOrder) {
      if (!column.visible) {
        continue;
      }

      const key = column.key;
      const col = columns[key];

      let arrow;
      if (col.sortable === true && sortOrder && sortOrder.key === key) {
        arrow = (
          <TableHeaderArrow>
            {sortOrder.direction === 'up' ? '▲' : '▼'}
          </TableHeaderArrow>
        );
      }

      const width = normaliseColumnWidth(columnSizes[key]);
      const isResizable = col.resizable !== false;

      const elem = (
        <TableHeadColumn
          key={key}
          id={key}
          hasFlex={hasFlex}
          isResizable={isResizable}
          leftHasResizer={lastResizable}
          width={width}
          sortable={col.sortable}
          sortOrder={sortOrder}
          onSort={onSort}
          columnSizes={columnSizes}
          onColumnResize={onColumnResize}
          title={key}
          horizontallyScrollable={horizontallyScrollable}>
          {col.value}
          {arrow}
        </TableHeadColumn>
      );

      elems.push(elem);

      colElems[key] = elem;

      lastResizable = isResizable;
    }
    return (
      <ContextMenu buildItems={this.buildContextMenu}>
        <TableHeadContainer horizontallyScrollable={horizontallyScrollable}>
          {elems}
        </TableHeadContainer>
      </ContextMenu>
    );
  }
}
