import { AntDesignOutlined } from '@ant-design/icons';
import _ from 'lodash';
import {
  ResourceDefinitions,
  ResourceProps,
  Translate,
  useGetIdentity,
  usePermissions,
  useResourceDefinitions,
  useTranslate
} from 'ra-core';
import { PropsWithChildren, createContext, useContext, useEffect, useMemo, useState } from 'react';
import { useLocation } from 'react-router';

type MenuItem = {
  id: string;
  title: string;
  icon?: React.ReactNode;
  type: 'item' | 'group' | 'collapse';
  url: string;
  resource?: boolean;
  roles?: string[];
  children?: MenuItem[];
  breadcrumbs?: boolean;
};

type MenuGroup = MenuItem & {
  parent?: MenuGroup;
  children?: MenuGroup[];
};
type MenuBreadcrumb = MenuItem & {
  parent?: MenuBreadcrumb;
  last?: boolean;
};

type MenuPermissions = string[] | undefined;

const RESOURCE_PREFIXES: string[] = ['e-', 'entities/'];

function getResourceName(name: string): string {
  let resourceName = name;
  RESOURCE_PREFIXES.forEach((prefix) => {
    resourceName = resourceName?.startsWith(prefix) ? resourceName?.substring(prefix.length) : resourceName;
  });
  return resourceName;
}

function isInGroup(group: MenuGroup, resource: ResourceProps): boolean {
  return resource.options?.group === group.id || (!resource.options?.group && group.id === 'admin');
}

function hasPermission(resource: ResourceProps, permissions: MenuPermissions): boolean {
  const name = getResourceName(resource.name);
  const hasPermission = permissions?.includes(`${name}:list`) || permissions?.includes(`${name}:find`);
  return hasPermission as boolean;
}

function getItem(menu: MenuItem[], id: string): MenuItem | undefined {
  let item = menu.find((item) => item.id === id);
  if (!item) {
    for (let i = 0; i < menu.length; i++) {
      const element = menu[i];
      if (element.children) {
        item = getItem(element.children, id);
        if (item) {
          return item;
        }
      }
    }
  }
  return item;
}

function getGroup(groups: MenuGroup[], resource: ResourceProps, permissions: MenuPermissions): MenuGroup | undefined {
  let group = groups.find(
    (g) =>
      ['group', 'collapse'].indexOf(g.type) !== -1 &&
      isInGroup(g, resource) /* && hasPermission(resource, permissions)*/
  );
  if (!group) {
    for (let i = 0; i < groups.length; i++) {
      const element = groups[i];
      if (element.children) {
        group = getGroup(element.children, resource, permissions);
        if (group) {
          return group;
        }
      }
    }
  }
  return group;
}

function getGroupById(groups: MenuGroup[], id: string): MenuGroup | undefined {
  let group = groups.find((g) => g.id === id);
  if (!group) {
    for (let i = 0; i < groups.length; i++) {
      const element = groups[i];
      if (element.children) {
        group = getGroupById(element.children, id);
        if (group) {
          return group;
        }
      }
    }
  }
  return group;
}
function filterNotEmpty(group: MenuGroup): boolean {
  if (!group) {
    return false;
  }
  if (group.type === 'item') {
    return true;
  }
  if (group.children) {
    group.children = group.children.filter(filterNotEmpty);
  }
  const match = group.children && group.children.length > 0;
  return match as boolean;
}

function mapParent(group: MenuGroup): MenuGroup {
  if (group.children) {
    group.children.forEach((child: MenuGroup) => {
      child.parent = group;
      mapParent(child);
    });
  }
  return group;
}

function build(
  userMenu: MenuItem[],
  resources: ResourceDefinitions<any>,
  permissions: MenuPermissions,
  translate: Translate,
  roles: string[]
): MenuGroup[] {
  function translateRecursive(group: MenuGroup) {
    group.title = translate(group.title, { _: group.title });

    if (group.children) {
      group.children.forEach((child) => translateRecursive(child));
    }
    return group;
  }
  const resourceNames = Object.keys(resources);

  function removeNotFoundResources(group: MenuGroup, resources: string[]): MenuGroup {
    if (group.children) {
      group.children = group.children.filter((child) => {
        if (child.resource === false) {
          const childRoles = child.roles || [];
          if (childRoles.length === 0 || childRoles.some((role) => roles.includes(role))) {
            return true;
          }
          return false;
        }
        if (child.type === 'item') {
          return resources.includes(child.id);
        }
        removeNotFoundResources(child, resources);
        return child.children && child.children.length > 0;
      });
    }
    return group;
  }

  const groupsA = _.cloneDeep(userMenu)
    .map((group) => ({
      children: [],
      ...group,
      type: group.type || 'group'
    }))
    .map(translateRecursive)
    .map((group) => removeNotFoundResources(group, resourceNames));

  const groupsB = Object.values(resources)
    // Detect group of each configured ra resource.
    .map((resource) => resource.options?.group || 'admin')
    // Generate unique list of groups (because a resource can be configured with the same group of another resource)
    .filter((group, index, self) => self.indexOf(group) === index)
    // Remove from the list groups already defined by the user (Applica developer)
    .filter((group) => group !== undefined && getGroupById(userMenu, group) === null)
    .map((group) => ({
      id: group,
      title: translate(`ra.menu.group.${group}`),
      type: 'group',
      icon: AntDesignOutlined,
      children: []
    }));

  const groups = [...groupsA, ...groupsB] as MenuGroup[];

  Object.values(resources).forEach((resource: ResourceProps) => {
    const resourceGroup = getGroup(groups, resource, permissions);
    const resourceMenu = resourceGroup?.children?.find((c) => c.id === resource.name);
    const resourceExists = resourceGroup?.children?.some((c) => c.id === resource.name);
    const resourceRoles = resourceMenu?.roles || resource.options?.roles || [];
    const hasRoles =
      !resourceRoles || resourceRoles.length === 0 || resourceRoles.some((role: string) => roles.includes(role));
    const hasPerms = hasPermission(resource, permissions);
    if (!hasPerms || !!resource.list || !hasRoles) {
      if (resourceExists && !!resourceGroup && !!resourceGroup.children && resourceGroup?.children?.length > 0) {
        resourceGroup.children = resourceGroup.children.filter((c) => c.id !== resource.name);
      }
      return;
    }
    if (resourceGroup) {
      if (!resourceGroup.children) {
        resourceGroup.children = [];
      }
      if (resourceExists) {
        return;
      }
    }
  });

  return groups.filter(filterNotEmpty).map(mapParent);
}

type MenuProviderContextProps = {
  openItem: string[];
  openComponent: string;
  selectedID: string | null;
  drawerOpen: boolean;
  error: any;
  menu: MenuItem[];
  activeItem: (openItem: string[]) => void;
  openDrawer: (drawerOpen: boolean) => void;
  activeID: (selectedID: string | null) => void;
  hasError: (error: any) => void;
  setMenu: (menu: MenuItem[]) => void;
};

function notImplemented() {
  throw new Error('Not implemented');
}

const initialState: MenuProviderContextProps = {
  openItem: [],
  openComponent: 'buttons',
  selectedID: null,
  drawerOpen: false,
  error: null,
  menu: [],
  activeItem: notImplemented,
  openDrawer: notImplemented,
  activeID: notImplemented,
  hasError: notImplemented,
  setMenu: notImplemented
};

type MenuProviderProps = PropsWithChildren<{
  menu: MenuItem[];
}>;
const MenuContext = createContext<MenuProviderContextProps>(initialState);

function MenuConfigProvider({ menu, children }: MenuProviderProps) {
  const [menuConfig, setMenuConfig] = useState<MenuProviderContextProps>({ ...initialState, menu }) as any;
  function setMenu(menu: MenuItem[]) {
    setMenuConfig((menuConfig: MenuProviderContextProps) => ({ ...menuConfig, menu }));
  }
  function activeItem(openItem: string) {
    setMenuConfig((menuConfig: MenuProviderContextProps) => ({ ...menuConfig, openItem }));
  }
  function activeID(selectedID: string) {
    setMenuConfig((menuConfig: MenuProviderContextProps) => ({ ...menuConfig, selectedID }));
  }
  function openDrawer(drawerOpen: boolean) {
    setMenuConfig((menuConfig: MenuProviderContextProps) => ({ ...menuConfig, drawerOpen }));
  }
  function hasError(error: any) {
    setMenuConfig((menuConfig: MenuProviderContextProps) => ({ ...menuConfig, error }));
  }

  useEffect(() => {
    setMenu(menu);
  }, [menu]);

  return (
    <MenuContext.Provider
      value={{
        ...menuConfig,
        setMenu,
        activeItem,
        openDrawer,
        activeID,
        hasError
      }}
    >
      {children}
    </MenuContext.Provider>
  );
}
function useMenuContext(): MenuProviderContextProps {
  return useContext(MenuContext);
}

type UseMenuProps = {
  menu: MenuGroup[] | null;
  isLoading: boolean;
  getItem: (id: string) => MenuItem | undefined;
  findInMenu: (menu: MenuGroup[], id: string) => MenuItem | undefined;
};
function useMenu(): UseMenuProps {
  const resources = useResourceDefinitions();
  const { isLoading: loadingIdentity, data: identity } = useGetIdentity();
  const isAuthenticated = !!identity && !!identity.id;

  const { permissions } = usePermissions(isAuthenticated);
  const { menu: userMenu } = useMenuContext();
  const translate = useTranslate();
  const menu = useMemo(() => {
    if (loadingIdentity || !identity) {
      return null;
    }
    const roles = ((identity?.roles || []) as { id: string }[]).map((role) => role.id);
    const groups = build(userMenu, resources, permissions, translate, roles);
    return groups;
  }, [loadingIdentity, identity, resources, permissions, translate, userMenu]);

  const isLoading = useMemo(() => loadingIdentity, [loadingIdentity]);

  function getMenuGroup(id: string): MenuGroup | undefined {
    if (!menu) {
      return undefined;
    }
    return getItem(menu, id);
  }

  return {
    menu,
    isLoading,
    getItem: getMenuGroup,
    findInMenu: isLoading ? () => undefined : getItem
  };
}

function useBreadcrumbs() {
  const translate = useTranslate();
  const { isLoading, menu: _menu, findInMenu } = useMenu();
  const { pathname } = useLocation();

  return useMemo(() => {
    const pathArgs = pathname.split('/').filter((x) => x);
    const menu = _.cloneDeep(_menu);
    let id = pathname;
    let menuItem = findInMenu(menu!, pathname);

    if (!menuItem || menuItem.type === 'collapse' || menuItem.type === 'group') {
      menuItem = findInMenu(menu!, pathArgs[pathArgs.length - 1]);
      id = menuItem ? menuItem.id : id;
    }
    if (!menuItem || menuItem.type === 'collapse' || menuItem.type === 'group') {
      for (let i = pathArgs.length; i >= 0; i--) {
        const path = pathArgs.slice(0, i).join('/');
        menuItem = findInMenu(menu!, path);
        if (menuItem) {
          id = menuItem.id;
          break;
        }
      }
    }

    const urlParts = menuItem?.url?.split('/');
    const urlPartsLength = urlParts?.length || 0;
    if (pathArgs.length >= urlPartsLength) {
      const data = pathArgs[pathArgs.length - 1];
      if (menuItem) {
        const context = data === 'create' || pathname === menuItem.url ? 'create' : 'edit';
        const title = `resources.${id}.breadcrumbs.${context}`;
        const child: MenuBreadcrumb = {
          id: `${id}-${context}`,
          type: 'item',
          title: translate(title, { _: translate(title, { _: title }) }),
          url: pathname,
          breadcrumbs: true,
          parent: menuItem
        };
        menuItem.type = 'collapse';
        menuItem.breadcrumbs = true;
        menuItem.children = menuItem.children || [];
        menuItem.children.push(child);
        menuItem = child;
      }
    }

    let breadcrumbs: MenuBreadcrumb[] = [];
    if (menuItem) {
      let current: MenuBreadcrumb = { ...menuItem, last: true };
      while (current) {
        breadcrumbs.push(current);
        current = current.parent as MenuBreadcrumb;
      }
      breadcrumbs = breadcrumbs.reverse();
    }

    return { isLoading, navigation: { items: menu }, breadcrumbs };
  }, [isLoading, pathname, _menu, findInMenu, translate]);
}

function useMenuConfig(): MenuProviderContextProps {
  return useMenuContext();
}
export type {
  MenuBreadcrumb,
  MenuGroup,
  MenuItem,
  MenuPermissions,
  MenuProviderContextProps,
  MenuProviderProps,
  UseMenuProps
};
export { MenuConfigProvider, useBreadcrumbs, useMenu, useMenuConfig, useMenuContext };
