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

// Readme Libs
// import { supportedLanguages } from '@readme/oas-to-snippet';

// Components
import { Box, Button, Dropdown, Flex, Select } from '@ui/components/';
import Method from '@ui/components/Method';
import Menu from '@ui/components/Menu';
import MenuDivider from '@ui/components/Menu/Divider';
import MenuHeader from '@ui/components/Menu/Header';
import MenuItem from '@ui/components/Menu/Item';

// Internals

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

// Constants
import { StepSnippetProp, CodeOptionProp, EndpointProp } from '@ui/compositions/TutorialModal/proptypes/tutorials';
import { ApiEndpointProp } from '@ui/compositions/TutorialModal/proptypes/apiEndpoints';

// Permissions
import { PermissionContext } from '@ui/compositions/TutorialModal';

// Readme Libs
const syntaxHighlighter =
  typeof window !== 'undefined' ? require('@readme/syntax-highlighter/dist/index.js').default : () => {};

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

const LanguageDropdown = ({ languageOpts, index, ...props }) => {
  const maxTabCount = 4;
  const defaultLang = 'c';
  const [language, setLanguage] = useState(defaultLang);

  const handleTabCreate = () => {
    if (language) {
      props.handleTabCreate({ language });
      setLanguage(defaultLang);
    }
  };

  return (
    <React.Fragment>
      {index < maxTabCount && (
        <Dropdown className={classes['TutorialEditor-Nav-Plus']}>
          <Button bem={{ white_text: true }}>
            <i className="fa fa-plus-circle" />
          </Button>
          <div className={classes['TutorialEditor-Nav-Plus-ClearWrapper']}>
            <Box bem={[['pop']]} style={{ padding: 0 }}>
              <Menu>
                <MenuItem TagName="div">
                  <Select
                    onChange={e => setLanguage(e.target.value)}
                    options={languageOpts}
                    size="sm"
                    value={language}
                  />
                </MenuItem>
                <MenuDivider />
                <MenuItem color="blue" onClick={handleTabCreate}>
                  Create Tab
                </MenuItem>
              </Menu>
            </Box>
          </div>
        </Dropdown>
      )}
    </React.Fragment>
  );
};

LanguageDropdown.propTypes = {
  handleTabCreate: PropTypes.func,
  index: PropTypes.number,
  languageOpts: PropTypes.arrayOf(
    PropTypes.shape({
      label: PropTypes.string,
      value: PropTypes.string,
    })
  ),
};

const SnippetLanguageMenu = ({ codeOptions, languageOpts, readOnly, setSelectedTab, selectedTab, ...props }) => {
  const handleTabCreate = ({ language }) => {
    // Basic scaffold for a new tab
    props.handleTabCreate({
      code: '',
      language,
      name: `${language.charAt(0).toUpperCase()}${language.slice(1)}`,
    });
  };

  const tutorialTabClasses = index =>
    [classes['TutorialEditor-Nav-Tab'], index === selectedTab && classes['TutorialEditor-Nav-Tab_active']]
      .filter(e => e)
      .join(' ');

  return (
    <Flex>
      {codeOptions &&
        codeOptions.map((opt, index) => (
          <Button
            key={index}
            bem={{ white_text: true }}
            className={`${tutorialTabClasses(index)}`}
            onClick={() => setSelectedTab(index)}
          >
            {opt.name}
          </Button>
        ))}
      {!readOnly && (
        <LanguageDropdown
          handleTabCreate={handleTabCreate}
          index={codeOptions.length - 1}
          languageOpts={languageOpts}
        />
      )}
    </Flex>
  );
};

SnippetLanguageMenu.propTypes = {
  codeOptions: PropTypes.arrayOf(CodeOptionProp),
  handleTabCreate: PropTypes.func,
  languageOpts: PropTypes.arrayOf(
    PropTypes.shape({
      label: PropTypes.string,
      value: PropTypes.string,
    })
  ),
  readOnly: PropTypes.bool,
  selectedTab: PropTypes.number,
  setSelectedTab: PropTypes.func,
};

const EndpointSelector = ({
  baseUrl,
  endpointOnboarding,
  endpoints,
  codeOptions,
  selectedEndpoint,
  readOnly,
  requestDataRefresh,
  handleSnippetSelection,
  clearSnippetSelection,
}) => {
  const { response, initRequest } = useReadmeApi(baseUrl);

  useEffect(() => {
    if (response.success) requestDataRefresh();
  }, [response]);

  return (
    <Dropdown
      justify="right"
      open={!endpointOnboarding && selectedEndpoint && !selectedEndpoint.title}
      sticky={!endpointOnboarding}
    >
      {(!readOnly || (selectedEndpoint && selectedEndpoint.title)) && (
        <Button
          bem={{ white_text: true }}
          className={classes['TutorialEditor-Nav-Button']}
          title={(selectedEndpoint && selectedEndpoint.title) || ''}
          {...(readOnly &&
            selectedEndpoint &&
            selectedEndpoint.slug && {
              href: `${baseUrl}/reference-link/${selectedEndpoint.slug}`,
              rel: 'noopener noreferrer',
              target: '_blank',
            })}
        >
          {selectedEndpoint && selectedEndpoint.method && <Method type={selectedEndpoint.method.toLowerCase()} />}
          <span className={classes['TutorialEditor-Nav-Button-Text']}>
            {(selectedEndpoint && selectedEndpoint.title) || 'Choose Endpoint'}
          </span>
          {!readOnly && <i className="fa fa-chevron-down" />}
          {readOnly && selectedEndpoint && selectedEndpoint.slug && (
            <i className={['fa fa-arrow-circle-up', classes['TutorialEditor-Nav-Button-Link-Icon']].join(' ')} />
          )}
        </Button>
      )}
      {!readOnly && (
        <Box bem={[['pop']]} className={classes['TutorialEditor-Nav-Menu']}>
          {!endpointOnboarding && selectedEndpoint && !selectedEndpoint.title && (
            <div className={classes['TutorialEditor-Nav-Menu-ReferenceGuide']}>
              <span
                aria-label="finger pointing up"
                className={classes['TutorialEditor-Nav-Menu-ReferenceGuide-Avatar']}
                role="img"
              >
                ☝️
              </span>
              <p className={classes['TutorialEditor-Nav-Menu-ReferenceGuide-Description']}>
                Start from one of your API endpoints, or write a tutorial from scratch.
                <button
                  className={classes['TutorialEditor-Nav-Menu-ReferenceGuide-Description-Button']}
                  onClick={() => ApiActions.updateOnboarding(initRequest, { endpoints: true })}
                  type="button"
                >
                  Don’t show again
                </button>
              </p>
            </div>
          )}
          <Menu
            className={[
              classes['TutorialEditor-Nav-Menu-List'],
              !endpointOnboarding ? classes['TutorialEditor-Nav-Menu-List_onboarding'] : '',
              endpoints.length === 1 ? classes['TutorialEditor-Nav-Menu-List_overflow'] : '',
            ].join(' ')}
          >
            {selectedEndpoint && selectedEndpoint.title && (
              <React.Fragment>
                <MenuHeader>{selectedEndpoint.title}</MenuHeader>
                <MenuItem color="red" onClick={clearSnippetSelection}>
                  Clear Selection
                </MenuItem>
                <MenuDivider />
              </React.Fragment>
            )}
            {endpoints &&
              endpoints.length === 1 &&
              endpoints.map((endpoint, index) => (
                <React.Fragment key={`tutorial-endpoint-group-${index}`}>
                  <MenuHeader title={endpoint.title}>{endpoint.title}</MenuHeader>
                  {endpoint.children.map((child, idx) => (
                    <MenuItem
                      key={`tutorial-endpoint-opt-${idx}`}
                      onClick={() => handleSnippetSelection({ page: child, codeOptions })}
                      title={child.title}
                    >
                      <React.Fragment>
                        <Method
                          className={classes['TutorialEditor-Nav-Menu-Method']}
                          type={child.api.method.toLowerCase()}
                          width={40}
                        />
                        <span className={classes['TutorialEditor-Nav-Menu-Text']}>{child.title}</span>
                      </React.Fragment>
                    </MenuItem>
                  ))}
                </React.Fragment>
              ))}
            {endpoints && endpoints.length > 1 && (
              <React.Fragment>
                <MenuHeader>API Definitions</MenuHeader>
                {endpoints.map((endpoint, index) => (
                  <MenuItem key={`tutorial-endpoint-group-${index}`} openAt="left" title={endpoint.title}>
                    {endpoint.title}
                    <Menu>
                      <MenuHeader>{endpoint.title}</MenuHeader>
                      {endpoint.children.map((child, idx) => (
                        <MenuItem
                          key={`tutorial-endpoint-opt-${idx}`}
                          onClick={() => handleSnippetSelection({ page: child, codeOptions })}
                          title={child.title}
                        >
                          <React.Fragment>
                            <Method
                              className={classes['TutorialEditor-Nav-Menu-Method']}
                              type={child.api.method.toLowerCase()}
                              width={40}
                            />
                            <span className={classes['TutorialEditor-Nav-Menu-Text']}>{child.title}</span>
                          </React.Fragment>
                        </MenuItem>
                      ))}
                    </Menu>
                  </MenuItem>
                ))}
              </React.Fragment>
            )}
          </Menu>
        </Box>
      )}
    </Dropdown>
  );
};

EndpointSelector.propTypes = {
  baseUrl: PropTypes.string,
  clearSnippetSelection: PropTypes.func,
  codeOptions: PropTypes.arrayOf(CodeOptionProp),
  endpointOnboarding: PropTypes.bool,
  endpoints: PropTypes.arrayOf(ApiEndpointProp),
  handleSnippetSelection: PropTypes.func,
  readOnly: PropTypes.bool,
  requestDataRefresh: PropTypes.func,
  selectedEndpoint: EndpointProp,
};

const TabControls = ({
  codeOption,
  selectedLines,
  languageOpts,
  selectedTab,
  isOnlyTab,
  handleLineUpdate,
  handleTabUpdate,
  handleTabDelete,
}) => {
  const lineNumberRegex = /^[0-9,-]+$/;
  const maxLength = 20;

  const handleLineInput = event => {
    const { value } = event.target;

    if (!value || lineNumberRegex.test(value)) {
      handleLineUpdate(value);
    }
  };

  const handleNameInput = (event, key) => {
    const { value } = event.target;
    if (value.length >= maxLength) event.target.value = value.slice(0, maxLength);
    handleTabUpdate(event, key);
  };

  return (
    <div className={classes['TutorialEditor-Controls']}>
      <label
        className={[
          classes['TutorialEditor-Controls-ControlWrapper'],
          classes['TutorialEditor-Controls-ControlWrapper_select'],
        ].join(' ')}
      >
        <span>Language</span>
        <select
          className={classes['TutorialEditor-Controls-ControlWrapper-Select']}
          onChange={e => handleTabUpdate(e, 'language')}
          value={codeOption.language}
        >
          {languageOpts.map(({ value, label }, idx) => (
            <option key={`tab-control-language-${idx}`} value={value}>
              {label}
            </option>
          ))}
        </select>
      </label>
      <label className={classes['TutorialEditor-Controls-ControlWrapper']}>
        <span>Highlight</span>
        <input
          className={classes['TutorialEditor-Controls-ControlWrapper-Input']}
          onChange={handleLineInput}
          placeholder="1,4,7-9"
          value={selectedLines}
        />
        <i
          className={['fa fa-question', classes['TutorialEditor-Controls-ControlWrapper-Tooltip']].join(' ')}
          data-tooltip="Highlight multiple lines with commas and hyphens"
          data-tooltip-position="right"
        />
      </label>
      <label className={classes['TutorialEditor-Controls-ControlWrapper']}>
        <span>Name</span>
        <input
          className={classes['TutorialEditor-Controls-ControlWrapper-Input']}
          maxLength={maxLength}
          onChange={e => handleNameInput(e, 'name')}
          value={codeOption.name}
        />
      </label>
      <button
        className={classes['TutorialEditor-Controls-Delete']}
        data-tooltip={isOnlyTab ? 'Must have at least one tab' : ''}
        data-tooltip-position="left"
        disabled={isOnlyTab}
        onClick={() => handleTabDelete(selectedTab)}
        type="button"
      >
        Delete Tab
      </button>
    </div>
  );
};

TabControls.propTypes = {
  codeOption: CodeOptionProp,
  handleLineUpdate: PropTypes.func,
  handleTabDelete: PropTypes.func,
  handleTabUpdate: PropTypes.func,
  isOnlyTab: PropTypes.bool,
  languageOpts: PropTypes.arrayOf(
    PropTypes.shape({
      label: PropTypes.string,
      value: PropTypes.string,
    })
  ),
  selectedLines: PropTypes.string,
  selectedTab: PropTypes.number,
};

export default function TutorialEditor({
  baseUrl,
  snippet,
  endpointOnboarding,
  endpoints,
  lineNumbers,
  referenceEnabled,
  requestDataRefresh,
  selectedTab,
  setSelectedTab,
  handleSnippetSelection,
  clearSnippetSelection,
  updateStep,
  updateSnippet,
  ...props
}) {
  const [editorInstance, setEditorInstance] = useState(null);
  const [rangeOpts, setRangeOpts] = useState([]);

  const { codeOptions, endpoint } = snippet;

  // TODO resolve module build issues for @readme/oas-to-snippet
  const languageOpts = [
    { value: 'c', label: 'c' },
    { value: 'node', label: 'node' },
    { value: 'shell', label: 'curl' },
    { value: 'python', label: 'python' },
    { value: 'php', label: 'php' },
  ];
  // const languageOpts = Object.keys(supportedLanguages).map(key => {
  //   return { value: key, label: key };
  // });

  // Converting between human readable `1, 2-5` to codemirror selection syntax
  const spreadLineNumbers = ranges => {
    return ranges.split(',').map(range => {
      const [anchor, head] = range.split('-');

      const end = head || anchor ? parseInt(head || anchor, 10) : 0;
      const start = parseInt(anchor || 0, 10) - 1;

      return [
        { ch: 0, line: start },
        { ch: 0, line: end },
      ];
    });
  };
  // Get the defined dimensions based on the first selected range
  // to programmatically scroll. This is necessary because codemirror likes to put
  // a margin buffer in their component
  const getEditorDimensions = (editor, range, readOnly) => {
    const editorHeight = readOnly ? editor.offsetHeight : editor.getWrapperElement().offsetHeight;
    const lineHeight = 15;
    const [start, end] = range;

    const rangeHeight = (end.line - start.line) * lineHeight;
    let editorCenter = start.line * lineHeight - editorHeight / 2 - lineHeight / 2;

    // if there’s a range greater than one line
    if (rangeHeight > 15) {
      editorCenter += rangeHeight / 2;
      // if the range is larger than the editor
      if (rangeHeight > editorHeight) editorCenter = start.line * lineHeight - lineHeight;
    }

    return { editorHeight, editorCenter };
  };

  const markEditorInstance = (editor, ranges) => {
    // Clear existing style tags from our editor instance
    editor.getAllMarks().forEach(mark => mark.clear());

    // Mark new ranges with classname
    ranges.forEach(([anchor, head]) =>
      editor.getDoc().markText(anchor, head, { className: 'CodeEditor-Highlight', addToHistory: true })
    );
    // Scroll to the first range so that code is in viewport
    const { editorHeight, editorCenter } = getEditorDimensions(editor, ranges[0]);
    editor.setSize('100%', editorHeight);
    editor.scrollTo(0, editorCenter);
  };
  // Editor user interaction marking
  // Called when lineNumbers is updated (via input or step/tab change)
  useEffect(() => {
    const newLines = lineNumbers[selectedTab];

    if (newLines) {
      const ranges = spreadLineNumbers(newLines);

      if (!editorInstance) setRangeOpts(ranges);
      if (editorInstance) setTimeout(() => markEditorInstance(editorInstance, ranges));
    }
  }, [lineNumbers[selectedTab], snippet.codeOptions[selectedTab].code, snippet.codeOptions.endpoint, editorInstance]);

  // getCode() is called when two conditions are fulfilled:
  // 1. When a snippet is already selected and assigned to the tutorial and a new tab is created
  // 2. When we create a new step
  // getCode will retrieve a code string and place it within the tab's target index
  const getCode = (language, index) => {
    const { page } = snippet.endpoint;
    const pages = endpoints.reduce((arr, val) => arr.concat(val.children), []);
    const foundPage = pages.find(({ _id }) => _id === page);

    if (foundPage) props.getCode({ page: foundPage, language, index });
  };

  const handleTabCreate = codeOption => {
    lineNumbers.push('');
    updateStep({ lineNumbers });

    codeOptions.push(codeOption);
    updateSnippet({ codeOptions });

    setSelectedTab(codeOptions.length - 1);
    getCode(codeOption.language, snippet.codeOptions.length - 1);
  };

  const handleTabUpdate = (event, key) => {
    codeOptions[selectedTab][key] = event.target.value;
    updateSnippet({ codeOptions });
  };

  const handleTabDelete = index => {
    lineNumbers.splice(index, 1);
    updateStep({ lineNumbers });

    codeOptions.splice(index, 1);
    updateSnippet({ codeOptions });

    setSelectedTab(0);
  };

  const handleLineUpdate = value => {
    lineNumbers[selectedTab] = value;
    updateStep({ lineNumbers });
  };

  const handleCodeChange = ({ origin }, code, readOnly) => {
    if (['+input', 'paste', '+delete'].includes(origin) && !readOnly) {
      codeOptions[selectedTab].code = code;
      updateSnippet({ codeOptions });
    }
  };

  const checkCodeChange = process.env.NODE_ENV === 'test' ? handleCodeChange : _.debounce(handleCodeChange, 300);
  const codeMirrorOpts = readOnly => {
    if (readOnly) {
      if (typeof window !== 'undefined' && rangeOpts.length) {
        const readOnlyEditor = document.querySelector('.TutorialEditor-Nav + .CodeEditor');
        const { editorCenter } = getEditorDimensions(readOnlyEditor, rangeOpts[0], readOnly);
        readOnlyEditor.scroll({ top: editorCenter, left: 0, behavior: 'auto' });
      }
      return {
        highlightMode: true,
        tokenizeVariables: true,
        ranges: rangeOpts,
      };
    }
    return { editable: true };
  };

  return (
    <PermissionContext.Consumer>
      {readOnly => (
        <React.Fragment>
          <Flex
            align="center"
            className={['TutorialEditor-Nav', classes['TutorialEditor-Nav']].join(' ')}
            justify="between"
            tag="nav"
          >
            <SnippetLanguageMenu
              codeOptions={codeOptions}
              handleTabCreate={handleTabCreate}
              languageOpts={languageOpts}
              readOnly={readOnly}
              selectedTab={selectedTab}
              setSelectedTab={setSelectedTab}
            />
            {referenceEnabled && (readOnly || endpoints.length > 0) && (
              <EndpointSelector
                baseUrl={baseUrl}
                clearSnippetSelection={clearSnippetSelection}
                codeOptions={snippet.codeOptions}
                endpointOnboarding={endpointOnboarding}
                endpoints={endpoints}
                handleSnippetSelection={handleSnippetSelection}
                readOnly={readOnly}
                requestDataRefresh={requestDataRefresh}
                selectedEndpoint={endpoint}
              />
            )}
          </Flex>
          {syntaxHighlighter(
            codeOptions[selectedTab].code,
            codeOptions[selectedTab].language,
            codeMirrorOpts(readOnly),
            {
              autoScroll: true,
              className: `CodeEditor-Input ${(readOnly && 'CodeEditor-Input_readonly') || ''}`,
              editorDidMount: setEditorInstance,
              onChange: (editor, data, code) => checkCodeChange(data, code, readOnly),
            }
          )}
          {!readOnly && (
            <TabControls
              codeOption={codeOptions[selectedTab]}
              handleLineUpdate={handleLineUpdate}
              handleTabDelete={handleTabDelete}
              handleTabUpdate={handleTabUpdate}
              isOnlyTab={codeOptions.length === 1}
              languageOpts={languageOpts}
              selectedLines={lineNumbers[selectedTab]}
              selectedTab={selectedTab}
            />
          )}
        </React.Fragment>
      )}
    </PermissionContext.Consumer>
  );
}

TutorialEditor.propTypes = {
  baseUrl: PropTypes.string,
  clearSnippetSelection: PropTypes.func,
  endpointOnboarding: PropTypes.bool,
  endpoints: PropTypes.arrayOf(ApiEndpointProp),
  getCode: PropTypes.func,
  handleSnippetSelection: PropTypes.func,
  lineNumbers: PropTypes.arrayOf(PropTypes.string),
  referenceEnabled: PropTypes.bool,
  requestDataRefresh: PropTypes.func,
  selectedTab: PropTypes.number,
  setSelectedTab: PropTypes.func,
  snippet: StepSnippetProp,
  updateSnippet: PropTypes.func,
  updateStep: PropTypes.func,
};
