import {
  FC,
  Ref,
  useEffect,
  useRef,
  useState,
  useCallback,
  ReactNode,
  CSSProperties,
} from "react";

import { action, autorun } from "mobx";
import { observer } from "mobx-react-lite";

import { useFloating, shift, flip, offset, size } from "@floating-ui/react-dom";

import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faHistory } from "@fortawesome/free-solid-svg-icons/faHistory";
import { faCaretDown } from "@fortawesome/free-solid-svg-icons/faCaretDown";
import { faSave } from "@fortawesome/free-regular-svg-icons/faSave";
import { faUndoAlt } from "@fortawesome/free-solid-svg-icons/faUndoAlt";
import { faTrash } from "@fortawesome/free-solid-svg-icons/faTrash";

import type { AlertStore, FilterT } from "Stores/AlertStore";
import type { Settings } from "Stores/Settings";
import { IsMobile } from "Common/Device";
import { localStored } from "Common/LocalStore";
import { DropdownSlide } from "Components/Animations/DropdownSlide";
import HistoryLabel from "Components/Labels/HistoryLabel";
import { useOnClickOutside } from "Hooks/useOnClickOutside";

interface ReduceFilterT {
  raw: string;
  name: string;
  matcher: string;
  value: string;
}

// takes a filter object out of alertStore.history.values and creates a new
// object with only those keys that will be stored in history
function ReduceFilter(filter: FilterT): ReduceFilterT {
  return {
    raw: filter.raw,
    name: filter.name,
    matcher: filter.matcher,
    value: filter.value,
  };
}

const ActionButton: FC<{
  colorClass: string;
  icon: IconDefinition;
  title: ReactNode;
  action: () => void;
  afterClick: () => void;
}> = ({ colorClass, icon, title, action, afterClick }) => (
  <button
    className={`component-history-button btn btn-sm ${colorClass}`}
    onClick={() => {
      action();
      afterClick();
    }}
  >
    <FontAwesomeIcon icon={icon} className="me-1" />
    {title}
  </button>
);

const HistoryMenu: FC<{
  x: number | null;
  y: number | null;
  floating: Ref<HTMLDivElement> | null;
  strategy: CSSProperties["position"];
  maxHeight: number | null;
  filters: ReduceFilterT[][];
  alertStore: AlertStore;
  settingsStore: Settings;
  afterClick: () => void;
  onClear: () => void;
}> = ({
  x,
  y,
  floating,
  strategy,
  maxHeight,
  filters,
  alertStore,
  settingsStore,
  afterClick,
  onClear,
}) => {
  const maxItems = IsMobile() ? 4 : 8;

  return (
    <div
      className="dropdown-menu d-block shadow components-navbar-historymenu m-0"
      ref={floating}
      style={{
        position: strategy,
        top: y ?? "",
        left: x ?? "",
        maxHeight: maxHeight ?? "",
      }}
    >
      <h6 className="dropdown-header text-center">
        <FontAwesomeIcon icon={faHistory} className="me-1" />
        Last used filters
      </h6>
      {filters.length === 0 ? (
        <h6 className="dropdown-header text-muted text-center">Empty</h6>
      ) : (
        filters.slice(0, maxItems).map((historyFilters, index) => (
          <button
            className="dropdown-item cursor-pointer px-3"
            key={`${index}/${historyFilters.length}`}
            onClick={() => {
              alertStore.filters.setFilters(historyFilters.map((f) => f.raw));
              afterClick();
            }}
          >
            <div className="components-navbar-historymenu-labels ps-2">
              {historyFilters.map((f) => (
                <HistoryLabel
                  key={f.raw}
                  alertStore={alertStore}
                  name={f.name}
                  matcher={f.matcher}
                  value={f.value}
                />
              ))}
            </div>
          </button>
        ))
      )}
      <div className="dropdown-divider" />
      <div className="px-3 d-flex justify-content-center flex-wrap">
        <ActionButton
          colorClass="btn-success"
          icon={faSave}
          title="Save filters"
          action={() => {
            settingsStore.savedFilters.save(
              alertStore.filters.values.map((f) => f.raw),
            );
          }}
          afterClick={afterClick}
        />
        <ActionButton
          colorClass="btn-danger"
          icon={faUndoAlt}
          title="Reset filters"
          action={settingsStore.savedFilters.clear}
          afterClick={afterClick}
        />
        <ActionButton
          colorClass="btn-secondary"
          icon={faTrash}
          title="Clear history"
          action={onClear}
          afterClick={afterClick}
        />
      </div>
    </div>
  );
};

interface HistoryStorageT {
  filters: ReduceFilterT[][];
}

class HistoryStorage {
  private store = localStored<HistoryStorageT>("filters", {
    filters: [] as ReduceFilterT[][],
  });

  get config(): HistoryStorageT {
    return this.store.value;
  }

  setFilters = action((newFilters: ReduceFilterT[][]) => {
    this.store.value.filters = newFilters;
  });

  destroy = () => {
    this.store.destroy();
  };
}

const History: FC<{
  alertStore: AlertStore;
  settingsStore: Settings;
}> = observer(({ alertStore, settingsStore }) => {
  const [history] = useState<HistoryStorage>(() => new HistoryStorage());
  const [isVisible, setIsVisible] = useState<boolean>(false);
  const [maxHeight, setMaxHeight] = useState<number | null>(null);
  const hide = useCallback(() => setIsVisible(false), []);
  const toggle = useCallback(() => setIsVisible(!isVisible), [isVisible]);

  const { x, y, refs, strategy } = useFloating({
    placement: "bottom-end",
    middleware: [
      shift(),
      flip(),
      offset(5),
      size({
        apply({ availableHeight }) {
          setMaxHeight(availableHeight);
        },
      }),
    ],
  });

  // every time this component updates we will rewrite history
  // (if there are changes)
  useEffect(() => {
    const disposeAutorun = autorun(() => {
      // we don't store unapplied (we only have raw text for those, we need
      // name & value for coloring) or invalid filters
      // also check for value, name might be missing for fuzzy filters, but
      // the value should always be set
      const validAppliedFilters = alertStore.filters.values
        .filter((f) => f.applied && f.isValid && f.value)
        .map((f) => ReduceFilter(f));

      // don't store empty filters in history
      if (validAppliedFilters.length === 0) return;
      // make a JSON dump for comparing later with what's already stored
      const filtersJSON = JSON.stringify(validAppliedFilters);

      // don't rewrite history if filters haven't changed
      if (history.config.filters.some((f) => JSON.stringify(f) === filtersJSON))
        return;

      const newHistory = [
        ...[validAppliedFilters],
        ...history.config.filters,
      ].slice(0, 8);
      history.setFilters(newHistory);
    });
    return () => {
      disposeAutorun();
      history.destroy();
    };
  }, []);

  const ref = useRef<HTMLSpanElement | null>(null);
  useOnClickOutside(ref, hide, isVisible);

  return (
    // data-filters is there to register filters for observation in mobx
    // it needs to be using full filter object to notice changes to
    // name & value but ignore hits
    // using it this way will force re-render on every change, which is
    // needed to keep track of all filter changes
    <span
      ref={ref}
      className="input-group-text border-0 rounded-0 bg-inherit px-0"
    >
      <button
        ref={refs.setReference}
        onClick={toggle}
        className="btn border-0 rounded-0 bg-inherit cursor-pointer components-navbar-history px-2 py-0 components-navbar-icon"
        type="button"
        data-toggle="dropdown"
        aria-haspopup="true"
        aria-expanded="true"
      >
        <FontAwesomeIcon icon={faCaretDown} />
      </button>
      <DropdownSlide in={isVisible} unmountOnExit>
        <HistoryMenu
          filters={history.config.filters}
          onClear={() => {
            history.setFilters([]);
          }}
          alertStore={alertStore}
          settingsStore={settingsStore}
          afterClick={hide}
          x={x}
          y={y}
          floating={refs.setFloating}
          strategy={strategy}
          maxHeight={maxHeight}
        />
      </DropdownSlide>
    </span>
  );
});

export { History, HistoryMenu, ReduceFilter };
