/*
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 {
  Item,
  Note,
  Notebook,
  Tag,
  VirtualizedGrouping
} from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import {
  FlatList,
  Platform,
  Text,
  TextInput,
  TouchableOpacity,
  View,
  useWindowDimensions
} from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { create } from "zustand";
import { db } from "../common/database";
import Paragraph from "../components/ui/typography/paragraph";
import { useDBItem, useNoteLocked } from "../hooks/use-db-item";
import { useNotebook } from "../hooks/use-notebook";
import { getElevationStyle } from "../utils/elevation";
import { initDatabase, useShareStore } from "./store";
import { DefaultAppStyles } from "../utils/styles";

export const useNotebookExpandedStore = create<{
  expanded: {
    [id: string]: boolean;
  };
  setExpanded: (id: string) => void;
}>((set, get) => ({
  expanded: {},
  setExpanded(id: string) {
    set({
      expanded: {
        ...get().expanded,
        [id]: !get().expanded[id]
      }
    });
  }
}));
const SearchSetters = {
  appendNote: (id: string) => {
    useShareStore.getState().setAppendNote(id);
  },
  selectNotebooks: (id: string) => {
    const selectedNotebooks = [...useShareStore.getState().selectedNotebooks];
    const currentIndex = selectedNotebooks.findIndex(
      (selected) => id === selected
    );
    if (currentIndex === -1) {
      selectedNotebooks.push(id as never);
    } else {
      selectedNotebooks.splice(currentIndex, 1);
    }
    useShareStore.getState().setSelectedNotebooks(selectedNotebooks);
  },
  selectTags: (id: string) => {
    const selectedTags = [...useShareStore.getState().selectedTags];
    const currentIndex = selectedTags.indexOf(id as never);
    if (currentIndex === -1) {
      selectedTags.push(id as never);
    } else {
      selectedTags.splice(currentIndex, 1);
    }
    useShareStore.getState().setSelectedTags(selectedTags);
  }
};

const SearchGetters = {
  appendNote: () => db.notes.all.sorted(db.settings.getGroupOptions("notes")),
  selectNotebooks: () =>
    db.notebooks.roots.sorted(db.settings.getGroupOptions("notebooks")),
  selectTags: () => {
    return db.tags.all.sorted(db.settings.getGroupOptions("tags"));
  }
};

const SearchLookup = {
  appendNote: (query: string) => {
    return db.lookup.notes(query).sorted();
  },
  selectNotebooks: (query: string) => {
    return db.lookup.notebooks(query).sorted();
  },
  selectTags: (query: string) => {
    return db.lookup.tags(query).sorted();
  }
};

const SearchPlaceholder = {
  appendNote: "Search for a note",
  selectNotebooks: "Search for a notebook",
  selectTags: "Search for a tag"
};

type SearchMode = "appendNote" | "selectNotebooks" | "selectTags";

const NotebookItem = ({
  id,
  mode,
  close,
  items,
  level = 0
}: {
  id: string | number;
  mode: SearchMode;
  close?: () => void;
  level?: number;
  items?: VirtualizedGrouping<Notebook>;
}) => {
  const { nestedNotebooks, notebook } = useNotebook(id, items);
  const isExpanded = useNotebookExpandedStore((state) =>
    !notebook ? false : state.expanded[notebook.id]
  );
  const { colors } = useThemeColors();
  const isSelected = useShareStore((state) =>
    !notebook
      ? false
      : state.selectedNotebooks.findIndex(
          (selectedId: string) => notebook?.id === selectedId
        ) > -1
  );
  const set = SearchSetters[mode];

  const onSelectItem = async () => {
    if (!notebook) return;
    set(notebook.id);
  };

  return !notebook ? (
    <View
      style={{
        height: 40
      }}
    />
  ) : (
    <TouchableOpacity
      activeOpacity={0.7}
      onPress={() => onSelectItem()}
      style={{
        flexDirection: "column",
        justifyContent: "center",
        paddingVertical: DefaultAppStyles.GAP_VERTICAL,
        borderBottomWidth: 1,
        borderBottomColor: colors.primary.border
      }}
    >
      <View
        style={{
          flexDirection: "row",
          paddingHorizontal: DefaultAppStyles.GAP,
          alignItems: "center"
        }}
      >
        <TouchableOpacity
          style={{
            width: 35,
            height: 35,
            justifyContent: "center",
            alignItems: "center"
          }}
          onPress={() => {
            if (!notebook || !nestedNotebooks?.placeholders.length) return;
            useNotebookExpandedStore.getState().setExpanded(notebook.id);
          }}
          activeOpacity={1}
        >
          <Icon
            size={20}
            color={colors.primary.icon}
            name={
              !nestedNotebooks?.placeholders.length
                ? "book-outline"
                : isExpanded
                ? "chevron-down"
                : "chevron-right"
            }
          />
        </TouchableOpacity>

        <View
          style={{
            flexDirection: "row",
            justifyContent: "space-between",
            flex: 1,
            alignItems: "center"
          }}
        >
          <Paragraph
            numberOfLines={1}
            allowFontScaling={false}
            style={{
              color: colors.primary.paragraph,
              fontSize: 15
            }}
          >
            {notebook.title}
          </Paragraph>
          <Icon
            name={isSelected ? "checkbox-outline" : "checkbox-blank-outline"}
            style={{
              marginRight: 10
            }}
            allowFontScaling={false}
            size={20}
            color={isSelected ? colors.primary.accent : colors.secondary.icon}
          />
        </View>
      </View>

      {nestedNotebooks?.placeholders?.length && isExpanded ? (
        <View
          style={{
            paddingLeft: level + 1 > 0 && level + 1 < 5 ? 15 : 0,
            marginTop: 5
          }}
        >
          {nestedNotebooks.placeholders.map((item, index) => (
            <NotebookItem
              key={notebook?.id + index}
              id={index}
              mode={mode}
              close={close}
              level={level + 1}
            />
          ))}
        </View>
      ) : null}
    </TouchableOpacity>
  );
};

const ListItem = ({
  id,
  mode,
  close,
  items
}: {
  id: string | number;
  mode: SearchMode;
  close?: () => void;
  items?: VirtualizedGrouping<Item>;
}) => {
  const [item] = useDBItem(
    id,
    mode === "appendNote" ? "note" : "tag",
    items as VirtualizedGrouping<Note | Tag>
  );
  const locked = useNoteLocked(mode === "appendNote" ? item?.id : undefined);
  const { colors } = useThemeColors();
  const isSelected = useShareStore((state) =>
    mode === "appendNote" || !item
      ? false
      : state.selectedTags.indexOf(item?.id as never) > -1
  );

  const set = SearchSetters[mode];

  const onSelectItem = async () => {
    if (!item) {
      return;
    }

    if (mode === "appendNote") close?.();
    set(item.id);
  };

  return locked ? null : (
    <TouchableOpacity
      activeOpacity={0.7}
      onPress={() => onSelectItem()}
      style={{
        flexDirection: "column",
        borderBottomWidth: 1,
        borderBottomColor: colors.primary.border,
        justifyContent: "center",
        paddingVertical: DefaultAppStyles.GAP_VERTICAL
      }}
    >
      {!item ? null : (
        <View
          style={{
            flexDirection: "row",
            paddingHorizontal: DefaultAppStyles.GAP,
            flex: 1,
            justifyContent: "space-between"
          }}
        >
          <View
            style={{
              flexDirection: "column"
            }}
          >
            <Paragraph
              numberOfLines={1}
              allowFontScaling={false}
              style={{
                color: colors.primary.paragraph,
                fontSize: 15
              }}
            >
              {item.type === "tag" ? "#" : ""}
              {item.title}
            </Paragraph>
          </View>

          {item.type !== "note" ? (
            <Icon
              name={isSelected ? "checkbox-outline" : "checkbox-blank-outline"}
              style={{
                marginRight: 10
              }}
              allowFontScaling={false}
              size={20}
              color={isSelected ? colors.primary.accent : colors.secondary.icon}
            />
          ) : null}
        </View>
      )}
    </TouchableOpacity>
  );
};

export const Search = ({
  close,
  getKeyboardHeight,
  mode
}: {
  close?: () => void;
  getKeyboardHeight: () => number;
  mode: SearchMode;
}) => {
  const { colors } = useThemeColors();
  const { height } = useWindowDimensions();
  const timer = useRef<NodeJS.Timeout>(null);
  const inputRef = useRef<TextInput>(null);
  const [items, setItems] = useState<VirtualizedGrouping<Item>>();
  const [searchKeyword, setSearchKeyword] = useState<string>();
  const [queryExists, setQueryExists] = useState(false);

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

  const get = SearchGetters[mode];
  const lookup = SearchLookup[mode];

  const onSearch = async () => {
    if (timer.current) {
      clearTimeout(timer.current);
      timer.current = null;
    }
    timer.current = setTimeout(async () => {
      if (!searchKeyword) {
        setItems(undefined);
        setItems(await get());
        setQueryExists(false);
        return;
      }
      setItems(await lookup(searchKeyword));
    }, 150);
  };

  useEffect(() => {
    (async () => {
      await initDatabase();
      setItems(await get());
    })();
  }, [get]);

  const renderItem = React.useCallback(
    ({ index }: { item: boolean; index: number }) =>
      mode === "selectNotebooks" ? (
        <NotebookItem
          id={index}
          mode={mode}
          close={close}
          items={items as VirtualizedGrouping<Notebook>}
        />
      ) : (
        <ListItem id={index} mode={mode} close={close} items={items} />
      ),
    [close, mode, items]
  );

  const searchHeight = height - getKeyboardHeight();
  return (
    <View
      style={{
        position: "absolute",
        top: Platform.OS === "android" ? 50 : 0,
        backgroundColor: colors.primary.background,
        borderRadius: 10,
        width: "95%",
        minHeight: 250,
        alignSelf: "center",
        overflow: "hidden",
        zIndex: 999,
        ...getElevationStyle(5)
      }}
    >
      <View
        style={{
          flexShrink: 1,
          flexDirection: "row",
          justifyContent: "space-between",
          alignItems: "center",
          paddingHorizontal: DefaultAppStyles.GAP,
          paddingTop: 6,
          height: 50,
          borderBottomWidth: 1,
          borderBottomColor: colors.primary.border
        }}
      >
        <Icon
          name="arrow-left"
          color={colors.secondary.icon}
          style={{
            marginRight: 10
          }}
          size={25}
          onPress={close}
        />
        <TextInput
          ref={inputRef}
          placeholder={SearchPlaceholder[mode]}
          placeholderTextColor={colors.primary.placeholder}
          style={{
            fontSize: 16,
            flex: 1
          }}
          allowFontScaling={false}
          onChangeText={(value) => {
            setSearchKeyword(value);
            onSearch();
            checkQueryExists(value);
          }}
          onSubmitEditing={onSearch}
        />

        <Icon
          name="magnify"
          color={colors.primary.icon}
          size={25}
          onPress={onSearch}
        />
      </View>

      <View
        style={{
          maxHeight: searchHeight > 550 ? 550 : searchHeight,
          height: searchHeight > 550 ? 550 : searchHeight
        }}
      >
        {mode === "selectTags" && !queryExists && searchKeyword ? (
          <TouchableOpacity
            style={{
              flexDirection: "row",
              paddingHorizontal: DefaultAppStyles.GAP,
              height: 40,
              alignItems: "center"
            }}
            activeOpacity={1}
            onPress={async () => {
              if (!searchKeyword) return;
              const tagId = await db.tags.add({
                title: searchKeyword
              });
              if (!tagId) return;
              SearchSetters.selectTags(tagId);
              onSearch();
              checkQueryExists(searchKeyword);
            }}
          >
            <Paragraph
              numberOfLines={1}
              style={{
                color: colors.primary.paragraph,
                fontSize: 15
              }}
              allowFontScaling={false}
            >
              Add #{searchKeyword}
            </Paragraph>
          </TouchableOpacity>
        ) : null}

        <FlatList
          data={items?.placeholders}
          keyboardShouldPersistTaps="always"
          keyboardDismissMode="none"
          renderItem={renderItem}
          windowSize={1}
          ListFooterComponent={<View style={{ height: 200 }} />}
          ListEmptyComponent={
            <View
              style={{
                flex: 1,
                justifyContent: "center",
                alignItems: "center",
                height: 200
              }}
            >
              <Text
                style={{
                  color: colors.secondary.icon
                }}
                allowFontScaling={false}
              >
                {searchKeyword
                  ? `No results found for "${searchKeyword}"`
                  : mode === "appendNote"
                  ? "No notes"
                  : mode === "selectTags"
                  ? "No tags"
                  : "No notebooks"}
              </Text>
            </View>
          }
        />
      </View>
    </View>
  );
};
