import { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react';
import { MatrixClient } from 'matrix-js-sdk';
import { AccountDataEvent } from '../../types/matrix/accountData';
import { useMatrixClient } from './useMatrixClient';
import { getAccountData, isSpace } from '../utils/room';
import { Membership } from '../../types/matrix/room';
import { useAccountDataCallback } from './useAccountDataCallback';

export type ISidebarFolder = {
  name?: string;
  id: string;
  content: string[];
};
export type TSidebarItem = string | ISidebarFolder;
export type SidebarItems = Array<TSidebarItem>;

export type InCinnySpacesContent = {
  shortcut?: string[];
  sidebar?: SidebarItems;
  hidden?: string[];
  hideHome?: boolean;
};

export const parseSidebar = (
  mx: MatrixClient,
  orphanSpaces: string[],
  content?: InCinnySpacesContent
) => {
  const sidebar = content?.sidebar ?? content?.shortcut ?? [];
  const hiddenSet = new Set(content?.hidden ?? []);
  const orphans = new Set(orphanSpaces);

  const items: SidebarItems = [];

  const safeToAdd = (spaceId: string): boolean => {
    if (typeof spaceId !== 'string') return false;
    if (hiddenSet.has(spaceId)) return false;
    const space = mx.getRoom(spaceId);
    if (space?.getMyMembership() !== Membership.Join) return false;
    return isSpace(space);
  };

  sidebar.forEach((item) => {
    if (typeof item === 'string') {
      if (safeToAdd(item) && !items.includes(item)) {
        orphans.delete(item);
        items.push(item);
      }
      return;
    }
    if (
      typeof item === 'object' &&
      typeof item.id === 'string' &&
      Array.isArray(item.content) &&
      !items.find((i) => (typeof i === 'string' ? false : i.id === item.id))
    ) {
      const safeContent = item.content.filter(safeToAdd);
      safeContent.forEach((i) => orphans.delete(i));
      if (safeContent.length > 0) {
        items.push({
          ...item,
          content: Array.from(new Set(safeContent)),
        });
      }
    }
  });

  orphans.forEach((spaceId) => {
    if (!hiddenSet.has(spaceId)) {
      items.push(spaceId);
    }
  });
  return items;
};

/**
 * Parse hidden spaces from account data
 */
export const parseHiddenSpaces = (
  mx: MatrixClient,
  content?: InCinnySpacesContent
): string[] => {
  const hidden = content?.hidden ?? [];
  return hidden.filter((spaceId) => {
    if (typeof spaceId !== 'string') return false;
    const space = mx.getRoom(spaceId);
    if (space?.getMyMembership() !== Membership.Join) return false;
    return isSpace(space);
  });
};

export const useSidebarItems = (
  orphanSpaces: string[]
): [SidebarItems, Dispatch<SetStateAction<SidebarItems>>] => {
  const mx = useMatrixClient();

  const [sidebarItems, setSidebarItems] = useState(() => {
    const inCinnySpacesContent = getAccountData(
      mx,
      AccountDataEvent.CinnySpaces
    )?.getContent<InCinnySpacesContent>();
    return parseSidebar(mx, orphanSpaces, inCinnySpacesContent);
  });

  useEffect(() => {
    const inCinnySpacesContent = getAccountData(
      mx,
      AccountDataEvent.CinnySpaces
    )?.getContent<InCinnySpacesContent>();
    setSidebarItems(parseSidebar(mx, orphanSpaces, inCinnySpacesContent));
  }, [mx, orphanSpaces]);

  useAccountDataCallback(
    mx,
    useCallback(
      (mEvent) => {
        if (mEvent.getType() === AccountDataEvent.CinnySpaces) {
          const newContent = mEvent.getContent<InCinnySpacesContent>();
          setSidebarItems(parseSidebar(mx, orphanSpaces, newContent));
        }
      },
      [mx, orphanSpaces]
    )
  );

  return [sidebarItems, setSidebarItems];
};

export const sidebarItemWithout = (items: SidebarItems, roomId: string) => {
  const newItems: SidebarItems = items
    .map((item) => {
      if (typeof item === 'string') {
        if (item === roomId) return null;
        return item;
      }
      if (item.content.includes(roomId)) {
        const newContent = item.content.filter((id) => id !== roomId);
        if (newContent.length === 0) return null;
        return {
          ...item,
          content: newContent,
        };
      }
      return item;
    })
    .filter((item) => item !== null) as SidebarItems;

  return newItems;
};

export const makeCinnySpacesContent = (
  mx: MatrixClient,
  items: SidebarItems
): InCinnySpacesContent => {
  const currentInSpaces =
    getAccountData(mx, AccountDataEvent.CinnySpaces)?.getContent<InCinnySpacesContent>() ?? {};

  const newSpacesContent: InCinnySpacesContent = {
    ...currentInSpaces,
    sidebar: items,
  };

  return newSpacesContent;
};

/**
 * Hook to get and manage hidden spaces
 */
export const useHiddenSpaces = (): [string[], Dispatch<SetStateAction<string[]>>] => {
  const mx = useMatrixClient();

  const [hiddenSpaces, setHiddenSpaces] = useState(() => {
    const content = getAccountData(
      mx,
      AccountDataEvent.CinnySpaces
    )?.getContent<InCinnySpacesContent>();
    return parseHiddenSpaces(mx, content);
  });

  useEffect(() => {
    const content = getAccountData(
      mx,
      AccountDataEvent.CinnySpaces
    )?.getContent<InCinnySpacesContent>();
    setHiddenSpaces(parseHiddenSpaces(mx, content));
  }, [mx]);

  useAccountDataCallback(
    mx,
    useCallback(
      (mEvent) => {
        if (mEvent.getType() === AccountDataEvent.CinnySpaces) {
          const newContent = mEvent.getContent<InCinnySpacesContent>();
          setHiddenSpaces(parseHiddenSpaces(mx, newContent));
        }
      },
      [mx]
    )
  );

  return [hiddenSpaces, setHiddenSpaces];
};

/**
 * Create content for hiding a space
 */
export const makeHideSpaceContent = (
  mx: MatrixClient,
  spaceId: string
): InCinnySpacesContent => {
  const currentContent =
    getAccountData(mx, AccountDataEvent.CinnySpaces)?.getContent<InCinnySpacesContent>() ?? {};

  const currentHidden = currentContent.hidden ?? [];
  if (currentHidden.includes(spaceId)) {
    return currentContent;
  }

  return {
    ...currentContent,
    hidden: [...currentHidden, spaceId],
  };
};

/**
 * Create content for unhiding a space
 */
export const makeUnhideSpaceContent = (
  mx: MatrixClient,
  spaceId: string
): InCinnySpacesContent => {
  const currentContent =
    getAccountData(mx, AccountDataEvent.CinnySpaces)?.getContent<InCinnySpacesContent>() ?? {};

  const currentHidden = currentContent.hidden ?? [];

  return {
    ...currentContent,
    hidden: currentHidden.filter((id) => id !== spaceId),
  };
};

/**
 * Create content for hiding home
 */
export const makeHideHomeContent = (mx: MatrixClient): InCinnySpacesContent => {
  const currentContent =
    getAccountData(mx, AccountDataEvent.CinnySpaces)?.getContent<InCinnySpacesContent>() ?? {};

  return {
    ...currentContent,
    hideHome: true,
  };
};

/**
 * Create content for unhiding home
 */
export const makeUnhideHomeContent = (mx: MatrixClient): InCinnySpacesContent => {
  const currentContent =
    getAccountData(mx, AccountDataEvent.CinnySpaces)?.getContent<InCinnySpacesContent>() ?? {};

  return {
    ...currentContent,
    hideHome: false,
  };
};

/**
 * Hook to get and manage home hidden state
 */
export const useHomeHidden = (): [boolean, Dispatch<SetStateAction<boolean>>] => {
  const mx = useMatrixClient();

  const [homeHidden, setHomeHidden] = useState(() => {
    const content = getAccountData(
      mx,
      AccountDataEvent.CinnySpaces
    )?.getContent<InCinnySpacesContent>();
    return content?.hideHome ?? false;
  });

  useEffect(() => {
    const content = getAccountData(
      mx,
      AccountDataEvent.CinnySpaces
    )?.getContent<InCinnySpacesContent>();
    setHomeHidden(content?.hideHome ?? false);
  }, [mx]);

  useAccountDataCallback(
    mx,
    useCallback(
      (mEvent) => {
        if (mEvent.getType() === AccountDataEvent.CinnySpaces) {
          const newContent = mEvent.getContent<InCinnySpacesContent>();
          setHomeHidden(newContent?.hideHome ?? false);
        }
      },
      [mx]
    )
  );

  return [homeHidden, setHomeHidden];
};
