/**
 * TEAM: frontend_infra
 * @flow
 */

import * as React from "react";
import {StyleSheet, css, type SheetEntry} from "aphrodite";
import Button from "../button/Button";
import IconButton from "../button/IconButton";
import {whitespaceSizeConstants} from "../styles/whitespace";
import colors from "../colors";
import {border} from "../styles/index";
import SelectFilter from "./SelectFilter";
import BaseFilter, {type PopupPlacement} from "./BaseFilter";

import {type Size} from "../sizes";

// Shape of one row in the filter dropdown, onApply() returns array of these
export type FilterRowData<T> = {|
  +operator?: string,
  +type: string,
  +values: T,
|};

export type Props<T> = {|
  /** Whether the filter is disabled */
  +disabled?: boolean,
  /** Controls where the dropdown menu is anchored in relation to the multiselect input */
  +dropdownPlacement?: PopupPlacement,
  /** Label on button that triggers dropdown */
  +label: string,
  /** Whether to use a Portal or React Fragment component for dropdown */
  +noPortal?: boolean,
  /** Called when 'Apply' button clicked */
  +onApply: (values: $ReadOnlyArray<FilterRowData<T>>) => void,
  /** Show the include/exclude filter at the beginning of each row */
  +showOperatorFilter?: boolean,
  /** The size of the filter button */
  +size?: Size,
  /** Type filter column: customizes the contents of the type filter */
  +typeFilter: {|
    +label?: string,
    +options: $ReadOnlyArray<{|+label: string, +value: any|}>,
  |},
  /** Value of conditionalFilter is an array of objects.
   * Each entry has fields: values, type & operator.
   * */
  +value?: $ReadOnlyArray<FilterRowData<T>>,
  /** Change the default that value filter is initialized to when a new row added. */
  +valueFilterDefault?: T,
  /** RenderProp for the valueFilter, receives rows current values & type data */
  +valueFilterRenderer: (
    type: string,
    value: T,
    updateValue: (value: T) => void
  ) => React.Node,
  /** event handler that gets triggered when you want to show/hide filter popup */
  +onClick?: () => void,
|};

// resolve a good display value from an option
function resolveLabel(value: any): string {
  if (typeof value === "object") {
    return ["label", "name", "value"].reduce(
      (returnVal, key) => returnVal || value[key],
      ""
    );
  }
  return value || "";
}

/**
 * @short Conditional Filter - Create a dropdown that allows for multiple rows of a filter and conditions on that filter.
 * @category Filter
 * @brandStatus V2
 * @status Stable
 * ConditionalFilter - designed to handle grouping several filters of same type under a single dropdown,
 * and allowing an `everything but` ability for filters by using the exclude option.
 */
function ConditionalFilter<T>({
  disabled,
  dropdownPlacement,
  label,
  noPortal = false,
  onApply,
  showOperatorFilter = true,
  size,
  typeFilter,
  value,
  // $FlowFixMe[incompatible-type] v0.144.0 -> v0.145.0
  valueFilterDefault = [],
  valueFilterRenderer,
  onClick,
}: Props<T>): React.Element<typeof BaseFilter> {
  const defaultRowValues: FilterRowData<T> = {
    operator: "",
    type: "",
    values: valueFilterDefault,
  };

  const [internalValues, setInternalValues] = React.useState([
    defaultRowValues,
  ]);
  const [selectedText, setSelectedText] = React.useState("");

  React.useEffect(() => {
    if (value && value.length) {
      setInternalValues(value);
    }
    // generate specific selectedText label for filter button
    if (value && value.length > 1) {
      return setSelectedText(`${value.length} rules`);
    }
    if (value && value.length === 1) {
      const val = value[0];
      let newSelectedText = `${
        showOperatorFilter && val.operator ? `${val.operator}s ` : ""
      }`;
      if (val.type) {
        const type = typeFilter.options.find(o => o.value === val.type);
        newSelectedText += resolveLabel(type);
      }
      if (Array.isArray(val.values) && val.values.length > 0) {
        newSelectedText += `: ${
          val.values.length > 1
            ? `${val.values.length} values`
            : resolveLabel(val.values[0])
        }`;
      } else if (resolveLabel(val.values)) {
        newSelectedText += `: ${resolveLabel(val.values)}`;
      }
      return setSelectedText(newSelectedText);
    }
    setSelectedText("");
  }, [showOperatorFilter, typeFilter, value]);

  function updateValues(value: T | string | null, row: number, key: string) {
    const newValue = {...internalValues[row], [key]: value};
    if (key === "type") newValue.values = valueFilterDefault;
    setInternalValues([
      ...internalValues.slice(0, row),
      newValue,
      ...internalValues.slice(row + 1),
    ]);
  }

  // let children resolve value updates
  function externalResolve(rowIndex: number, key: string) {
    return (val: T | string | null) => updateValues(val, rowIndex, key);
  }

  function addRow() {
    // rows.length should be <= typeFilter.options.length
    if (internalValues.length < typeFilter.options.length) {
      setInternalValues([...internalValues, defaultRowValues]);
    }
  }

  function removeRow(rowIndex: number) {
    if (internalValues.length > 1) {
      setInternalValues([
        ...internalValues.slice(0, rowIndex),
        ...internalValues.slice(rowIndex + 1),
      ]);
    } else {
      // clears filter value using trash icon if there's only one row left
      setInternalValues([defaultRowValues]);
    }
  }

  const validRows = internalValues.filter(v => {
    // remove empty or incomplete rows
    if (!v.type) return false;
    if (Array.isArray(v.values) && v.values.length === 0) return false;
    return true;
  });

  const containsIncompleteInputs = () => {
    const anyOptionSelected = internalValues.find(
      v =>
        Boolean(v.type) ||
        Boolean(Array.isArray(v.values) && v.values.length !== 0)
    );

    if (anyOptionSelected && validRows.length !== internalValues.length) {
      return true;
    }

    return false;
  };

  return (
    <BaseFilter
      label={label}
      size={size}
      selectedText={selectedText}
      disabled={disabled}
      placement={dropdownPlacement}
      noPortal={noPortal}
      onClick={onClick}
    >
      {closePopup => (
        <div className={css(styles.filterDropdown)}>
          {internalValues.map((rowValues, rowIndex) => (
            <div
              className={css(styles.filterRow)}
              /* eslint-disable-next-line react/no-array-index-key */
              key={`${rowValues.type}${rowIndex}`}
            >
              {showOperatorFilter && (
                <div className={css(styles.filterRowItem)}>
                  <SelectFilter
                    isNullable={false}
                    label="Operator"
                    onChange={value =>
                      updateValues(value, rowIndex, "operator")
                    }
                    options={[
                      {label: "Include", value: "include"},
                      {label: "Exclude", value: "exclude"},
                    ]}
                    shyLabel={true}
                    value={rowValues.operator ? rowValues.operator : null}
                  />
                </div>
              )}
              <div className={css(styles.filterRowItem)}>
                <SelectFilter
                  isNullable={false}
                  label={typeFilter.label || "Filter Type"}
                  options={typeFilter.options}
                  onChange={value => updateValues(value, rowIndex, "type")}
                  shyLabel={true}
                  value={rowValues.type}
                />
              </div>
              <div className={css(styles.filterRowItem)}>
                {valueFilterRenderer(
                  rowValues.type,
                  rowValues.values,
                  externalResolve(rowIndex, "values")
                )}
              </div>
              <IconButton
                iconName="trash"
                type="button"
                width="responsive"
                onClick={() => removeRow(rowIndex)}
              />
            </div>
          ))}
          <div className={css(styles.buttonsRow)}>
            <Button
              intent="none"
              kind="bare"
              disabled={disabled}
              onClick={addRow}
            >
              Add another
            </Button>
          </div>
          <div className={css(styles.filterDropdownFooter)}>
            <div className={css(styles.buttonsRow)}>
              <Button
                intent="none"
                kind="bare"
                disabled={disabled}
                onClick={() => setInternalValues([defaultRowValues])}
              >
                Clear
              </Button>
              <Button
                intent="basic"
                kind="bare"
                disabled={disabled || containsIncompleteInputs()}
                onClick={() => {
                  onApply(validRows);
                  closePopup();
                }}
              >
                Apply
              </Button>
            </div>
          </div>
        </div>
      )}
    </BaseFilter>
  );
}

export default ConditionalFilter;

export const styles: {|
  buttonsRow: SheetEntry,
  filterDropdown: SheetEntry,
  filterDropdownFooter: SheetEntry,
  filterRow: SheetEntry,
  filterRowItem: SheetEntry,
|} = StyleSheet.create({
  buttonsRow: {
    display: "flex",
    justifyContent: "space-between",
    margin: "0 -10px", // account for 10px of padding on buttons so they align correctly
  },
  filterDropdown: {
    ...border.a.s,
    backgroundColor: colors.white,
    minHeight: "160px",
    minWidth: "320px",
    padding: whitespaceSizeConstants.l,
  },
  filterDropdownFooter: {
    borderTop: `1px solid ${colors.grey30}`,
    marginTop: whitespaceSizeConstants.l,
    paddingTop: whitespaceSizeConstants.l,
  },
  filterRow: {
    display: "table-row",
  },
  filterRowItem: {
    display: "table-cell",
    paddingRight: whitespaceSizeConstants.s,
    paddingBottom: whitespaceSizeConstants.s,
    ":nth-child(1n) > div > span": {
      minWidth: "100%",
    },
  },
});
