import {
  DataTypes,
  IdDefaultType,
  Keyboard,
  LabelDefaultType,
  ListType
} from "@etsoo/shared";
import React from "react";
import { Utils as SharedUtils } from "@etsoo/shared";
import { ReactUtils } from "@etsoo/react";
import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank";
import CheckBoxIcon from "@mui/icons-material/CheckBox";
import type { AutocompleteExtendedProps } from "./AutocompleteExtendedProps";
import { SearchField } from "./SearchField";
import { InputField } from "./InputField";
import { useAppContext } from "./app/ReactApp";
import Checkbox from "@mui/material/Checkbox";
import Autocomplete, {
  AutocompleteRenderInputParams
} from "@mui/material/Autocomplete";

const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
const checkedIcon = <CheckBoxIcon fontSize="small" />;

/**
 * ComboBox multiple props
 */
export type ComboBoxMultipleProps<
  T extends object = ListType,
  D extends DataTypes.Keys<T> = IdDefaultType<T>,
  L extends DataTypes.Keys<T, string> = LabelDefaultType<T>
> = Omit<AutocompleteExtendedProps<T, D, true>, "onValueChange"> & {
  /**
   * Auto add blank item
   */
  autoAddBlankItem?: boolean;

  /**
   * Data readonly
   */
  dataReadonly?: boolean;

  /**
   * Label field
   */
  labelField?: L;

  /**
   * Load data callback
   */
  loadData?: () => PromiseLike<T[] | null | undefined>;

  /**
   * On load data handler
   */
  onLoadData?: (options: T[]) => void;

  /**
   * Array of options.
   */
  options?: ReadonlyArray<T>;

  /**
   * Id values
   */
  idValues?: T[D][];
};

/**
 * ComboBox multiple
 * @param props Props
 * @returns Component
 */
export function ComboBoxMultiple<
  T extends object = ListType,
  D extends DataTypes.Keys<T> = IdDefaultType<T>,
  L extends DataTypes.Keys<T, string> = LabelDefaultType<T>
>(props: ComboBoxMultipleProps<T, D, L>) {
  // Global app
  const app = useAppContext();

  // Labels
  const labels = app?.getLabels("noOptions", "loading");

  // Destruct
  const {
    search = false,
    autoAddBlankItem = search,
    idField = "id" as D,
    idValue,
    idValues,
    inputError,
    inputHelperText,
    inputMargin,
    inputOnChange,
    inputRequired,
    inputReset,
    inputVariant,
    defaultValue,
    label,
    labelField = "label" as L,
    loadData,
    onLoadData,
    name,
    inputAutoComplete = "off",
    options,
    dataReadonly = true,
    readOnly,
    onChange,
    openOnFocus = true,
    value,
    disableCloseOnSelect = true,
    renderOption = ({ key, ...restProps }, option, { selected }) => (
      <li key={key} {...restProps}>
        <Checkbox
          icon={icon}
          checkedIcon={checkedIcon}
          style={{ marginRight: 8 }}
          checked={selected}
        />
        {`${option[labelField]}`}
      </li>
    ),
    getOptionLabel = (option: T) => `${option[labelField]}`,
    getOptionKey = (option: T) => `${option[idField]}`,
    sx = { minWidth: "150px" },
    noOptionsText = labels?.noOptions,
    loadingText = labels?.loading,
    disabled,
    ...rest
  } = props;

  // Value input ref
  const inputRef = React.createRef<HTMLInputElement>();
  const localRef = React.useRef<HTMLInputElement>(undefined);

  // Options state
  const [localOptions, setOptions] = React.useState(options ?? []);
  const isMounted = React.useRef(true);

  const propertyWay = loadData == null;
  React.useEffect(() => {
    if (propertyWay && options != null) {
      setOptions(options);
    }
  }, [options, propertyWay]);

  // State
  // null for controlled
  const [stateValue, setStateValue] = React.useState<T[] | null>(null);

  const selectedCount = stateValue?.length ?? 0;
  React.useEffect(() => {
    if (localRef.current && inputRequired)
      localRef.current.required = selectedCount === 0;
  }, [inputRequired, selectedCount]);

  React.useEffect(() => {
    const localValue: T[] | null | undefined =
      idValue != null
        ? localOptions.filter((o) => o[idField] === idValue)
        : idValues != null
          ? localOptions.filter((o) => idValues?.includes(o[idField]))
          : (defaultValue?.concat() ?? value?.concat());

    setStateValue(localValue ?? []);
  }, [localOptions, idField, idValue, idValues, defaultValue, value]);

  // Add readOnly
  const addReadOnly = (params: AutocompleteRenderInputParams) => {
    if (readOnly != null) {
      Object.assign(params, { readOnly });
    }

    Object.assign(params.inputProps, { "data-reset": inputReset });

    if (dataReadonly) {
      params.inputProps.onKeyDown = (event) => {
        if (Keyboard.isTypingContent(event.key)) {
          event.preventDefault();
        }
      };
    }

    // https://stackoverflow.com/questions/15738259/disabling-chrome-autofill
    // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html
    Object.assign(params.inputProps, { autoComplete: inputAutoComplete });

    return params;
  };

  const getValue = (value: T[] | null): string => {
    if (value == null) return "";
    return value.map((item) => item[idField]).join(",");
  };

  const setInputValue = (value: T[] | null) => {
    // Set state
    setStateValue(value);

    // Input value
    const input = inputRef.current;
    if (input) {
      // Update value
      const newValue = getValue(value);

      if (newValue !== input.value) {
        // Different value, trigger change event
        ReactUtils.triggerChange(input, newValue, false);
      }
    }
  };

  React.useEffect(() => {
    if (propertyWay || loadData == null) return;
    loadData().then((result) => {
      if (result == null || !isMounted.current) return;
      if (onLoadData) onLoadData(result);
      if (autoAddBlankItem) {
        SharedUtils.addBlankItem(result, idField, labelField);
      }
      setOptions(result);
    });
  }, [propertyWay, autoAddBlankItem, idField, labelField]);

  React.useEffect(() => {
    return () => {
      isMounted.current = false;
    };
  }, []);

  // Layout
  return (
    <div style={{ flex: 2 }}>
      <input
        ref={inputRef}
        data-reset={inputReset ?? true}
        type="text"
        style={{ display: "none" }}
        name={name}
        value={getValue(stateValue)}
        readOnly
        onChange={inputOnChange}
        disabled={disabled}
      />
      {/* Previous input will reset first with "disableClearable = false", next input trigger change works */}
      <Autocomplete<T, true, false, false>
        value={
          stateValue == null
            ? []
            : Array.isArray(stateValue)
              ? stateValue
              : [stateValue]
        }
        disabled={disabled}
        disableCloseOnSelect={disableCloseOnSelect}
        getOptionLabel={getOptionLabel}
        getOptionKey={getOptionKey}
        multiple
        isOptionEqualToValue={(option: T, value: T) =>
          option[idField] === value[idField]
        }
        onChange={(event, value, reason, details) => {
          // Set value
          setInputValue(value.concat());

          // Custom
          if (onChange != null) onChange(event, value, reason, details);
        }}
        openOnFocus={openOnFocus}
        sx={sx}
        renderInput={(params) =>
          search ? (
            <SearchField
              {...addReadOnly(params)}
              label={label}
              name={name + "Input"}
              margin={inputMargin}
              variant={inputVariant}
              required={inputRequired}
              error={inputError}
              helperText={inputHelperText}
              inputRef={localRef}
            />
          ) : (
            <InputField
              {...addReadOnly(params)}
              label={label}
              name={name + "Input"}
              margin={inputMargin}
              variant={inputVariant}
              required={inputRequired}
              error={inputError}
              helperText={inputHelperText}
              inputRef={localRef}
            />
          )
        }
        options={localOptions}
        renderOption={renderOption}
        noOptionsText={noOptionsText}
        loadingText={loadingText}
        {...rest}
      />
    </div>
  );
}
