/**
 * 
 */

import { findInReactTree, findInTree, getOwnerInstance } from '@vizality/util/react';
import { getModule, getModuleByDisplayName } from '@vizality/webpack';
import { waitForElement, joinClassNames } from '@vizality/util/dom';
import { patch, unpatch } from '@vizality/patcher';
import { useForceUpdate } from '@vizality/hooks';
import { isArray } from '@vizality/util/array';
import { Events } from '@vizality/constants';
import { Builtin } from '@vizality/entities';
import React, { useEffect } from 'react';

export default class Router extends Builtin {
  /**
   * Starts up the builtin.
   * @returns {Promise<void>}
   */
  async start () {
    await this.patchRouter();
    await this.patchViews();
    await this.patchSidebar();
    await this.forceRouterUpdate();
    vizality.api.routes.on(Events.VIZALITY_ROUTE_ADD, this.forceRouterUpdate);
    vizality.api.routes.on('routeRemove', this.forceRouterUpdate);
  }

  /**
   * Shuts down the builtin.
   * @returns {void}
   */
  stop () {
    vizality.api.routes.off(Events.VIZALITY_ROUTE_ADD, this.forceRouterUpdate);
    vizality.api.routes.off('routeRemove', this.forceRouterUpdate);
    unpatch('vz-router-routes');
    unpatch('vz-router-views');
    unpatch('vz-router-sidebar');
    this.forceRouterUpdate();
  }

  /**
   * Patches the app's route renderer.
   * @returns {Promise<void>}
   */
  async patchRouter () {
    const { container } = await getModule('container', 'downloadProgressCircle', true);
    const RouteRenderer = getOwnerInstance(await waitForElement(`.${container}`));
    patch('vz-router-routes', RouteRenderer?.props?.children, 'type', (_, res) => {
      try {
        const { children } = findInReactTree(res, m => isArray(m.children) && m.children.length > 5);
        children.push(
          ...vizality.api.routes.getAllRoutes()?.map(route => ({
            ...children[0],
            props: {
              /**
               * Not exactly sure what this property is, but Discord added it around
               * March of 2021 and sounds like something we would want to have set to true.
               */
              disableTrack: true,
              render: () => {
                const Render = route.render;
                return <Render />;
              },
              path: `/vizality${route.path}`
            }
          }))
        );
      } catch (err) {
        return this.error('There was a problem patching the routes!', err);
      }
    });
  }

  /**
   * Patches the app's views.
   * @returns {Promise<void>}
   */
  async patchViews () {
    const FluxifiedViews = await getModuleByDisplayName('FluxContainer(ViewsWithMainInterface)', true);
    const Views = FluxifiedViews?.prototype?.render?.call({ memoizedGetStateFromStores: () => ({}) })?.type;
    patch('vz-router-views', Views?.prototype, 'render', (_, res) => {
      try {
        const routes = findInTree(res, n => isArray(n) && n[0]?.key && n[0].props?.path && n[0].props.render);
        if (!isArray(routes)) {
          return;
        }
        routes[routes.length - 1].props.path = [
          ...new Set(
            routes[routes.length - 1]?.props?.path?.concat(
              vizality.api.routes.getAllRoutes()?.map(route => `/vizality${route.path}`)
            )
          )
        ];
      } catch (err) {
        return this.error('There was a problem patching the views!', err);
      }
    });
  }

  /**
   * Patches Discord's channel sidebar so that we can decide to render a custom one or
   * none at all when registering new routes.
   * @returns {Promise<void>}
   */
  async patchSidebar () {
    try {
      const { panels } = await getModule('panels', true);
      const instance = getOwnerInstance(await waitForElement(`.${panels}`));
      const Routes = await getModule('handleRouteChange', true);
      patch('vz-router-sidebar', instance?.props?.children, 'type', (_, res) => {
        try {
          const forceUpdate = useForceUpdate();
          /**
           * Handles collapsing/expanding the dashboard sidebar.
           * @param {string} setting Setting key
           */
          const handleSidebarCollapse = setting => {
            /**
             * If it's the setting we're looking for, rerender the sidebar.
             */
            if (setting === 'dashboardSidebarCollapse') {
              forceUpdate();
            }
          };
          /**
           * Add listeners for route changes and addon settings toggle actions.
           */
          useEffect(() => {
            vizality.api.settings
              .on(Events.VIZALITY_SETTING_UPDATE, handleSidebarCollapse);
            Routes.listeners.add(forceUpdate);
            /**
             * Remove the listeners on unmount.
             */
            return () => {
              vizality.api.settings
                .removeListener(Events.VIZALITY_SETTING_UPDATE, handleSidebarCollapse);
              Routes.listeners.delete(forceUpdate);
            };
          }, []);
          const content = findInReactTree(res, n => n?.className?.startsWith('content-'));
          const child = content?.children[0];
          if (child?.type?.displayName === 'ChannelSidebar') {
            const oType = child.type;
            child.type = props => {
              try {
                let ret = Reflect.apply(oType, null, [ props ]);
                if (vizality.api.routes.getLocation()?.pathname?.startsWith('/vizality')) {
                /**
                 * Add and remove collapsed and expanded attributes based on the setting.
                 */
                  ret.props.className = joinClassNames('vz-dashboard-sidebar', ret.props.className);
                  ret.props['vz-collapsed'] = vizality.settings.get('dashboardSidebarCollapse', false) && '';
                  ret.props['vz-expanded'] = !vizality.settings.get('dashboardSidebarCollapse', false) && '';
                  const rawPath = vizality.api.routes.getLocation().pathname.substring('vizality/'.length);
                  const route = vizality.api.routes.getAllRoutes()?.find(rte => rawPath.startsWith(rte.path));
                  if (route && route.sidebar) {
                    const Sidebar = route.sidebar;
                    ret.props.children[0] = <Sidebar />;
                  /**
                   * If the sidebar property is false, don't render one.
                   */
                  } else {
                    ret = null;
                  }
                }
                return ret;
              } catch (err) {
                return this.error('There was a problem patching ChannelSidebar!', err);
              }
            };
          }
        } catch (err) {
          return this.error('There was a problem patching the channels sidebar!', err);
        }
      });
    } catch (err) {
      return this.error('There was a problem patching the channels sidebar!', err);
    }
  }

  /**
   * Forces updates on the app's views and routes.
   * @returns {Promise<void>}
   */
  async forceRouterUpdate () {
    try {
      /**
       * Views
       */
      const { app } = await getModule('app', true);
      const viewsInstance = getOwnerInstance(await waitForElement(`.${app}`));
      findInTree(viewsInstance?._reactInternals, n => n?.historyUnlisten, { walkable: [ 'child', 'stateNode' ] })?.forceUpdate();
      /**
       * Routes
       */
      const { container } = await getModule('container', 'downloadProgressCircle', true);
      const routesInstance = getOwnerInstance(await waitForElement(`.${container}`));
      routesInstance?.forceUpdate();
    } catch (err) {
      return this.error('There was a problem force updating the views or routes!', err);
    }
  }
}
