/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow strict-local
 */

import {bufferToBlob} from 'flipper';
import {RequiredParametersDialog} from './components';
import {
  removeBookmarkFromDB,
  readBookmarksFromDB,
  writeBookmarkToDB,
} from './util/indexedDB';
import {} from './util/autoCompleteProvider';
import {getAppMatchPatterns} from './util/appMatchPatterns';
import {getRequiredParameters, filterOptionalParameters} from './util/uri';
import {
  Bookmark,
  NavigationEvent,
  AppMatchPattern,
  URI,
  RawNavigationEvent,
} from './types';
import React from 'react';
import {PluginClient, createState, renderReactRoot} from 'flipper-plugin';

export type State = {
  shouldShowSaveBookmarkDialog: boolean;
  shouldShowURIErrorDialog: boolean;
  saveBookmarkURI: URI | null;
  requiredParameters: Array<string>;
};

type Events = {
  nav_event: RawNavigationEvent;
};

type Methods = {
  navigate_to(params: {url: string}): Promise<void>;
};

export type NavigationPlugin = ReturnType<typeof plugin>;

export function plugin(client: PluginClient<Events, Methods>) {
  const bookmarks = createState(new Map<URI, Bookmark>(), {
    persist: 'bookmarks',
  });
  const navigationEvents = createState<NavigationEvent[]>([], {
    persist: 'navigationEvents',
  });
  const appMatchPatterns = createState<AppMatchPattern[]>([], {
    persist: 'appMatchPatterns',
  });
  const currentURI = createState('');
  const shouldShowSaveBookmarkDialog = createState(false);
  const saveBookmarkURI = createState<null | string>(null);

  client.onMessage('nav_event', async (payload) => {
    const navigationEvent: NavigationEvent = {
      uri: payload.uri === undefined ? null : decodeURIComponent(payload.uri),
      date: payload.date ? new Date(payload.date) : new Date(),
      className: payload.class === undefined ? null : payload.class,
      screenshot: null,
    };

    if (navigationEvent.uri) currentURI.set(navigationEvent.uri);

    navigationEvents.update((draft) => {
      draft.unshift(navigationEvent);
    });

    const screenshot: Buffer = await client.device.screenshot();
    if (screenshot.byteLength === 0) {
      console.warn(
        '[navigation] Could not retrieve valid screenshot from the device.',
      );
      return;
    }
    const blobURL = URL.createObjectURL(bufferToBlob(screenshot));
    // this process is async, make sure we update the correct one..
    const navigationEventIndex = navigationEvents
      .get()
      .indexOf(navigationEvent);
    if (navigationEventIndex !== -1) {
      navigationEvents.update((draft) => {
        draft[navigationEventIndex].screenshot = blobURL;
      });
    }
  });

  getAppMatchPatterns(client.appId, client.device)
    .then((patterns) => {
      appMatchPatterns.set(patterns);
    })
    .catch((e) => {
      console.error('[Navigation] Failed to find appMatchPatterns', e);
    });

  readBookmarksFromDB()
    .then((bookmarksData) => {
      bookmarks.set(bookmarksData);
    })
    .catch((e) => console.error('[navigation] readBookmarksFromDB failed:', e));

  function navigateTo(query: string) {
    const filteredQuery = filterOptionalParameters(query);
    currentURI.set(filteredQuery);
    const params = getRequiredParameters(filteredQuery);
    if (params.length === 0) {
      if (client.appName === 'Facebook' && client.device.os === 'iOS') {
        // use custom navigate_to event for Wilde
        client.send('navigate_to', {
          url: filterOptionalParameters(filteredQuery),
        });
      } else {
        client.device.navigateToLocation(
          filterOptionalParameters(filteredQuery),
        );
      }
    } else {
      renderReactRoot((unmount) => (
        <RequiredParametersDialog
          onHide={unmount}
          uri={filteredQuery}
          requiredParameters={params}
          onSubmit={navigateTo}
        />
      ));
    }
  }

  function onFavorite(uri: string) {
    shouldShowSaveBookmarkDialog.set(true);
    saveBookmarkURI.set(uri);
  }

  function addBookmark(bookmark: Bookmark) {
    const newBookmark = {
      uri: bookmark.uri,
      commonName: bookmark.commonName,
    };

    bookmarks.update((draft) => {
      draft.set(newBookmark.uri, newBookmark);
    });
    writeBookmarkToDB(newBookmark);
  }

  function removeBookmark(uri: string) {
    bookmarks.update((draft) => {
      draft.delete(uri);
    });
    removeBookmarkFromDB(uri);
  }

  return {
    navigateTo,
    onFavorite,
    addBookmark,
    removeBookmark,
    bookmarks,
    saveBookmarkURI,
    shouldShowSaveBookmarkDialog,
    appMatchPatterns,
    navigationEvents,
    currentURI,
    getAutoCompleteAppMatchPatterns(
      query: string,
      bookmarks: Map<string, Bookmark>,
      appMatchPatterns: AppMatchPattern[],
      limit: number,
    ): AppMatchPattern[] {
      const q = query.toLowerCase();
      const results: AppMatchPattern[] = [];
      for (const item of appMatchPatterns) {
        if (
          !bookmarks.has(item.pattern) &&
          (item.className.toLowerCase().includes(q) ||
            item.pattern.toLowerCase().includes(q))
        ) {
          results.push(item);
          if (--limit < 1) break;
        }
      }
      return results;
    },
  };
}

/* @scarf-info: do not remove, more info: https://fburl.com/scarf */
/* @scarf-generated: flipper-plugin index.js.template 0bfa32e5-fb15-4705-81f8-86260a1f3f8e */
