import type {
  GroupFilter,
  QueryFilter,
  SingleFilter,
} from "@brevity-builder/models";
import {
  isGroupFilter,
  isLegacyFilter,
  isSingleFilter,
} from "@brevity-builder/models";
import * as React from "react";
import { nanoid } from "../utils";

const EMPTY_GROUP: GroupFilter = {
  __type: "filterGroup",
  id: "empty",
  children: [],
  logicalOperator: "AND",
};

export function useDynamicFilter(
  initialValue: GroupFilter | SingleFilter | QueryFilter = EMPTY_GROUP,
  onChange?: (state?: GroupFilter) => void,
) {
  const prepare = React.useCallback(
    (value: GroupFilter | SingleFilter | QueryFilter) => {
      if (isLegacyFilter(value)) {
        value = filterToGroup(value);
      }
      if (isGroupFilter(value)) {
        return value;
      }
      if (!isSingleFilter(value)) {
        console.error("Invalid initialValue for filter group:", value);
        return EMPTY_GROUP;
      }
      return {
        __type: "filterGroup",
        id: nanoid(),
        children: [value],
        logicalOperator: "AND",
      } as GroupFilter;
    },
    [],
  );

  const defaultProp = React.useMemo(() => {
    if (!initialValue) return EMPTY_GROUP;
    let value = initialValue;
    return prepare(value);
  }, []);

  const [group, _setGroup] = React.useState<GroupFilter>(defaultProp);
  const setGroup = React.useCallback(
    ((valueOrCallback) => {
      if (typeof valueOrCallback === "function") {
        _setGroup((prev) => {
          const value = valueOrCallback(prev);
          onChange?.(value);
          return value;
        });
        return;
      }

      _setGroup(valueOrCallback);
      onChange?.(valueOrCallback);
    }) as typeof _setGroup,
    [_setGroup],
  );

  const clear = React.useCallback(() => setGroup(EMPTY_GROUP), []);

  const add = React.useCallback((filter: SingleFilter | GroupFilter) => {
    if (!filter) return;
    setGroup((group = EMPTY_GROUP) => {
      return {
        ...group,
        children: [...group.children, maybeReplaceIds(filter)],
      } as GroupFilter;
    });
  }, []);

  const remove = React.useCallback((old: SingleFilter | GroupFilter) => {
    if (!old) return;
    setGroup((group = EMPTY_GROUP) => {
      return {
        ...group,
        children: group.children.filter((f) => f.id !== old.id),
      };
    });
  }, []);

  const replace = React.useCallback(
    (old: SingleFilter | GroupFilter, filter: SingleFilter | GroupFilter) => {
      if (!filter) return;
      setGroup((group = EMPTY_GROUP) => {
        const index = group.children.findIndex((f) => f.id === old.id);
        if (index === -1) return group;
        const newFilters = [...group.children];
        newFilters.splice(
          index,
          1,
          filter.id ? filter : (maybeReplaceIds(filter) as SingleFilter),
        );
        return {
          ...group,
          children: newFilters,
        };
      });
    },
    [],
  );

  const setAnd = React.useCallback(() => {
    setGroup((group = EMPTY_GROUP) => {
      return {
        ...group,
        logicalOperator: "AND",
      };
    });
  }, []);

  const setOr = React.useCallback(() => {
    setGroup((group = EMPTY_GROUP) => {
      return {
        ...group,
        logicalOperator: "OR",
      };
    });
  }, []);

  const toggleOperator = React.useCallback(() => {
    setGroup((group = EMPTY_GROUP) => {
      return {
        ...group,
        logicalOperator: group.logicalOperator === "OR" ? "AND" : "OR",
      };
    });
  }, []);

  const imp = React.useCallback((children: any, logicalOperator: any) => {
    _setGroup({
      children,
      logicalOperator,
    } as GroupFilter);
  }, []);

  const methods = React.useMemo(
    () => ({
      clear,
      add,
      remove,
      replace,
      setAnd,
      setOr,
      toggleOperator,
      import: imp,
    }),
    [clear, add, remove, replace, setAnd, setOr, toggleOperator, imp],
  );

  return [
    group?.children || [],
    group?.logicalOperator || "AND",
    methods,
  ] as const;
}

export function maybeReplaceIds<T extends { id: string }>(
  filter: PartialBy<T, "id">,
): T {
  if (!filter) return filter;
  if (isSingleFilter(filter as unknown as SingleFilter)) {
    return {
      ...filter,
      id: filter.id ?? nanoid(),
    } as T;
  }
  if (isGroupFilter(filter as unknown as GroupFilter)) {
    return {
      ...filter,
      id: filter.id ?? nanoid(),
      children: (filter as unknown as GroupFilter).children.map((c) =>
        maybeReplaceIds(c),
      ),
    } as unknown as T;
  }
  return {
    ...filter,
    id: filter.id ?? nanoid(),
  } as T;
}

export function filterToGroup(
  filter?: QueryFilter,
): GroupFilter | SingleFilter {
  if (!filter) {
    return EMPTY_GROUP;
  }
  const { children, logicalOperator, ...newFilter } = filter;
  if (children?.length) {
    return {
      __type: "filterGroup",
      id: nanoid(),
      children: [newFilter, ...children.map(filterToGroup)],
      logicalOperator: logicalOperator ?? "AND",
    } as GroupFilter;
  }
  return {
    ...(newFilter as unknown as SingleFilter),
    __type: "filter",
  } as SingleFilter;
}

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
