import React, { memo, useEffect, useState } from 'react';
import { unstable_batchedUpdates } from 'react-dom';
import { toPlural } from '@vizality/util/string';
import { useForceUpdate } from '@vizality/hooks';
import { contextMenu } from '@vizality/webpack';
import { Events } from '@vizality/constants';
import { Messages } from '@vizality/i18n';

import { Divider, FormTitle, Anchor, Icon, LazyImage, Button, Switch, OverflowTooltip } from '..';
import { AddonContextMenu } from '.';

const { openContextMenu } = contextMenu;

/**
 * @typedef Permission
 * @type {object}
 * @property {React.NamedExoticComponent<object>} icon
 * @property {function(): string} text
 */

/**
 * Valid addon permissions.
 */
const PERMISSIONS = {
  /** @type {Permission} */
  keypresses: {
    icon: memo(({ size }) => <Icon name='keyboard' size={size} />),
    text: () => Messages.VIZALITY_ADDONS_PERMISSIONS_KEYPRESSES
  },
  /** @type {Permission} */
  use_eud: {
    icon: memo(({ size }) => <Icon name='person-shield' size={size} />),
    text: () => Messages.VIZALITY_ADDONS_PERMISSIONS_USE_EUD
  },
  /** @type {Permission} */
  filesystem: {
    icon: memo(({ size }) => <Icon name='copy' size={size} />),
    text: () => Messages.VIZALITY_ADDONS_PERMISSIONS_FS
  },
  /** @type {Permission} */
  ext_api: {
    icon: memo(({ size }) => <Icon name='import-export' size={size} />),
    text: () => Messages.VIZALITY_ADDONS_PERMISSIONS_API
  }
};

/**
 * Permissions component. Used to show what permissions an addon (likely a plugin) requires.
 * @component
 * @param {object} props
 * @param {string[]} props.permissions Array of permissions
 */
const Permissions = memo(({ permissions }) => {
  const hasPermissions = permissions && permissions.length > 0;
  if (!hasPermissions) {
    return null;
  }
  return (
    <>
      <Divider />
      <div className='vz-addon-card-permissions'>
        <FormTitle>{Messages.PERMISSIONS}</FormTitle>
        {Object.keys(PERMISSIONS).map(perm => permissions.includes(perm) &&
          <div className='vz-addon-card-permission'>
            {React.createElement(PERMISSIONS[perm].icon, { size: 22 })} {PERMISSIONS[perm].text()}
          </div>)}
      </div>
    </>
  );
});

/**
 * Addon author. Used to link to an addon author's profile modal.
 * @component
 * @param {object} props
 * @param {object|string} props.author Addon author object or author name
 * @param {string} props.author.id Author ID
 * @param {string} props.author.name Author name
 */
const Author = memo(({ author }) => {
  return (
    <OverflowTooltip className='vz-addon-card-author-wrapper' text={author.name || author}>
      <Anchor
        type='user'
        userId={author.id}
        className='vz-addon-card-author'
      >
        {author.name || author}
      </Anchor>
    </OverflowTooltip>
  );
});

/**
 * Addon description.
 * @component
 * @param {object} props
 * @param {string|React.Component|function(): React.ReactElement} props.description Description
 */
const Description = memo(({ description }) => (
  <div className='vz-addon-card-description'>
    {description}
  </div>
));

/**
 * Addon details.
 * @component
 */
const Details = memo(({ stars }) => {
  const downloads = Math.floor(Math.random() * (7777777 - 0)).toLocaleString();
  return (
    <div className='vz-addon-card-details'>
      <div className='vz-addon-card-detail-wrapper'>
        <OverflowTooltip text={`${stars} Stars`} className='vz-addon-card-detail-value-wrapper'>
          <Icon
            className='vz-addon-card-detail-value-icon-wrapper vz-addon-card-rating-icon-wrapper'
            iconClassName='vz-addon-card-rating-icon'
            name='star'
            size='18'
          />
          <div className='vz-addon-card-detail-value vz-addon-card-detail-rating-number'>
            {stars}
          </div>
        </OverflowTooltip>
      </div>
      <div className='vz-addon-card-detail-wrapper'>
        <OverflowTooltip text={`${downloads} Downloads`} className='vz-addon-card-detail-value-wrapper'>
          <Icon
            className='vz-addon-card-detail-value-icon-wrapper vz-addon-card-rating-icon-wrapper'
            iconClassName='vz-addon-card-rating-icon'
            name='cloud-download'
            size='18'
          />
          <div className='vz-addon-card-detail-value vz-addon-card-detail-downloads-count'>
            {downloads}
          </div>
        </OverflowTooltip>
      </div>
    </div>
  );
});

/**
 * Addon icon.
 * @component
 * @param {object} props
 * @param {string} props.icon Addon icon
 */
const AddonIcon = memo(({ icon }) => {
  return (
    <div className='vz-addon-card-icon'>
      <LazyImage
        className='vz-addon-card-icon-image-wrapper'
        imageClassName='vz-addon-card-icon-img'
        src={icon}
      />
    </div>
  );
});

/**
 * Addon footer. Used in addon cards.
 * @component
 * @param {object} props
 * @param {Addon} props.addon Addon object
 * @param {string} props.type Addon type
 * @param {boolean} props.hasSettings Has settings
 * @param {boolean} props.isEnabled Is enabled
 * @param {boolean} props.isInstalled Is installed
 * @param {function(!props.isEnabled)} props.onToggle Toggle handler
 */
const Footer = memo(({ addon, type, community, hasSettings, isEnabled, isInstalled, onToggle }) => {
  return (
    <div className='vz-addon-card-footer-wrapper'>
      <div className='vz-addon-card-footer'>
        <div className='vz-addon-card-footer-section-left'>
          {!community && isInstalled &&
            <div className='vz-addon-card-uninstall'>
              <Button
                onClick={evt => {
                  evt.stopPropagation();
                  vizality.manager[toPlural(type)].uninstall(addon.addonId);
                }}
                color={Button.Colors.RED}
                look={Button.Looks.FILLED}
                size={Button.Sizes.ICON}
              >
                <Icon name='trash-can' tooltip='Uninstall' size='20' />
              </Button>
            </div>
          }
          {community && <Details stars={addon.stars} />}
        </div>
        <div className='vz-addon-card-footer-section-right'>
          {!community && isInstalled && hasSettings &&
            <div className='vz-addon-card-settings-button'>
              <Icon
                className='vz-addon-card-settings-button-icon-wrapper'
                iconClassName='vz-addon-card-settings-button-icon'
                name='gear'
                tooltip='Settings'
                onClick={evt => {
                  evt.preventDefault();
                  vizality.api.routes.navigateTo(`/vizality/${type}/${addon.addonId}/settings`);
                }}
              />
            </div>
          }
          {!community && (
            <div className='vz-addon-card-toggle-wrapper'>
              <Switch
                className='vz-addon-card-toggle'
                checked={isEnabled}
                onChange={async v => onToggle(v)}
              />
            </div>
          )}
          {community && (
            <div className='vz-addon-card-community-action'>
              {isInstalled
                ? (
                  <div className='vz-addon-card-uninstall'>
                    <Button
                      onClick={evt => {
                        evt.stopPropagation();
                        vizality.manager[toPlural(type)].uninstall(addon.addonId);
                      }}
                      color={Button.Colors.RED}
                      look={Button.Looks.FILLED}
                      size={Button.Sizes.ICON}
                    >
                      <Icon name='trash-can' tooltip='Uninstall' size='20' />
                    </Button>
                  </div>
                )
                : (
                  <Button
                    onClick={evt => {
                      evt.stopPropagation();
                      vizality.manager[toPlural(type)].install(addon.addonId);
                    }}
                    color={Button.Colors.GREEN}
                    look={Button.Looks.FILLED}
                    size={Button.Sizes.SMALL}
                  >
                    Install
                  </Button>
                )
              }
            </div>
          )}
        </div>
      </div>
    </div>
  );
});

const Banner = memo(({ addon }) => {
  const hasBanner = Boolean(addon.manifest.banner);
  return (
    <div className='vz-addon-card-banner-wrapper'>
      <div className='vz-addon-card-banner-inner' >
        {hasBanner && (
          <LazyImage
            className='vz-addon-card-banner-image-wrapper'
            imageClassName='vz-addon-card-banner-image'
            src={addon.manifest.banner}
          />
        )}
      </div>
    </div>
  );
});

/**
 * Addon card in card layout.
 * @component
 * @param {object} props
 * @param {object} props.addon Addon object
 * @param {string} props.type Addon type
 * @param {boolean} props.hasSettings Has settings
 * @param {boolean} props.isEnabled Is enabled
 * @param {boolean} props.isInstalled Is installed
 * @param {function()} props.onToggle Toggle handler
 */
const AddonCard = memo(({ addon, type, community, hasSettings, isEnabled, isInstalled, onToggle }) => {
  return (
    <div className='vz-addon-card-header-wrapper'>
      <div className='vz-addon-card-content-wrapper'>
        <div className='vz-addon-card-content'>
          <div className='vz-addon-card-header'>
            <AddonIcon icon={addon.manifest.icon} />
            <div className='vz-addon-card-metadata'>
              <div className='vz-addon-card-name-version'>
                <OverflowTooltip className='vz-addon-card-name' text={addon.manifest.name}>
                  {addon.manifest.name}
                </OverflowTooltip>
                <span className='vz-addon-card-version'>
                  {addon.manifest.version}
                </span>
              </div>
              <Author author={addon.manifest.author} />
            </div>
          </div>
          <Description description={addon.manifest.description} />
          <Permissions permissions={addon.manifest.permissions} />
          <Footer
            community={community}
            addon={addon}
            type={type}
            hasSettings={hasSettings}
            isEnabled={isEnabled}
            isInstalled={isInstalled}
            onToggle={onToggle}
          />
        </div>
      </div>
    </div>
  );
});

/**
 * Addon card in compact layout.
 * @component
 * @param {object} props
 * @param {object} props.addon Addon object
 * @param {string} props.type Addon type
 * @param {boolean} props.hasSettings Has settings
 * @param {boolean} props.isEnabled Is enabled
 * @param {boolean} props.isInstalled Is installed
 * @param {function()} props.onToggle Toggle handler
 */
const AddonCardCompact = memo(({ addon, type, community, hasSettings, isEnabled, isInstalled, onToggle }) => {
  const [ installing, setInstalling ] = useState(false);
  const [ installFailed, setInstallFailed ] = useState(false);

  return (
    <div className='vz-addon-card-header-wrapper'>
      <div className='vz-addon-card-content-wrapper'>
        <div className='vz-addon-card-content'>
          <div className='vz-addon-card-header'>
            <AddonIcon icon={addon.manifest.icon} />
            <div className='vz-addon-card-metadata'>
              <div className='vz-addon-card-name-version'>
                <OverflowTooltip className='vz-addon-card-name' text={addon.manifest.name}>
                  {addon.manifest.name}
                </OverflowTooltip>
              </div>
              <Author author={addon.manifest.author} />
            </div>
            <div className='vz-addon-card-actions'>
              {!community && hasSettings && (
                <div className='vz-addon-card-settings'>
                  <Icon
                    className='vz-addon-card-settings-button-wrapper'
                    iconClassName='vz-addon-card-settings-button'
                    name='gear'
                    tooltip='Settings'
                    onClick={evt => {
                      evt.preventDefault();
                      vizality.api.routes.navigateTo(`/vizality/${type}/${addon.addonId}/settings`);
                    }}
                  />
                </div>
              )}
              {community && (
                isInstalled
                  ? (
                    <div className='vz-addon-card-uninstall'>
                      <Button
                        onClick={evt => {
                          evt.stopPropagation();
                          vizality.manager[toPlural(type)].uninstall(addon.addonId);
                        }}
                        color={Button.Colors.RED}
                        look={Button.Looks.FILLED}
                        size={Button.Sizes.ICON}
                      >
                        <Icon name='trash-can' tooltip='Uninstall' size='20' />
                      </Button>
                    </div>
                  )
                  : (
                    <div className='vz-addon-card-install'>
                      <Button
                        onClick={evt => unstable_batchedUpdates(async () => {
                          evt.stopPropagation();
                          setInstallFailed(false);
                          setInstalling(true);
                          await vizality.manager[toPlural(type)].install(addon.addonId)
                            .then(() => setInstalling(false))
                            .catch(() => unstable_batchedUpdates(() => {
                              setInstalling(false);
                              setInstallFailed(true);
                            }));
                        })}
                        disabled={installing}
                        submitting={installing}
                        color={installFailed ? Button.Colors.YELLOW : Button.Colors.GREEN}
                        look={Button.Looks.FILLED}
                        size={Button.Sizes.ICON}
                      >
                        <Icon name={installFailed ? 'retry' : 'cloud-download'} tooltip={installFailed ? 'Retry' : 'Install'} />
                      </Button>
                    </div>
                  )
              )}
              {!community && (
                <div className='vz-addon-card-toggle-wrapper'>
                  <Switch
                    className='vz-addon-card-toggle'
                    checked={isEnabled}
                    onChange={async v => onToggle(v)}
                  />
                </div>
              )}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
});

/**
 * Addon card in cover layout.
 * @component
 * @param {object} props
 * @param {object} props.addon Addon object
 * @param {string} props.type Addon type
 * @param {boolean} props.hasSettings Has settings
 * @param {boolean} props.isEnabled Is enabled
 * @param {boolean} props.isInstalled Is installed
 * @param {function()} props.onToggle Toggle handler
 */
const AddonCardCover = memo(({ addon, type, community, hasSettings, isEnabled, isInstalled, onToggle }) => {
  return (
    <div className='vz-addon-card-header-wrapper'>
      <div className='vz-addon-card-content-wrapper'>
        <div className='vz-addon-card-content'>
          <AddonIcon icon={addon.manifest.icon} />
          <div className='vz-addon-card-header'>
            <div className='vz-addon-card-metadata'>
              <div className='vz-addon-card-name-version'>
                <OverflowTooltip className='vz-addon-card-name' text={addon.manifest.name}>
                  {addon.manifest.name}
                </OverflowTooltip>
                <span className='vz-addon-card-version'>
                  {addon.manifest.version}
                </span>
              </div>
              <Author author={addon.manifest.author} />
            </div>
          </div>
          <Description description={addon.manifest.description} />
          <Permissions permissions={addon.manifest.permissions} />
          <Footer
            community={community}
            addon={addon}
            type={type}
            hasSettings={hasSettings}
            isEnabled={isEnabled}
            isInstalled={isInstalled}
            onToggle={onToggle}
          />
        </div>
      </div>
    </div>
  );
});

/**
 * Addon card in list layout.
 * @component
 * @param {object} props
 * @param {object} props.addon Addon object
 * @param {string} props.type Addon type
 * @param {boolean} props.hasSettings Has settings
 * @param {boolean} props.isEnabled Is enabled
 * @param {boolean} props.isInstalled Is installed
 * @param {function()} props.onToggle Toggle handler
 */
const AddonCardList = memo(({ addon, type, community, hasSettings, isEnabled, isInstalled, onToggle }) => {
  return (
    <div className='vz-addon-card-header-wrapper'>
      <AddonIcon icon={addon.manifest.icon} />
      <div className='vz-addon-card-content-wrapper'>
        <div className='vz-addon-card-content'>
          <div className='vz-addon-card-header'>
            <div className='vz-addon-card-metadata'>
              <div className='vz-addon-card-name-version'>
                <OverflowTooltip className='vz-addon-card-name' text={addon.manifest.name}>
                  {addon.manifest.name}
                </OverflowTooltip>
                <span className='vz-addon-card-version'>
                  {addon.manifest.version}
                </span>
              </div>
              <Author author={addon.manifest.author} />
            </div>
          </div>
          <Description description={addon.manifest.description} />
          <Permissions permissions={addon.manifest.permissions} />
          <Footer
            community={community}
            addon={addon}
            type={type}
            hasSettings={hasSettings}
            isEnabled={isEnabled}
            isInstalled={isInstalled}
            onToggle={onToggle}
          />
        </div>
      </div>
    </div>
  );
});

/**
 * Addon card. Displays information regarding a plugin/theme along with some action buttons.
 * @component
 * @param {object} props
 * @param {string} props.addonId Addon ID
 * @param {string} props.type Addon type
 * @param {string} props.display Addon card display type
 */
export default memo(({ addonId, type, display, community, isEnabled, isInstalled, hasSettings, hasUpdates }) => {
  isEnabled = typeof isEnabled === 'boolean' ? isEnabled : vizality.manager[toPlural(type)].isEnabled(addonId);
  isInstalled = typeof isInstalled === 'boolean' ? isInstalled : vizality.manager[toPlural(type)].isInstalled(addonId);
  hasSettings = typeof hasSettings === 'boolean' ? hasSettings : vizality.manager[toPlural(type)].hasSettings(addonId);
  const forceUpdate = useForceUpdate();

  /**
   * Handles addon enables and disables.
   * @param {object} payload Event payload
   * @param {string} payload.addonId Addon ID
   * @param {string} payload.type Addon type
   */
  const handleEnableDisable = payload => {
    /**
     * If this is the addon being enabled/disabled, force rerender the card.
     */
    if (payload.addonId === addonId && payload.type === type) {
      forceUpdate();
    }
  };

  /**
   * Add event listeners for addon enable and addon disable.
   */
  useEffect(() => {
    vizality.manager[toPlural(type)].on(Events.VIZALITY_ADDON_ENABLE, handleEnableDisable);
    vizality.manager[toPlural(type)].on(Events.VIZALITY_ADDON_DISABLE, handleEnableDisable);
    return () => {
      /**
       * Remove the event listeners.
       */
      vizality.manager[toPlural(type)].removeListener(Events.VIZALITY_ADDON_ENABLE, handleEnableDisable);
      vizality.manager[toPlural(type)].removeListener(Events.VIZALITY_ADDON_DISABLE, handleEnableDisable);
    };
  }, [ type, addonId, display, community, isEnabled, isInstalled, hasSettings ]);

  let addon;
  if (community) {
    addon = vizality.manager.community[toPlural(type)].get(addonId);
  } else {
    addon = vizality.manager[toPlural(type)].get(addonId);
  }

  if (!addon) {
    return;
  }

  const onToggle = async () => {
    if (vizality.manager[toPlural(type)].isEnabled(addonId)) {
      await vizality.manager[toPlural(type)].disable(addonId);
    } else {
      await vizality.manager[toPlural(type)].enable(addonId);
    }
  };

  /**
   * Handles addon card context menus.
   * @param {document#event:mousedown} evt Mousedown event
   * @returns {void}
   */
  const handleContextMenu = evt => {
    return openContextMenu(evt, () =>
      <AddonContextMenu
        community={community}
        addonId={addonId}
        type={type}
        hasSettings={hasSettings}
        isEnabled={isEnabled}
        isInstalled={isInstalled}
        onToggle={onToggle}
      />
    );
  };

  const Addon = ({ display }) => {
    switch (display) {
      case 'compact': return (
        <AddonCardCompact
          community={community}
          addon={addon}
          type={type}
          hasSettings={hasSettings}
          isEnabled={isEnabled}
          isInstalled={isInstalled}
          onToggle={onToggle}
        />
      );
      case 'cover': return (
        <AddonCardCover
          community={community}
          addon={addon}
          type={type}
          hasSettings={hasSettings}
          isEnabled={isEnabled}
          isInstalled={isInstalled}
          onToggle={onToggle}
        />
      );
      case 'list': return (
        <AddonCardList
          community={community}
          addon={addon}
          type={type}
          hasSettings={hasSettings}
          isEnabled={isEnabled}
          isInstalled={isInstalled}
          onToggle={onToggle}
        />
      );
      default: return (
        <AddonCard
          community={community}
          addon={addon}
          type={type}
          hasSettings={hasSettings}
          isEnabled={isEnabled}
          isInstalled={isInstalled}
          onToggle={onToggle}
        />
      );
    }
  };

  return (
    <div
      className='vz-addon-card'
      vz-addon-id={addonId}
      vz-addon-type={type}
      onContextMenu={handleContextMenu}
      onClick={evt => {
        if (evt.target.matches('input') || evt.target.matches('button') || evt.target.matches('svg') || evt.target.matches('a')) {
          return;
        }
        vizality.api.routes.navigateTo(`/vizality/${type}/${addonId}`);
      }}
    >
      <Addon display={display} />
    </div>
  );
});
