/*
This file is part of the Notesnook project (https://notesnook.com/)

Copyright (C) 2023 Streetwriters (Private) Limited

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

import { LegendList } from "@legendapp/list";
import { isFeatureAvailable } from "@notesnook/common";
import { Tag, VirtualizedGrouping, sanitizeTag } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { TextInput, View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { Header } from "../../components/header";
import Input from "../../components/ui/input";
import { Pressable } from "../../components/ui/pressable";
import Heading from "../../components/ui/typography/heading";
import Paragraph from "../../components/ui/typography/paragraph";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import {
  sendItemUpdateEvent,
  ToastManager
} from "../../services/event-manager";
import Navigation, { NavigationProps } from "../../services/navigation";
import {
  ItemSelection,
  createItemSelectionStore
} from "../../stores/item-selection-store";
import { useRelationStore } from "../../stores/use-relation-store";
import { useTagStore } from "../../stores/use-tag-store";
import { AppFontSize, defaultBorderRadius } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";

async function updateInitialSelectionState(items: string[]) {
  const relations = await db.relations
    .to(
      {
        type: "note",
        ids: items
      },
      "tag"
    )
    .get();

  const initialSelectionState: ItemSelection = {};
  const tagId = [...new Set(relations.map((relation) => relation.fromId))];

  for (const id of tagId) {
    const all = items.every((noteId) => {
      return (
        relations.findIndex(
          (relation) => relation.fromId === id && relation.toId === noteId
        ) > -1
      );
    });
    if (all) {
      initialSelectionState[id] = "selected";
    } else {
      initialSelectionState[id] = "intermediate";
    }
  }

  return initialSelectionState;
}

const useTagItemSelection = createItemSelectionStore(true);

const ManageTags = (props: NavigationProps<"ManageTags">) => {
  const { colors } = useThemeColors();
  const ids = React.useMemo(
    () => props.route.params.ids || [],
    [props.route.params.ids]
  );
  const [tags, setTags] = useState<Tag[]>();
  const [query, setQuery] = useState<string>();
  const inputRef = useRef<TextInput>(null);
  useNavigationFocus(props.navigation, { focusOnInit: true });
  const timerRef = useRef<NodeJS.Timeout>(undefined);
  const [queryExists, setQueryExists] = useState(false);
  const refreshSelection = useCallback(() => {
    updateInitialSelectionState(ids).then((selection) => {
      useTagItemSelection.setState({
        initialState: selection,
        selection: { ...selection }
      });
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [ids, tags]);

  const sortAndSetTags = useCallback(
    async (items: VirtualizedGrouping<Tag>) => {
      const tags = [];
      const noteTags = [];
      const assignedTags =
        ids.length > 1
          ? []
          : await db.relations
              .to({ type: "note", id: ids[0] }, "tag")
              .resolve();

      for (let i = 0; i < items.placeholders.length; i++) {
        const item = (await items.item(i)).item;
        if (item) {
          if (assignedTags.find((tag) => tag.id === item.id)) {
            noteTags.push(item);
          } else {
            tags.push(item);
          }
        }
      }
      tags.splice(0, 0, ...noteTags);
      setTags(tags);
    },
    [ids]
  );

  const refreshTags = useCallback(() => {
    if (query && query.trim() !== "") {
      db.lookup
        .tags(query)
        .sorted(db.settings.getGroupOptions("tags"))
        .then(sortAndSetTags);
    } else {
      db.tags.all
        .sorted(db.settings.getGroupOptions("tags"))
        .then(sortAndSetTags);
    }
  }, [query, sortAndSetTags]);

  useEffect(() => {
    refreshTags();
  }, [refreshTags, query]);

  useEffect(() => {
    refreshSelection();
  }, [refreshSelection]);

  const checkQueryExists = (query: string) => {
    db.tags.all
      .find((v) => v.and([v(`title`, "==", query)]))
      .then((exists) => setQueryExists(!!exists));
  };

  const onSubmit = async () => {
    if (!query || query === "" || query.trimStart().length == 0) {
      return;
    }

    const tag = query;
    inputRef.current?.setNativeProps({
      text: ""
    });

    try {
      const exists = await db.tags.all.find((v) =>
        v.and([v(`title`, "==", tag)])
      );

      let id = exists?.id;

      if (!id) {
        const tagsFeature = await isFeatureAvailable("tags");
        if (!tagsFeature.isAllowed) {
          ToastManager.show({
            message: tagsFeature.error,
            type: "info",
            context: "local"
          });
          return;
        }
        id = await db.tags.add({
          title: tag
        });
      }

      if (id) {
        for (const noteId of ids) {
          await db.relations.add(
            {
              id: id,
              type: "tag"
            },
            {
              id: noteId,
              type: "note"
            }
          );
        }
      }

      useRelationStore.getState().update();
      sendItemUpdateEvent(id, "tag");
      useTagStore.getState().refresh();
      setQuery(undefined);
    } catch (e) {
      ToastManager.error(e as Error);
    }

    Navigation.queueRoutesForUpdate();
  };

  const onPress = useCallback(
    async (id: string) => {
      for (const noteId of ids) {
        try {
          if (!id) return;
          const isSelected =
            useTagItemSelection.getState().initialState[id] === "selected";
          if (isSelected) {
            await db.relations.unlink(
              {
                id: id,
                type: "tag"
              },
              {
                id: noteId,
                type: "note"
              }
            );
          } else {
            await db.relations.add(
              {
                id: id,
                type: "tag"
              },
              {
                id: noteId,
                type: "note"
              }
            );
          }
        } catch (e) {
          console.error(e);
        }
      }
      sendItemUpdateEvent(id, "tag");
      useTagStore.getState().refresh();
      useRelationStore.getState().update();
      refreshTags();
      setTimeout(() => {
        Navigation.queueRoutesForUpdate();
      }, 1);
      refreshSelection();
    },
    [ids, refreshSelection, refreshTags]
  );

  const renderTag = useCallback(
    ({ index, item }: { item: Tag; index: number }) => (
      <TagItem tag={item} onPress={onPress} />
    ),
    [onPress]
  );

  return (
    <SafeAreaView
      style={{
        width: "100%",
        alignSelf: "center",
        backgroundColor: colors.primary.background,
        gap: DefaultAppStyles.GAP_VERTICAL,
        flex: 1
      }}
    >
      <Header title={strings.manageTags()} canGoBack />

      <View
        style={{
          paddingHorizontal: DefaultAppStyles.GAP,
          flex: 1
        }}
      >
        <Input
          button={{
            icon: "magnify",
            color: colors.primary.accent,
            size: AppFontSize.lg,
            onPress: () => {}
          }}
          testID="tag-input"
          fwdRef={inputRef}
          autoCapitalize="none"
          onChangeText={(v) => {
            clearTimeout(timerRef.current);
            timerRef.current = setTimeout(() => {
              setQuery(sanitizeTag(v));
              checkQueryExists(sanitizeTag(v));
            }, 300);
          }}
          onSubmit={() => {
            onSubmit();
          }}
          placeholder={strings.searchForTags()}
        />

        {query && !queryExists ? (
          <Pressable
            key={"query_item"}
            style={{
              flexDirection: "row",
              justifyContent: "space-between",
              paddingHorizontal: DefaultAppStyles.GAP,
              paddingVertical: DefaultAppStyles.GAP_VERTICAL
            }}
            onPress={onSubmit}
            type="selected"
          >
            <Heading size={AppFontSize.sm} color={colors.selected.heading}>
              {strings.add()} {'"' + "#" + query + '"'}
            </Heading>
            <Icon
              name="plus"
              color={colors.selected.icon}
              size={AppFontSize.lg}
            />
          </Pressable>
        ) : null}

        <View
          style={{
            flex: 1
          }}
        >
          <LegendList
            data={tags || []}
            extraData={tags}
            keyboardShouldPersistTaps
            keyboardDismissMode="interactive"
            estimatedItemSize={50}
            renderItem={renderTag}
            ListEmptyComponent={
              <View
                style={{
                  width: "100%",
                  height: 200,
                  justifyContent: "center",
                  alignItems: "center"
                }}
              >
                <Heading size={50} color={colors.secondary.heading}>
                  #
                </Heading>
                <Paragraph
                  textBreakStrategy="balanced"
                  color={colors.secondary.paragraph}
                >
                  {strings.emptyPlaceholders("tag")}
                </Paragraph>
              </View>
            }
            ListFooterComponent={<View style={{ height: 50 }} />}
          />
        </View>
      </View>
    </SafeAreaView>
  );
};

ManageTags.present = (ids?: string[]) => {
  Navigation.push("ManageTags", {
    ids: ids
  });
};

export default ManageTags;

const TagItem = ({
  onPress,
  tag
}: {
  tag: Tag;
  onPress: (id: string) => void;
}) => {
  const { colors } = useThemeColors();
  const selection = useTagItemSelection((state) =>
    tag?.id ? state.selection[tag?.id] : false
  );

  return (
    <Pressable
      style={{
        flexDirection: "row",
        marginVertical: 5,
        justifyContent: "flex-start",
        minHeight: 40,
        alignItems: "center"
      }}
      onPress={() => {
        if (!tag) return;
        onPress(tag.id);
      }}
      type="plain"
    >
      {!tag ? null : (
        <Icon
          size={22}
          onPress={() => {
            if (!tag) return;
            onPress(tag.id);
          }}
          color={
            selection === "selected" || selection === "intermediate"
              ? colors.selected.icon
              : colors.primary.icon
          }
          style={{
            marginRight: 6
          }}
          testID={
            selection === "selected"
              ? "check-circle-outline"
              : selection === "intermediate"
                ? "minus-circle-outline"
                : "checkbox-blank-circle-outline"
          }
          name={
            selection === "selected"
              ? "check-circle-outline"
              : selection === "intermediate"
                ? "minus-circle-outline"
                : "checkbox-blank-circle-outline"
          }
        />
      )}
      {tag ? (
        <Paragraph style={{ flexShrink: 1 }} size={AppFontSize.sm}>
          {"#" + tag?.title}
        </Paragraph>
      ) : (
        <View
          style={{
            width: 200,
            height: 30,
            borderRadius: defaultBorderRadius
          }}
        />
      )}
    </Pressable>
  );
};
