import { h, Component } from "preact";
import Notes, { IVault, BaseNote } from "../../../notes";
import AddButton from "../../AddButton";
import Navigation from "../../../navigation";
import { ArrowLeft, Search } from "preact-feather";
import ContextMenu from "../../context";
import Notifications from "../../../notifications";
import { Observable, Lock } from "@hibas123/utils";

export default class EntryList extends Component<
   { vault: IVault },
   { notes: BaseNote[]; context: h.JSX.Element | undefined }
> {
   rawNotes: BaseNote[];

   private searchObservableServer = new Observable<void>(1000);
   private searchObservable = this.searchObservableServer.getPublicApi();

   constructor(props) {
      super(props);
      console.log("Creating new Instance of EntryList");
      this.state = { notes: [], context: undefined };
      this.onDragOver = this.onDragOver.bind(this);
      this.onDrop = this.onDrop.bind(this);
      this.reloadNotes = this.reloadNotes.bind(this);
      this.searchChanged = this.searchChanged.bind(this);
   }
   vault: IVault;

   async reloadNotes(s?: boolean) {
      if (s) return;
      this.rawNotes = await this.vault.getAllNotes();
      await this.applySearch(true);
   }

   async componentWillMount() {
      this.vault = this.props.vault;
      this.reloadNotes();
      document.body.addEventListener("dragover", this.onDragOver);
      document.body.addEventListener("drop", this.onDrop);
      Notes.syncObservable.subscribe(this.reloadNotes);
      this.searchObservable.subscribeCollect(() => this.applySearch());
   }

   componentWillUnmount() {
      document.body.removeEventListener("dragover", this.onDragOver);
      document.body.removeEventListener("drop", this.onDrop);
      Notes.syncObservable.unsubscribe(this.reloadNotes);
   }

   componentDidMount() {
      console.log("ON Component Did mount", this.search);
      this.searchInput.value = this.search;
   }

   onDragOver(evt: DragEvent) {
      evt.preventDefault();
   }

   onContext(evt: MouseEvent, note: BaseNote) {
      evt.preventDefault();
      evt.stopPropagation();

      const close = () => {
         document.documentElement.removeEventListener("click", close);
         this.setState({ context: undefined });
      };
      document.documentElement.addEventListener("click", close);

      const shareNote = async () => {
         let nav = window.navigator as any;
         if (nav.share !== undefined) {
            let vnote = await this.vault.getNote(note._id);
            nav.share({
               title: vnote.preview.split("\n")[0],
               text: vnote.__value,
            })
               .then(() => console.log("Successful share"))
               .catch((error) => {
                  console.error("Error sharing:", error);
                  Notifications.sendError(error);
               });
         } else {
            Notifications.sendError("Sharing not possible on this device");
         }
      };

      const deleteNote = async () => {
         this.vault
            .deleteNote(note._id)
            .then(() => {
               Notifications.sendSuccess("Deleted");
               this.rawNotes = this.rawNotes.filter((e) => e._id !== note._id);
               this.setState({
                  notes: this.state.notes.filter((e) => e._id !== note._id),
               });
            })
            .catch((err) => {
               Notifications.sendError(err);
            });
      };

      const copyNote = async () => {
         this.vault.getNote(note._id).then((note) => {
            const el = document.createElement("textarea");
            el.value = note.__value;
            document.body.appendChild(el);
            el.select();
            document.execCommand("copy");
            document.body.removeChild(el);
            Notifications.sendSuccess("Copied");
         });
      };

      let share;
      if ((window.navigator as any).share) {
         share = (
            <button class="btn" onClick={shareNote}>
               share
            </button>
         );
      }

      let context = (
         <ContextMenu event={evt}>
            {share}
            <button class="btn" onClick={deleteNote}>
               delete
            </button>
            <button class="btn" onClick={copyNote}>
               copy
            </button>
         </ContextMenu>
      );

      this.setState({ context });
      return false;
   }

   async onDrop(evt: DragEvent) {
      evt.preventDefault();
      if (evt.dataTransfer.items) {
         for (let i = 0; i < evt.dataTransfer.items.length; i++) {
            let item = evt.dataTransfer.items[i];

            if (item.kind === "file") {
               let file = item.getAsFile();
               if (file.type !== "application/json") {
                  Notifications.sendError("Invalid File Type!!!");
               } else {
                  try {
                     let data = await new Promise<string>((yes, no) => {
                        let fr = new FileReader();
                        fr.onload = (ev) => {
                           yes((ev.target as any).result);
                        };
                        fr.onerror = no;
                        fr.readAsText(file);
                     });
                     let parsed = JSON.parse(data);
                     let c = new Error("Could not parse!");
                     let notes: { content: string; time: Date }[] = null;
                     if (Array.isArray(parsed)) {
                        // Could be from SecureNotes 1
                        notes = parsed.map((elm) => {
                           if (
                              typeof elm.message !== "string" ||
                              typeof elm.time !== "string"
                           ) {
                              throw c;
                           }

                           return {
                              content: elm.message,
                              time: new Date(elm.time),
                           };
                        });
                     } else if (parsed.version) {
                        // Could be from SecureNotes 2
                        if (parsed.version === 1) {
                           //Could be from SecureNotes 2 version 1
                           notes = (parsed.notes as any[]).map((elm) => {
                              if (
                                 typeof elm.content !== "string" ||
                                 typeof elm.time !== "string"
                              ) {
                                 throw c;
                              }
                              return {
                                 content: elm.content,
                                 time: new Date(elm.time),
                              };
                           });
                        } else {
                           throw c;
                        }
                     } else throw c;

                     await Promise.all(
                        notes.map((n) => {
                           let note = this.vault.newNote();
                           note.__value = n.content;
                           return this.vault.saveNote(note, n.time);
                        })
                     );
                     await this.reloadNotes();
                     Notifications.sendSuccess(
                        `Imported ${notes.length} notes!`
                     );
                  } catch (err) {
                     Notifications.sendError("Cannot read File!");
                     console.error(err);
                  }
               }
            }
         }
      }
   }

   searchLock = new Lock();
   oldSearch = "";
   async applySearch(force = false) {
      const search = this.search.toLowerCase();
      if (!force && search === this.oldSearch) {
         return;
      }

      if (this.searchLock.locked) return;
      const lock = await this.searchLock.getLock();
      console.time("SearchOP");

      let notes: BaseNote[] = [];

      if (search === "") {
         notes = this.rawNotes;
      } else {
         const parts = search.split(" ");
         const match = (note: BaseNote) => {
            return parts.every(function (el) {
               return note.preview.toLowerCase().indexOf(el) > -1;
            });
         };

         let elements: BaseNote[];
         if (!force && this.oldSearch && search.startsWith(this.oldSearch)) {
            elements = [...this.state.notes];
         } else {
            elements = [...this.rawNotes];
         }

         await new Promise((yes) => {
            const idle = () => {
               window.requestIdleCallback(
                  (deadline) => {
                     let invTR = deadline.timeRemaining() <= 0;
                     while (
                        (deadline.timeRemaining() > 0 || invTR) &&
                        elements.length > 0
                     ) {
                        let element = elements.shift();
                        if (match(element)) {
                           notes.push(element);
                        }
                     }
                     if (elements.length > 0) idle();
                     else yes();
                  },
                  { timeout: 100 }
               );
            };
            idle();
         });
         // notes = elements.filter(note => match(note));
      }

      await new Promise((yes) => this.setState({ notes }, yes));
      this.oldSearch = search;
      lock.release();
      console.timeEnd("SearchOP");
   }

   search = "";
   searchChanged(evt: Event) {
      let input = evt.target as HTMLInputElement;
      this.search = input.value;
      this.searchObservableServer.send();
   }

   searchInput: HTMLInputElement;
   render() {
      const open_entry = (id: string | null) => {
         Navigation.setPage(
            "/vault",
            { id: this.vault.id },
            { id, entry: "true" }
         );
      };

      return (
         <div>
            {this.state.context}
            <header class="header">
               <div class="header-icon-button" onClick={() => history.back()}>
                  <ArrowLeft height={undefined} width={undefined} />
               </div>
               <span onClick={() => Navigation.setPage("/")}>
                  {this.vault ? this.vault.name : ""}
               </span>
               <span></span>
               {/* <a class="button header_icon_button"><MoreVertival height={undefined} width={undefined} /></a> */}
            </header>
            <AddButton onClick={() => open_entry(null)} />
            <div class="container">
               <div style="display:flex; margin-top: .5rem;">
                  <input
                     class="inp"
                     type="text"
                     style="width: 100%; height: 40px;"
                     onKeyUp={this.searchChanged}
                     ref={(elm) => (this.searchInput = elm)}
                  />
                  <button
                     class="btn btn-primary"
                     style="padding: 5px 10px; margin: 0; height: 40px; width: 40px;"
                  >
                     <Search />
                  </button>
               </div>

               <div class="vault-list" style="margin-top: 1rem;">
                  {this.state.notes.map((note) => {
                     let [first, second] = note.preview.split("\n", 2);
                     return (
                        <div
                           class="card vault-vault"
                           onContextMenu={(evt) => this.onContext(evt, note)}
                           onClick={() => {
                              open_entry(note._id);
                           }}
                        >
                           <div>{first}</div>
                           <div>{second}</div>
                        </div>
                     );
                  })}
               </div>
            </div>
         </div>
      );
   }
}
