// 3rd-Party Libraries
import React, { useRef, useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import cloneDeep from 'lodash/cloneDeep';
import findIndex from 'lodash/findIndex';
import debounce from 'lodash/debounce';

// Components
import { Button, Flex, Input, Modal, Notification, Title } from '@ui/components';
import { TutorialCard, TutorialObstacle, TutorialTile } from '@ui/compositions';
import TutorialEditor from './components/TutorialEditor';
import TutorialStep from './components/TutorialStep';
import TutorialStepNav from './components/TutorialStepNav';
import TutorialResponse from './components/TutorialResponse';
import TutorialReview from './components/TutorialReview';

// Constants
import { DEFAULT_TUTORIAL, DEFAULT_STEP, DEFAULT_SNIPPET } from './constants/stepDefaults';
import { TutorialProp } from './proptypes/tutorials';

// Internals
import * as ApiActions from './apiActions';
import useReadmeApi from '@core/hooks/useReadmeApi';

// Style
let classes = {};
/* istanbul ignore next */
if (typeof window !== 'undefined' || typeof _COMPILED_ === 'undefined') classes = require('./style.module.scss');

// Permissions
export const PermissionContext = React.createContext(true);

export default function TutorialModal({
  action,
  baseUrl,
  closeTutorialModal,
  confirmModal,
  onboarding,
  moduleEnabled,
  referenceEnabled,
  requestDataRefresh,
  setConfirmModal,
  target,
  tutorial,
  ...props
}) {
  const modalRef = React.createRef();
  const tutorialForm = useRef(false);
  // Modal interaction state
  const [open, setOpenState] = useState(props.open);
  const [selectedStep, setSelectedStep] = useState(0);
  const [selectedTab, setSelectedTab] = useState(0);
  const [tutorialReview, setTutorialReview] = useState(false);
  const [tutorialCreated, setTutorialCreated] = useState(false);
  // Modal data state
  const [steps, setSteps] = useState(tutorial.steps);
  const [snippet, setSnippet] = useState(tutorial.snippet);
  const [tutorialShell, setTutorialShell] = useState(tutorial);
  // Indirect request handling (via API)
  const [endpoints, setEndpoints] = useState([]);
  const { response, error, setError, initRequest } = useReadmeApi(baseUrl);

  // Applying outer props to modal component ref
  useEffect(() => {
    if (props.open !== open) {
      if (tutorial.steps[selectedStep]) tutorial.steps[selectedStep].isOpen = false;
      setSelectedStep(0);
      setSteps([...tutorial.steps]);
      setOpenState(props.open);
      modalRef.current.toggle(props.open);
      document.body.style.overflow = '';

      if (props.open) {
        setTutorialCreated(false);
        setTutorialReview(false);
        document.body.style.overflow = 'hidden';
      }
    }
  });

  /* Start: Applying Hub2 API request data to state */
  useEffect(() => {
    if (open && action !== 'View' && !endpoints.length) ApiActions.getEndpoints(initRequest);

    tutorial.steps[selectedStep].isOpen = true;
  }, [open]);

  useEffect(() => {
    if (response.endpoints) setEndpoints(response.endpoints);
  }, [response.endpoints]);

  useEffect(() => {
    if (response.tutorial) {
      setTutorialShell({ ...response.tutorial });
      setSnippet({ ...response.tutorial.snippet });
      requestDataRefresh();
      setError(null);

      if (onboarding.published) closeTutorialModal();
    }
  }, [response.tutorial]);

  useEffect(() => {
    if (action === 'Create' && onboarding.published) setTutorialCreated(true);
  }, [onboarding]);

  useEffect(() => {
    if (response.snippet) setSnippet(response.snippet);
  }, [response.snippet]);

  useEffect(() => {
    if (response.codeString) {
      setSnippet(prevSnippet => {
        const { code, index } = response.codeString;
        prevSnippet.codeOptions[index].code = code;
        return { ...prevSnippet };
      });
    }
  }, [response.codeString]);
  /* End: Applying Hub2 API request data to state */

  /* Start: Applying new prop data to state */
  useEffect(() => {
    setSelectedStep(0);
    tutorial.steps[0].isOpen = true;
    setSteps([...tutorial.steps]);
  }, [tutorial.steps]);

  useEffect(() => {
    setSelectedTab(0);
    setSnippet({ ...tutorial.snippet });
  }, [tutorial.snippet]);

  useEffect(() => {
    setTutorialShell({ ...tutorial });
  }, [tutorial]);
  /* End: Applying new prop data to state */

  /* Start Syncing States */
  // Syncing Step and Snippet states to parent tutorial
  useEffect(() => {
    setTutorialShell(prevTutorial => {
      return {
        ...prevTutorial,
        stepCount: steps.length,
        steps,
        snippet,
      };
    });
  }, [steps, snippet]);
  // Syncing steps[n].lineNumbers with snippet.codeOptions
  // These are disjointed arrays which can be referenced in ./propTypes/tutorials
  // Which deal with highly related, but disparate data. We need to ensure
  // that they have the same number of elements. We can do that by reading from `codeOptions`.
  useEffect(() => {
    const tabCount = snippet.codeOptions.length;

    setSteps(prevSteps => {
      prevSteps.forEach(({ lineNumbers }) => {
        if (lineNumbers.length > tabCount) lineNumbers.splice(selectedTab, 1);
        if (lineNumbers.length < tabCount) lineNumbers.push('');
      });
      return prevSteps;
    });
  }, [snippet.codeOptions.length]);
  /* End Syncing States */

  const navStep = direction => {
    setSteps(prevSteps => {
      const newSelection = selectedStep + direction;

      prevSteps[selectedStep].isOpen = false;
      prevSteps[newSelection].isOpen = true;
      setSelectedStep(newSelection);

      return [...prevSteps];
    });
  };

  const validateStep = () => tutorialForm.current.reportValidity();

  const selectStep = index => {
    if (validateStep()) {
      setSelectedStep(index);
      setSteps(prevSteps => {
        const step = prevSteps[index];
        const previousStep = prevSteps.find(s => s.isOpen);

        previousStep.isOpen = false;
        step.isOpen = true;

        return [...prevSteps];
      });
    }
  };

  const shiftStepOrder = (index, direction) => {
    setSteps(prevSteps => {
      const swap = index + direction;
      // https://stackoverflow.com/a/872317
      // Acts a means to swap indices without assigning to a temp variable
      [prevSteps[index], prevSteps[swap]] = [prevSteps[swap], prevSteps[index]];

      const updatedSelection = findIndex(steps, s => s.isOpen);
      setSelectedStep(updatedSelection);

      return [...prevSteps];
    });
  };

  const createStep = () => {
    if (validateStep()) {
      setSelectedStep(steps.length);
      setSteps(prevSteps => {
        const prevOpen = prevSteps.find(s => s.isOpen);
        prevOpen.isOpen = false;

        return [
          ...prevSteps,
          {
            ...cloneDeep(DEFAULT_STEP),
            lineNumbers: snippet.codeOptions.map(() => ''),
          },
        ];
      });
    }
  };

  const updateStep = data => {
    const [key, value] = Object.entries(data)[0];

    setSteps(prevSteps => {
      const step = prevSteps[selectedStep];
      step[key] = value;
      return [...prevSteps];
    });
  };

  const deleteStep = index => {
    setSelectedStep(0);
    setSteps(prevSteps => {
      prevSteps.splice(index, 1);
      prevSteps[0].isOpen = true;

      return [...prevSteps];
    });
  };

  const updateSnippet = data => {
    setSnippet(prevSnippet => {
      return {
        ...prevSnippet,
        ...data,
      };
    });
  };

  const clearSnippetSelection = () => {
    setSelectedTab(0);
    setSnippet(cloneDeep(DEFAULT_SNIPPET));
  };

  const updateTutorial = data => {
    setTutorialShell(prevShell => {
      return {
        ...prevShell,
        ...data,
      };
    });
  };

  const upsertTutorial = e => {
    e.preventDefault();
    if (validateStep()) {
      if (tutorialShell._id) return ApiActions.updateTutorial(initRequest, tutorialShell);
      return ApiActions.createTutorial(initRequest, tutorialShell);
    }
    return false;
  };

  const debounceInputChange = debounce(inputData => {
    updateTutorial(inputData);
  }, 300);

  const confirmEndpoint = data => {
    const code = snippet.codeOptions.map(opt => opt.code).join('');
    if (!code.length) return ApiActions.getCodeSnippets(initRequest, data);

    confirmModal.current.toggle(true);
    return setConfirmModal({
      title: 'Are you sure you want to choose a new endpoint?',
      body: `Choosing a new endpoint will replace all current code`,
      action: ApiActions.getCodeSnippets.bind(null, initRequest, data),
      buttonText: `Choose endpoint`,
    });
  };

  return (
    <PermissionContext.Provider value={action === 'View'}>
      <Modal
        ref={modalRef}
        className={[classes.TutorialModal, tutorialCreated && classes.TutorialModal_created].join(' ')}
        noDismiss={action !== 'View'}
        onClose={closeTutorialModal}
        open={!!open}
        target={target}
      >
        {!tutorialReview && !tutorialCreated && (
          <React.Fragment>
            <Flex align="center" className={classes['TutorialModal-Nav']} justify="between">
              {action === 'View' && (
                <Title className={classes['TutorialStepNav-Title']} level={4} title={tutorialShell.title}>
                  {tutorialShell.title}
                </Title>
              )}
              {action !== 'View' && (
                <Flex align="center">
                  <button className={`${classes['TutorialModal-Nav-Tab']} ${classes['TutorialModal-Nav-Tab_active']}`}>
                    <i className={`${classes['TutorialModal-Nav-Tab-Icon']} fa fa-cog`} />
                    Configure
                  </button>
                  <button
                    className={classes['TutorialModal-Nav-Tab']}
                    form="tutorialForm"
                    onClick={e => {
                      e.preventDefault();
                      if (validateStep()) setTutorialReview(true);
                    }}
                  >
                    <i className={`${classes['TutorialModal-Nav-Tab-Icon']} fa fa-paint-brush`} />
                    Appearance
                  </button>
                </Flex>
              )}
              <Flex align="center">
                {action !== 'View' && (
                  <Button bem={{ slate: true }} form="tutorialForm" onClick={e => upsertTutorial(e)} type="submit">
                    Save & Close
                  </Button>
                )}
                <button className={classes['TutorialModal-Nav-Close']} onClick={closeTutorialModal} type="button">
                  <i className="icon icon-readme-remove" />
                </button>
              </Flex>
            </Flex>
            <div className={classes['TutorialModal-Row']}>
              <form
                ref={tutorialForm}
                className={[classes['TutorialModal-Col'], classes['TutorialModal-Col_steps']].join(' ')}
                id="tutorialForm"
              >
                {action !== 'View' && (
                  <Input
                    className={classes['TutorialModal-Col-Input']}
                    onChange={e => debounceInputChange({ title: e.target.value })}
                    placeholder="Recipe Title"
                    required
                    value={tutorialShell.title}
                  />
                )}
                <TutorialStepNav createStep={createStep}>
                  {steps &&
                    steps.map((step, index) => (
                      <TutorialStep
                        key={`tutorial-step-${index}`}
                        action={action}
                        deleteStep={deleteStep}
                        handleClick={() => selectStep(index)}
                        index={index}
                        lastIndex={steps.length - 1}
                        navStep={navStep}
                        shiftOrder={direction => shiftStepOrder(index, direction)}
                        step={step}
                        updateStep={updateStep}
                      />
                    ))}
                  {moduleEnabled && action === 'View' && (
                    <a
                      className={classes['TutorialModal-Col-More']}
                      href={`${baseUrl}/tutorials`}
                      rel="noopener noreferrer"
                      target="_blank"
                    >
                      More Recipes
                      <i className={`fa fa-arrow-circle-right ${classes['TutorialModal-Col-More-Icon']}`} />
                    </a>
                  )}
                </TutorialStepNav>
              </form>
              <div
                className={[
                  classes['TutorialModal-Col'],
                  !(action !== 'View' || (action === 'View' && !!tutorialShell.response.length)) &&
                    classes['TutorialModal-Col_singleEditor'],
                ].join(' ')}
              >
                <TutorialEditor
                  key={`tutorial-editor-${selectedStep}`}
                  baseUrl={baseUrl}
                  clearSnippetSelection={clearSnippetSelection}
                  endpointOnboarding={onboarding.endpoints}
                  endpoints={endpoints}
                  getCode={data => ApiActions.getCodeString(initRequest, data)}
                  handleSnippetSelection={confirmEndpoint}
                  lineNumbers={steps[selectedStep].lineNumbers}
                  referenceEnabled={referenceEnabled}
                  requestDataRefresh={requestDataRefresh}
                  selectedTab={selectedTab}
                  setSelectedTab={setSelectedTab}
                  snippet={snippet}
                  updateSnippet={updateSnippet}
                  updateStep={updateStep}
                />
                {(action !== 'View' ||
                  (action === 'View' && tutorialShell.response && !!tutorialShell.response.length)) && (
                  <TutorialResponse
                    key={`tutorial-result-${selectedStep}`}
                    response={tutorialShell.response}
                    updateTutorial={updateTutorial}
                  />
                )}
              </div>
            </div>
          </React.Fragment>
        )}
        {tutorialReview && !tutorialCreated && (
          <React.Fragment>
            <Flex align="center" className={classes['TutorialModal-Nav']} justify="between">
              <Flex align="center">
                <button
                  className={classes['TutorialModal-Nav-Tab']}
                  form="tutorialForm"
                  onClick={e => {
                    e.preventDefault();
                    if (validateStep()) setTutorialReview(false);
                  }}
                >
                  <i className={`${classes['TutorialModal-Nav-Tab-Icon']} fa fa-cog`} />
                  Configure
                </button>
                <button className={`${classes['TutorialModal-Nav-Tab']} ${classes['TutorialModal-Nav-Tab_active']}`}>
                  <i className={`${classes['TutorialModal-Nav-Tab-Icon']} fa fa-paint-brush`} />
                  Appearance
                </button>
              </Flex>
              <Flex align="center">
                <Button bem={{ slate: true }} form="tutorialForm" onClick={e => upsertTutorial(e)} type="submit">
                  Save & Close
                </Button>
                <button className={classes['TutorialModal-Nav-Close']} onClick={closeTutorialModal} type="button">
                  <i className="icon icon-readme-remove" />
                </button>
              </Flex>
            </Flex>
            <div className={classes['TutorialModal-Row']}>
              <form
                ref={tutorialForm}
                className={[classes['TutorialModal-Col'], classes['TutorialModal-Col_review']].join(' ')}
                id="tutorialForm"
              >
                <TutorialReview
                  debounceInputChange={debounceInputChange}
                  endpoints={endpoints}
                  tutorial={tutorialShell}
                  updateTutorial={updateTutorial}
                />
              </form>
              <div className={[classes['TutorialModal-Col'], classes['TutorialModal-Col_review']].join(' ')}>
                <Flex align="center" className={classes['TutorialModal-Col-Wrapper']} justify="center" layout="col">
                  <TutorialCard
                    className={classes['TutorialModal-Col-Wrapper-Card']}
                    tutorial={tutorialShell}
                    tutorialReview={tutorialReview}
                  />
                  <span className={classes['TutorialModal-Col-Wrapper-Caption']}>Recipe Page Preview</span>
                </Flex>
                <Flex align="center" className={classes['TutorialModal-Col-Wrapper']} justify="center" layout="col">
                  <TutorialTile tutorial={tutorialShell} />
                  <span className={classes['TutorialModal-Col-Wrapper-Caption']}>Guide/References Embed Preview</span>
                </Flex>
              </div>
            </div>
          </React.Fragment>
        )}
        {tutorialCreated && (
          <TutorialObstacle
            body="You can view and update your Recipe from the dedicated Recipes page."
            className={classes['TutorialModal-Obstacle']}
            closeTutorialModal={closeTutorialModal}
            heading="You’ve created a Recipe!"
            tutorial={tutorialShell}
          >
            <Button onClick={closeTutorialModal}>View Recipes</Button>
          </TutorialObstacle>
        )}
        {action !== 'View' && error && (
          <Notification color={'red'} dismissible={true} handleClick={() => setError(null)} position={'top'}>
            <p>{error}</p>
          </Notification>
        )}
      </Modal>
    </PermissionContext.Provider>
  );
}

TutorialModal.propTypes = {
  action: PropTypes.oneOf(['Create', 'Edit', 'View']),
  baseUrl: PropTypes.string,
  closeTutorialModal: PropTypes.func,
  confirmModal: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({ current: PropTypes.any })]),
  moduleEnabled: PropTypes.bool,
  onboarding: PropTypes.shape({
    endpoints: PropTypes.bool,
    published: PropTypes.bool,
  }),
  open: PropTypes.bool,
  referenceEnabled: PropTypes.bool,
  requestDataRefresh: PropTypes.func,
  setConfirmModal: PropTypes.func,
  target: PropTypes.string,
  tutorial: TutorialProp,
};

TutorialModal.defaultProps = {
  action: 'View',
  baseUrl: '',
  closeTutorialModal: () => {},
  moduleEnabled: false,
  onboarding: {
    endpoints: false,
    published: false,
  },
  open: false,
  referenceEnabled: false,
  requestDataRefresh: () => {},
  tutorial: cloneDeep(DEFAULT_TUTORIAL),
};
