import React, {
  useCallback, useContext, useEffect, useRef, useState,
} from 'react';
import PropTypes from 'prop-types';

import {
  Button, Form, Spinner, StatefulButton,
} from '@openedx/paragon';
import { Help, Post } from '@openedx/paragon/icons';
import { Formik } from 'formik';
import { isEmpty } from 'lodash';
import { useGoogleReCaptcha } from 'react-google-recaptcha-v3';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import * as Yup from 'yup';

import { useIntl } from '@edx/frontend-platform/i18n';
import { AppContext } from '@edx/frontend-platform/react';

import { TinyMCEEditor } from '../../../components';
import FormikErrorFeedback from '../../../components/FormikErrorFeedback';
import PostHelpPanel from '../../../components/PostHelpPanel';
import PostPreviewPanel from '../../../components/PostPreviewPanel';
import useDispatchWithState from '../../../data/hooks';
import selectCourseCohorts from '../../cohorts/data/selectors';
import fetchCourseCohorts from '../../cohorts/data/thunks';
import DiscussionContext from '../../common/context';
import withPostingRestrictions from '../../common/withPostingRestrictions';
import { useCurrentDiscussionTopic } from '../../data/hooks';
import {
  selectAnonymousPostingConfig,
  selectCaptchaSettings,
  selectContentCreationRateLimited,
  selectDivisionSettings,
  selectEnableInContext,
  selectIsNotifyAllLearnersEnabled,
  selectIsUserLearner,
  selectModerationSettings,
  selectUserHasModerationPrivileges,
  selectUserIsGroupTa,
  selectUserIsStaff,
} from '../../data/selectors';
import EmptyPage from '../../empty-posts/EmptyPage';
import {
  selectArchivedTopics,
  selectCoursewareTopics as inContextCourseware,
  selectNonCoursewareIds as inContextCoursewareIds,
  selectNonCoursewareTopics as inContextNonCourseware,
} from '../../in-context-topics/data/selectors';
import { selectCoursewareTopics, selectNonCoursewareIds, selectNonCoursewareTopics } from '../../topics/data/selectors';
import { updateUserDiscussionsTourByName } from '../../tours/data';
import {
  discussionsPath, formikCompatibleHandler, isFormikFieldInvalid, useCommentsPagePath,
} from '../../utils';
import { hidePostEditor } from '../data';
import { selectThread } from '../data/selectors';
import { createNewThread, fetchThread, updateExistingThread } from '../data/thunks';
import messages from './messages';
import PostTypeCard from './PostTypeCard';

const PostEditor = ({
  editExisting, openRestrictionDialogue,
}) => {
  const intl = useIntl();
  const navigate = useNavigate();
  const location = useLocation();
  const dispatch = useDispatch();
  const editorRef = useRef(null);
  const { executeRecaptcha } = useGoogleReCaptcha();
  const { courseId, postId } = useParams();
  const { authenticatedUser } = useContext(AppContext);
  const { category, enableInContextSidebar } = useContext(DiscussionContext);
  const topicId = useCurrentDiscussionTopic();
  const commentsPagePath = useCommentsPagePath();
  const [submitting, dispatchSubmit] = useDispatchWithState();
  const [captchaError, setCaptchaError] = useState('');
  const enableInContext = useSelector(selectEnableInContext);
  const nonCoursewareTopics = useSelector(enableInContext ? inContextNonCourseware : selectNonCoursewareTopics);
  const nonCoursewareIds = useSelector(enableInContext ? inContextCoursewareIds : selectNonCoursewareIds);
  const coursewareTopics = useSelector(enableInContext ? inContextCourseware : selectCoursewareTopics);
  const cohorts = useSelector(selectCourseCohorts);
  const post = useSelector(editExisting ? selectThread(postId) : () => ({}));
  const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
  const userIsGroupTa = useSelector(selectUserIsGroupTa);
  const settings = useSelector(selectDivisionSettings);
  const { allowAnonymous, allowAnonymousToPeers } = useSelector(selectAnonymousPostingConfig);
  const { editReasons } = useSelector(selectModerationSettings);
  const userIsStaff = useSelector(selectUserIsStaff);
  const archivedTopics = useSelector(selectArchivedTopics);
  const postEditorId = `post-editor-${editExisting ? postId : 'new'}`;
  const isNotifyAllLearnersEnabled = useSelector(selectIsNotifyAllLearnersEnabled);
  const captchaSettings = useSelector(selectCaptchaSettings);
  const isUserLearner = useSelector(selectIsUserLearner);
  const contentCreationRateLimited = useSelector(selectContentCreationRateLimited);
  const shouldRequireCaptcha = !postId && captchaSettings.enabled && isUserLearner;

  const canDisplayEditReason = (editExisting
    && (userHasModerationPrivileges || userIsGroupTa || userIsStaff)
    && post?.author !== authenticatedUser.username
  );

  const editReasonCodeValidation = canDisplayEditReason && {
    editReasonCode: Yup.string().required(intl.formatMessage(messages.editReasonCodeError)),
  };

  const enableNotifyAllLearnersTour = useCallback((enabled) => {
    const data = {
      enabled,
      tourName: 'notify_all_learners',
    };
    dispatch(updateUserDiscussionsTourByName(data));
  }, []);

  useEffect(() => {
    enableNotifyAllLearnersTour(true);
    return () => {
      enableNotifyAllLearnersTour(false);
    };
  }, [enableNotifyAllLearnersTour]);

  const canSelectCohort = useCallback((tId) => {
    // If the user isn't privileged, they can't edit the cohort.
    // If the topic is being edited the cohort can't be changed.
    if (!userHasModerationPrivileges) {
      return false;
    }
    if (nonCoursewareIds.includes(tId)) {
      return settings.dividedCourseWideDiscussions.includes(tId);
    }
    const isCohorting = settings.alwaysDivideInlineDiscussions || settings.dividedInlineDiscussions.includes(tId);
    return isCohorting;
  }, [nonCoursewareIds, settings, userHasModerationPrivileges]);

  const initialValues = {
    postType: post?.type || 'discussion',
    topic: post?.topicId || topicId || nonCoursewareTopics?.[0]?.id,
    title: post?.title || '',
    comment: post?.rawBody || '',
    follow: isEmpty(post?.following) ? true : post?.following,
    notifyAllLearners: false,
    anonymous: allowAnonymous ? false : undefined,
    anonymousToPeers: allowAnonymousToPeers ? false : undefined,
    cohort: post?.cohort || 'default',
    editReasonCode: post?.lastEdit?.reasonCode || (
      userIsStaff && canDisplayEditReason ? 'violates-guidelines' : undefined
    ),
  };

  const hideEditor = useCallback((resetForm) => {
    resetForm({ values: initialValues });
    if (editExisting) {
      const newLocation = discussionsPath(commentsPagePath, {
        courseId,
        topicId,
        postId,
        learnerUsername: post?.author,
        category,
      })(location);
      navigate({ ...newLocation });
    }
  }, [postId, topicId, post?.author, category, editExisting, commentsPagePath, location]);

  useEffect(() => {
    if (contentCreationRateLimited && !postId) {
      openRestrictionDialogue();
    }
  }, [contentCreationRateLimited, postId]);

  // null stands for no cohort restriction ("All learners" option)
  const selectedCohort = useCallback(
    (cohort) => (
      cohort === 'default' ? null : cohort),
    [],
  );

  const submitForm = useCallback(async (values, { resetForm }) => {
    let recaptchaToken;
    if (shouldRequireCaptcha && executeRecaptcha) {
      try {
        recaptchaToken = await executeRecaptcha('submit_post');
        if (!recaptchaToken) {
          setCaptchaError(intl.formatMessage(messages.captchaVerificationLabel));
          return;
        }
      } catch (error) {
        setCaptchaError(intl.formatMessage(messages.captchaVerificationLabel));
        return;
      }
      setCaptchaError('');
    }

    if (editExisting) {
      await dispatchSubmit(updateExistingThread(postId, {
        topicId: values.topic,
        type: values.postType,
        title: values.title,
        content: values.comment,
        editReasonCode: values.editReasonCode || undefined,
      }));
    } else {
      const cohort = canSelectCohort(values.topic) ? selectedCohort(values.cohort) : undefined;

      await dispatchSubmit(createNewThread({
        courseId,
        topicId: values.topic,
        type: values.postType,
        title: values.title,
        content: values.comment,
        following: values.follow,
        anonymous: allowAnonymous ? values.anonymous : undefined,
        anonymousToPeers: allowAnonymousToPeers ? values.anonymousToPeers : undefined,
        cohort,
        enableInContextSidebar,
        notifyAllLearners: values.notifyAllLearners,
        ...(shouldRequireCaptcha && recaptchaToken ? { recaptchaToken } : {}),
      }));
    }

    if (editorRef.current) {
      editorRef.current.plugins.autosave.removeDraft();
    }
    hideEditor(resetForm);
  }, [
    allowAnonymous, allowAnonymousToPeers, canSelectCohort, editExisting,
    enableInContextSidebar, hideEditor, postId, selectedCohort, topicId, shouldRequireCaptcha, executeRecaptcha,
  ]);

  useEffect(() => {
    if (userHasModerationPrivileges && isEmpty(cohorts)) {
      dispatch(fetchCourseCohorts(courseId));
    }
    if (editExisting) {
      dispatchSubmit(fetchThread(postId, courseId));
    }
  }, [courseId, editExisting]);

  if (editExisting && !post) {
    if (submitting) {
      return (
        <div className="m-4 card p-4 align-items-center">
          <Spinner animation="border" variant="primary" />
        </div>
      );
    }
    if (!submitting) {
      return (
        <EmptyPage
          title={intl.formatMessage(messages.noThreadFound)}
        />
      );
    }
  }

  const validationSchema = Yup.object().shape({
    postType: Yup.mixed()
      .oneOf(['discussion', 'question']),
    topic: Yup.string()
      .required(),
    title: Yup.string()
      .required(intl.formatMessage(messages.titleError)),
    comment: Yup.string()
      .required(intl.formatMessage(messages.commentError)),
    follow: Yup.bool()
      .default(true),
    anonymous: Yup.bool()
      .default(false)
      .nullable(),
    anonymousToPeers: Yup.bool()
      .default(false)
      .nullable(),
    notifyAllLearners: Yup.bool()
      .default(false),
    cohort: Yup.string()
      .nullable()
      .default(null),
    ...editReasonCodeValidation,
  });

  const handleInContextSelectLabel = (section, subsection) => (
    `${section.displayName} / ${subsection.displayName}` || intl.formatMessage(messages.unnamedTopics)
  );

  return (
    <Formik
      enableReinitialize
      initialValues={initialValues}
      validationSchema={validationSchema}
      onSubmit={submitForm}
    >{({
      values,
      errors,
      touched,
      handleSubmit,
      handleBlur,
      handleChange,
      resetForm,
    }) => (
      <Form className="m-4 card p-4 post-form" onSubmit={handleSubmit}>
        <h4 className="mb-4 font-style" style={{ lineHeight: '16px' }}>
          {editExisting
            ? intl.formatMessage(messages.editPostHeading)
            : intl.formatMessage(messages.addPostHeading)}
        </h4>
        <Form.RadioSet
          name="postType"
          className="d-flex flex-row flex-wrap"
          value={values.postType}
          onChange={handleChange}
          onBlur={handleBlur}
          aria-label={intl.formatMessage(messages.postTitle)}
        >
          <PostTypeCard
            value="discussion"
            selected={values.postType === 'discussion'}
            type={intl.formatMessage(messages.discussionType)}
            icon={<Post />}
          />
          <PostTypeCard
            value="question"
            selected={values.postType === 'question'}
            type={intl.formatMessage(messages.questionType)}
            icon={<Help />}
          />
        </Form.RadioSet>
        <div className="d-flex flex-row my-4.5 justify-content-between">
          <Form.Group className="w-100 m-0">
            <Form.Control
              className="m-0"
              name="topic"
              as="select"
              value={values.topic}
              onChange={handleChange}
              onBlur={handleBlur}
              aria-describedby="topicAreaInput"
              floatingLabel={intl.formatMessage(messages.topicArea)}
              disabled={enableInContextSidebar}
              data-testid="topic-select"
            >
              {nonCoursewareTopics.map(topic => (
                <option
                  key={topic.id}
                  value={topic.id}
                >{topic.name || intl.formatMessage(messages.unnamedSubTopics)}
                </option>
              ))}
              {enableInContext ? (
                <>
                  {coursewareTopics?.map(section => (
                    section?.children?.map(subsection => (
                      <optgroup
                        label={handleInContextSelectLabel(section, subsection)}
                        key={subsection.id}
                      >
                        {subsection?.children?.map(unit => (
                          <option key={unit.id} value={unit.id}>
                            {unit.name || intl.formatMessage(messages.unnamedSubTopics)}
                          </option>
                        ))}
                      </optgroup>
                    ))
                  ))}
                  {(userIsStaff || userIsGroupTa || userHasModerationPrivileges) && (
                  <optgroup label={intl.formatMessage(messages.archivedTopics)}>
                    {archivedTopics.map(topic => (
                      <option key={topic.id} value={topic.id}>
                        {topic.name || intl.formatMessage(messages.unnamedSubTopics)}
                      </option>
                    ))}
                  </optgroup>
                  )}
                </>
              ) : (
                coursewareTopics.map(categoryObj => (
                  <optgroup
                    label={categoryObj.name || intl.formatMessage(messages.unnamedTopics)}
                    key={categoryObj.id}
                  >
                    {categoryObj.topics.map(subtopic => (
                      <option key={subtopic.id} value={subtopic.id}>
                        {subtopic.name || intl.formatMessage(messages.unnamedSubTopics)}
                      </option>
                    ))}
                  </optgroup>
                ))
              )}
            </Form.Control>
          </Form.Group>
          {canSelectCohort(values.topic) && (
          <Form.Group className="w-100 ml-3 mb-0">
            <Form.Control
              className="m-0"
              name="cohort"
              as="select"
              value={values.cohort}
              onChange={handleChange}
              onBlur={handleBlur}
              aria-describedby="cohortAreaInput"
              floatingLabel={intl.formatMessage(messages.cohortVisibility)}
            >
              <option value="default">{intl.formatMessage(messages.cohortVisibilityAllLearners)}</option>
              {cohorts.map(cohort => (
                <option key={cohort.id} value={cohort.id}>{cohort.name}</option>
              ))}
            </Form.Control>
          </Form.Group>
          )}
        </div>
        <div className="d-flex flex-row mb-4.5 justify-content-between">
          <Form.Group
            className="w-100 m-0"
            isInvalid={isFormikFieldInvalid('title', {
              errors,
              touched,
            })}
          >
            <Form.Control
              className="m-0"
              name="title"
              type="text"
              onChange={handleChange}
              onBlur={handleBlur}
              aria-describedby="titleInput"
              floatingLabel={intl.formatMessage(messages.postTitle)}
              value={values.title}
              data-testid="post-title-input"
            />
            <FormikErrorFeedback name="title" />
          </Form.Group>
          {canDisplayEditReason && (
          <Form.Group
            className="w-100 ml-4 mb-0"
            isInvalid={isFormikFieldInvalid('editReasonCode', {
              errors,
              touched,
            })}
          >
            <Form.Control
              name="editReasonCode"
              className="m-0"
              as="select"
              value={values.editReasonCode}
              onChange={handleChange}
              onBlur={handleBlur}
              aria-describedby="editReasonCodeInput"
              floatingLabel={intl.formatMessage(messages.editReasonCode)}
            >
              <option key="empty" value="">---</option>
              {editReasons.map(({ code, label }) => (
                <option key={code} value={code}>{label}</option>
              ))}
            </Form.Control>
            <FormikErrorFeedback name="editReasonCode" />
          </Form.Group>
          )}
        </div>
        <div className="mb-3">
          <TinyMCEEditor
            onInit={
                /* istanbul ignore next: TinyMCE is mocked so this cannot be easily tested */
                (_, editor) => {
                  editorRef.current = editor;
                }
              }
            id={postEditorId}
            value={values.comment}
            onEditorChange={formikCompatibleHandler(handleChange, 'comment')}
            onBlur={formikCompatibleHandler(handleBlur, 'comment')}
          />
          <PostHelpPanel />
          <FormikErrorFeedback name="comment" />
        </div>
        <PostPreviewPanel htmlNode={values.comment} isPost editExisting={editExisting} />
        <div className="d-flex flex-row mt-n4 w-75 text-primary font-style">
          {!editExisting && (
          <>
            {isNotifyAllLearnersEnabled && (
            <Form.Group>
              <Form.Checkbox
                name="notifyAllLearners"
                id="notify-learners"
                checked={values.notifyAllLearners}
                onChange={handleChange}
                onBlur={handleBlur}
                className="mr-4.5"
              >
                <span>
                  {intl.formatMessage(messages.notifyAllLearners)}
                </span>
              </Form.Checkbox>
            </Form.Group>
            )}
            <Form.Group>
              <Form.Checkbox
                name="follow"
                checked={values.follow}
                onChange={handleChange}
                onBlur={handleBlur}
                className="mr-4.5"
              >
                <span>
                  {intl.formatMessage(messages.followPost)}
                </span>
              </Form.Checkbox>
            </Form.Group>
            {allowAnonymousToPeers && (
            <Form.Group>
              <Form.Checkbox
                name="anonymousToPeers"
                checked={values.anonymousToPeers}
                onChange={handleChange}
                onBlur={handleBlur}
              >
                <span>
                  {intl.formatMessage(messages.anonymousToPeersPost)}
                </span>
              </Form.Checkbox>
            </Form.Group>
            )}
          </>
          )}
        </div>
        { shouldRequireCaptcha && captchaSettings.siteKey && captchaError && (
        <div className="mb-3 pgn__form-text-invalid pgn__form-text">
          {captchaError}
        </div>
        )}
        <div className="d-flex justify-content-end">
          <Button
            variant="outline-primary"
            onClick={() => {
              dispatch(hidePostEditor());
              hideEditor(resetForm);
            }}
          >
            {intl.formatMessage(messages.cancel)}
          </Button>
          <StatefulButton
            labels={{
              default: intl.formatMessage(messages.submit),
              pending: intl.formatMessage(messages.submitting),
            }}
            state={submitting ? 'pending' : 'default'}
            className="ml-2"
            variant="primary"
            onClick={handleSubmit}
          />
        </div>
      </Form>
    )}
    </Formik>
  );
};

PostEditor.propTypes = {
  editExisting: PropTypes.bool,
  openRestrictionDialogue: PropTypes.func.isRequired,
};

PostEditor.defaultProps = {
  editExisting: false,
};

export default React.memo(withPostingRestrictions(PostEditor));
