// @flow
import * as React from 'react';
import Card from 'component/common/card';
import TagsSearch from 'component/tagsSearch';
import Page from 'component/page';
import Button from 'component/button';
import ChannelSelector from 'component/channelSelector';
import Spinner from 'component/spinner';
import { FormField } from 'component/common/form-components/form-field';
import LbcSymbol from 'component/common/lbc-symbol';
import I18nMessage from 'component/i18nMessage';
import { isNameValid, parseURI } from 'lbry-redux';
import ClaimPreview from 'component/claimPreview';
import { getUriForSearchTerm } from 'util/search';

const DEBOUNCE_REFRESH_MS = 1000;

const LBC_MAX = 21000000;
const LBC_MIN = 0;
const LBC_STEP = 1.0;

type Props = {
  activeChannelClaim: ChannelClaim,
  settingsByChannelId: { [string]: PerChannelSettings },
  fetchingCreatorSettings: boolean,
  fetchingBlockedWords: boolean,
  moderationDelegatesById: { [string]: Array<{ channelId: string, channelName: string }> },
  commentBlockWords: (ChannelClaim, Array<string>) => void,
  commentUnblockWords: (ChannelClaim, Array<string>) => void,
  commentModAddDelegate: (string, string, ChannelClaim) => void,
  commentModRemoveDelegate: (string, string, ChannelClaim) => void,
  commentModListDelegates: (ChannelClaim) => void,
  fetchCreatorSettings: (channelId: string) => void,
  updateCreatorSettings: (ChannelClaim, PerChannelSettings) => void,
  doToast: ({ message: string }) => void,
};

export default function SettingsCreatorPage(props: Props) {
  const {
    activeChannelClaim,
    settingsByChannelId,
    moderationDelegatesById,
    commentBlockWords,
    commentUnblockWords,
    commentModAddDelegate,
    commentModRemoveDelegate,
    commentModListDelegates,
    fetchCreatorSettings,
    updateCreatorSettings,
    doToast,
  } = props;

  const [commentsEnabled, setCommentsEnabled] = React.useState(true);
  const [mutedWordTags, setMutedWordTags] = React.useState([]);
  const [moderatorTags, setModeratorTags] = React.useState([]);
  const [moderatorSearchTerm, setModeratorSearchTerm] = React.useState('');
  const [moderatorSearchError, setModeratorSearchError] = React.useState('');
  const [moderatorSearchClaimUri, setModeratorSearchClaimUri] = React.useState('');
  const [minTipAmountComment, setMinTipAmountComment] = React.useState(0);
  const [minTipAmountSuperChat, setMinTipAmountSuperChat] = React.useState(0);
  const [slowModeMinGap, setSlowModeMinGap] = React.useState(0);
  const [lastUpdated, setLastUpdated] = React.useState(1);

  /**
   * Updates corresponding GUI states with the given PerChannelSettings values.
   *
   * @param settings
   * @param fullSync If true, update all states and consider 'undefined' settings as "cleared/false";
   *                 if false, only update defined settings.
   */
  function settingsToStates(settings: PerChannelSettings, fullSync: boolean) {
    const doSetMutedWordTags = (words: Array<string>) => {
      const tagArray = Array.from(new Set(words));
      setMutedWordTags(
        tagArray
          .filter((t) => t !== '')
          .map((x) => {
            return { name: x };
          })
      );
    };

    if (fullSync) {
      setCommentsEnabled(settings.comments_enabled || false);
      setMinTipAmountComment(settings.min_tip_amount_comment || 0);
      setMinTipAmountSuperChat(settings.min_tip_amount_super_chat || 0);
      setSlowModeMinGap(settings.slow_mode_min_gap || 0);
      doSetMutedWordTags(settings.words || []);
    } else {
      if (settings.comments_enabled !== undefined) {
        setCommentsEnabled(settings.comments_enabled);
      }
      if (settings.min_tip_amount_comment !== undefined) {
        setMinTipAmountComment(settings.min_tip_amount_comment);
      }
      if (settings.min_tip_amount_super_chat !== undefined) {
        setMinTipAmountSuperChat(settings.min_tip_amount_super_chat);
      }
      if (settings.slow_mode_min_gap !== undefined) {
        setSlowModeMinGap(settings.slow_mode_min_gap);
      }
      if (settings.words) {
        doSetMutedWordTags(settings.words);
      }
    }
  }

  function setSettings(newSettings: PerChannelSettings) {
    settingsToStates(newSettings, false);
    updateCreatorSettings(activeChannelClaim, newSettings);
    setLastUpdated(Date.now());
  }

  function addMutedWords(newTags: Array<Tag>) {
    const validatedNewTags = [];
    newTags.forEach((newTag) => {
      if (!mutedWordTags.some((tag) => tag.name === newTag.name)) {
        validatedNewTags.push(newTag);
      }
    });

    if (validatedNewTags.length !== 0) {
      setMutedWordTags([...mutedWordTags, ...validatedNewTags]);
      commentBlockWords(
        activeChannelClaim,
        validatedNewTags.map((x) => x.name)
      );
      setLastUpdated(Date.now());
    }
  }

  function removeMutedWord(tagToRemove: Tag) {
    const newMutedWordTags = mutedWordTags.slice().filter((t) => t.name !== tagToRemove.name);
    setMutedWordTags(newMutedWordTags);
    commentUnblockWords(activeChannelClaim, ['', tagToRemove.name]);
    setLastUpdated(Date.now());
  }

  function addModerator(newTags: Array<Tag>) {
    // Ignoring multiple entries for now, although <TagsSearch> supports it.
    let modUri;
    try {
      modUri = parseURI(newTags[0].name);
    } catch (e) {}

    if (modUri && modUri.isChannel && modUri.claimName && modUri.claimId) {
      if (!moderatorTags.some((modTag) => modTag.name === newTags[0].name)) {
        setModeratorTags([...moderatorTags, newTags[0]]);
        commentModAddDelegate(modUri.claimId, modUri.claimName, activeChannelClaim);
        setLastUpdated(Date.now());
      }
    } else {
      doToast({ message: __('Invalid channel URL "%url%"', { url: newTags[0].name }), isError: true });
    }
  }

  function removeModerator(tagToRemove: Tag) {
    let modUri;
    try {
      modUri = parseURI(tagToRemove.name);
    } catch (e) {}

    if (modUri && modUri.isChannel && modUri.claimName && modUri.claimId) {
      const newModeratorTags = moderatorTags.slice().filter((t) => t.name !== tagToRemove.name);
      setModeratorTags(newModeratorTags);
      commentModRemoveDelegate(modUri.claimId, modUri.claimName, activeChannelClaim);
      setLastUpdated(Date.now());
    }
  }

  function handleChannelSearchSelect(claim) {
    if (claim && claim.name && claim.claim_id) {
      addModerator([{ name: claim.name + '#' + claim.claim_id }]);
    }
  }

  // 'moderatorSearchTerm' to 'moderatorSearchClaimUri'
  React.useEffect(() => {
    if (!moderatorSearchTerm) {
      setModeratorSearchError('');
      setModeratorSearchClaimUri('');
    } else {
      const [searchUri, error] = getUriForSearchTerm(moderatorSearchTerm);
      setModeratorSearchError(error ? __('Something not quite right..') : '');

      try {
        const { streamName, channelName, isChannel } = parseURI(searchUri);

        if (!isChannel && streamName && isNameValid(streamName)) {
          setModeratorSearchError(__('Not a channel (prefix with "@", or enter the channel URL)'));
          setModeratorSearchClaimUri('');
        } else if (isChannel && channelName && isNameValid(channelName)) {
          setModeratorSearchClaimUri(searchUri);
        }
      } catch (e) {
        if (moderatorSearchTerm !== '@') {
          setModeratorSearchError('');
        }
        setModeratorSearchClaimUri('');
      }
    }
  }, [moderatorSearchTerm, setModeratorSearchError]);

  // Update local moderator states with data from API.
  React.useEffect(() => {
    commentModListDelegates(activeChannelClaim);
  }, [activeChannelClaim, commentModListDelegates]);

  React.useEffect(() => {
    if (activeChannelClaim) {
      const delegates = moderationDelegatesById[activeChannelClaim.claim_id];
      if (delegates) {
        setModeratorTags(
          delegates.map((d) => {
            return {
              name: d.channelName + '#' + d.channelId,
            };
          })
        );
      } else {
        setModeratorTags([]);
      }
    }
  }, [activeChannelClaim, moderationDelegatesById]);

  // Update local states with data from API.
  React.useEffect(() => {
    if (lastUpdated !== 0 && Date.now() - lastUpdated < DEBOUNCE_REFRESH_MS) {
      // Still debouncing. Skip update.
      return;
    }

    if (activeChannelClaim && settingsByChannelId && settingsByChannelId[activeChannelClaim.claim_id]) {
      const channelSettings = settingsByChannelId[activeChannelClaim.claim_id];
      settingsToStates(channelSettings, true);
    }
  }, [activeChannelClaim, settingsByChannelId, lastUpdated]);

  // Re-sync list on first idle time; mainly to correct any invalid settings.
  React.useEffect(() => {
    if (lastUpdated && activeChannelClaim) {
      const timer = setTimeout(() => {
        fetchCreatorSettings(activeChannelClaim.claim_id);
      }, DEBOUNCE_REFRESH_MS);
      return () => clearTimeout(timer);
    }
  }, [lastUpdated, activeChannelClaim, fetchCreatorSettings]);

  const isBusy =
    !activeChannelClaim || !settingsByChannelId || settingsByChannelId[activeChannelClaim.claim_id] === undefined;
  const isDisabled =
    activeChannelClaim && settingsByChannelId && settingsByChannelId[activeChannelClaim.claim_id] === null;

  return (
    <Page
      noFooter
      noSideNavigation
      backout={{
        title: __('Creator settings'),
        backLabel: __('Done'),
      }}
      className="card-stack"
    >
      <ChannelSelector hideAnon />
      {isBusy && (
        <div className="main--empty">
          <Spinner />
        </div>
      )}
      {isDisabled && (
        <Card
          title={__('Settings unavailable for this channel')}
          subtitle={__("This channel isn't staking enough LBRY Credits to enable Creator Settings.")}
        />
      )}
      {!isBusy && !isDisabled && (
        <>
          <Card
            title={__('General')}
            actions={
              <>
                <FormField
                  type="checkbox"
                  name="comments_enabled"
                  label={__('Enable comments for channel.')}
                  checked={commentsEnabled}
                  onChange={() => setSettings({ comments_enabled: !commentsEnabled })}
                />
                <FormField
                  name="slow_mode_min_gap"
                  label={__('Minimum time gap in seconds between comments (affects livestream chat as well).')}
                  min={0}
                  step={1}
                  type="number"
                  placeholder="1"
                  value={slowModeMinGap}
                  onChange={(e) => setSettings({ slow_mode_min_gap: parseInt(e.target.value) })}
                />
              </>
            }
          />
          <Card
            title={__('Filter')}
            actions={
              <div className="tag--blocked-words">
                <TagsSearch
                  label={__('Muted words')}
                  labelAddNew={__('Add words')}
                  labelSuggestions={__('Suggestions')}
                  onRemove={removeMutedWord}
                  onSelect={addMutedWords}
                  disableAutoFocus
                  tagsPassedIn={mutedWordTags}
                  placeholder={__('Add words to block')}
                  hideSuggestions
                  disableControlTags
                />
              </div>
            }
          />
          <Card
            title={__('Tip')}
            actions={
              <>
                <FormField
                  name="min_tip_amount_comment"
                  label={
                    <I18nMessage tokens={{ lbc: <LbcSymbol /> }}>Minimum %lbc% tip amount for comments</I18nMessage>
                  }
                  helper={__(
                    'Enabling a minimum amount to comment will force all comments, including livestreams, to have tips associated with them. This can help prevent spam.'
                  )}
                  className="form-field--price-amount"
                  max={LBC_MAX}
                  min={LBC_MIN}
                  step={LBC_STEP}
                  type="number"
                  placeholder="3.14"
                  value={minTipAmountComment}
                  onChange={(e) => setSettings({ min_tip_amount_comment: parseFloat(e.target.value) })}
                />
                <FormField
                  name="min_tip_amount_super_chat"
                  label={
                    <I18nMessage tokens={{ lbc: <LbcSymbol /> }}>Minimum %lbc% tip amount for hyperchats</I18nMessage>
                  }
                  helper={
                    <>
                      {__(
                        'Enabling a minimum amount to hyperchat will force all TIPPED comments to have this value in order to be shown. This still allows regular comments to be posted.'
                      )}
                      {minTipAmountComment !== 0 && (
                        <p className="help--inline">
                          <em>{__('(This settings is not applicable if all comments require a tip.)')}</em>
                        </p>
                      )}
                    </>
                  }
                  className="form-field--price-amount"
                  min={0}
                  step="any"
                  type="number"
                  placeholder="1"
                  value={minTipAmountSuperChat}
                  disabled={minTipAmountComment !== 0}
                  onChange={(e) => setSettings({ min_tip_amount_super_chat: parseFloat(e.target.value) })}
                />
              </>
            }
          />
          <Card
            title={__('Delegation')}
            className="card--enable-overflow"
            actions={
              <div className="tag--blocked-words">
                <FormField
                  type="text"
                  name="moderator_search"
                  className="form-field--address"
                  label={__('Add moderator')}
                  placeholder={__('Enter a @username or URL')}
                  helper={__('examples: @channel, @channel#3, https://odysee.com/@Odysee:8, lbry://@Odysee#8')}
                  value={moderatorSearchTerm}
                  onChange={(e) => setModeratorSearchTerm(e.target.value)}
                  error={moderatorSearchError}
                />
                {moderatorSearchClaimUri && (
                  <div className="section">
                    <ClaimPreview
                      key={moderatorSearchClaimUri}
                      uri={moderatorSearchClaimUri}
                      // type={'small'}
                      // showNullPlaceholder
                      hideMenu
                      hideRepostLabel
                      disableNavigation
                      properties={''}
                      renderActions={(claim) => {
                        return (
                          <Button
                            requiresAuth
                            button="primary"
                            label={__('Add as moderator')}
                            onClick={() => handleChannelSearchSelect(claim)}
                          />
                        );
                      }}
                    />
                  </div>
                )}
                <TagsSearch
                  label={__('Moderators')}
                  labelAddNew={__('Add moderator')}
                  onRemove={removeModerator}
                  onSelect={addModerator}
                  tagsPassedIn={moderatorTags}
                  disableAutoFocus
                  hideInputField
                  hideSuggestions
                  disableControlTags
                />
              </div>
            }
          />
        </>
      )}
    </Page>
  );
}
