import React, { useState, useEffect, useRef, FormEvent } from 'react'
import { FormControl, IconButton, Select, SegmentedControl } from '@primer/react'
import { CheckIcon, CopyIcon, InfoIcon } from '@primer/octicons-react'
import { announce } from '@primer/live-region-element'
import Cookies from '@/frame/components/lib/cookies'
import cx from 'classnames'

import hljs from 'highlight.js/lib/core'
import json from 'highlight.js/lib/languages/json'
import javascript from 'highlight.js/lib/languages/javascript'
import { generateExampleOptions } from '@/rest/lib/code-example-utils'
import hljsCurl from 'highlightjs-curl'

import { useTranslation } from '@/languages/components/useTranslation'
import useClipboard from '@/rest/components/useClipboard'
import {
  getShellExample,
  getGHExample,
  getJSExample,
} from '@/rest/components/get-rest-code-samples'
import styles from './RestCodeSamples.module.scss'
import { RestMethod } from './RestMethod'
import type { Operation, ExampleT } from './types'
import { ResponseKeys, CodeSampleKeys } from './types'
import { useVersion } from '@/versions/components/useVersion'
import { useMainContext } from '@/frame/components/context/MainContext'

type Props = {
  slug: string
  operation: Operation
  heading: string
}

const responseSelectOptions = Object.values(ResponseKeys)

// Add as needed. It's pretty cheap to add but please don't use
// highlight.js import that loads all and everything.
hljs.registerLanguage('json', json)
hljs.registerLanguage('javascript', javascript)
hljs.registerLanguage('curl', hljsCurl)

function getLanguageHighlight(selectedLanguage: string) {
  return selectedLanguage === CodeSampleKeys.javascript ? 'javascript' : 'curl'
}

function highlightElement(element: HTMLElement) {
  element.className = 'hljs'
  // If the element was already highlighted, remove the dataset property
  // otherwise the `hljs.highlightElement` function will not highlight.
  delete element.dataset.highlighted
  hljs.highlightElement(element)
}

export function RestCodeSamples({ operation, slug, heading }: Props) {
  const { t } = useTranslation(['rest_reference'])
  const { isEnterpriseServer, isEnterpriseCloud } = useVersion()

  // Refs to track the request example, response example
  // and the first render
  const requestCodeExample = useRef<HTMLSpanElement>(null)
  const responseCodeExample = useRef<HTMLSpanElement>(null)
  const firstRender = useRef(true)
  const scrollRef = useRef<HTMLDivElement>(null)

  const { currentVersion } = useVersion()
  const { allVersions } = useMainContext()

  // Get format examples for each language
  const languageExamples = operation.codeExamples.map((sample) => ({
    description: sample.request.description,
    curl: getShellExample(operation, sample, currentVersion, allVersions),
    javascript: getJSExample(operation, sample, currentVersion, allVersions),
    ghcli: getGHExample(operation, sample, currentVersion, allVersions),
    response: sample.response,
    request: sample.request,
  }))

  // Menu options for the language selector
  const languageSelectOptions: CodeSampleKeys[] = [CodeSampleKeys.curl]

  // Management Console, GHES Manage API, and GitHub Models
  // operations are not supported by Octokit
  if (
    operation.category !== 'models' &&
    operation.subcategory !== 'management-console' &&
    operation.subcategory !== 'manage-ghes'
  ) {
    languageSelectOptions.push(CodeSampleKeys.javascript)

    // Not all examples support the GH CLI language option. If any of
    // the examples don't support it, we don't show GH CLI as an option.
    if (!languageExamples.some((example) => example.ghcli === undefined)) {
      languageSelectOptions.push(CodeSampleKeys.ghcli)
    }
  }

  // Menu options for the example selector
  const exampleSelectOptions = generateExampleOptions(languageExamples)

  const [selectedLanguage, setSelectedLanguage] = useState(languageSelectOptions[0])
  const [selectedExample, setSelectedExample] = useState(exampleSelectOptions[0])
  const [selectedResponse, setSelectedResponse] = useState(responseSelectOptions[0])

  const isSingleExample = languageExamples.length === 1
  const displayedExample: ExampleT = languageExamples[selectedExample.languageIndex]

  const handleExampleSelection = (event: FormEvent<HTMLSelectElement>) => {
    setSelectedExample(exampleSelectOptions[Number(event.currentTarget.value)])
  }

  const handleResponseSelection = (responseKey: ResponseKeys) => {
    setSelectedResponse(responseKey)
  }

  const handleLanguageSelection = (languageKey: CodeSampleKeys) => {
    setSelectedLanguage(languageKey)
    Cookies.set('codeSampleLanguagePreferred', languageKey)
  }

  // Change the language based on cookies
  useEffect(() => {
    // If the user previously selected a language preference and the language
    // is available in this component set it as the selected language
    const cookieValue = Cookies.get('codeSampleLanguagePreferred')
    const preferredCodeLanguage = languageSelectOptions.find((item) => item === cookieValue)
    if (cookieValue && preferredCodeLanguage) {
      setSelectedLanguage(cookieValue as CodeSampleKeys)
    }
  }, [])

  // Handle syntax highlighting when the language changes or
  // a cookie is set
  useEffect(() => {
    const reqElem = requestCodeExample.current

    // Do not highlight on the first render because the
    // intersection observer syntax highlighting
    // (ClientSideHighlightJS) will have already handled highlighting
    if (reqElem && !firstRender.current) {
      highlightElement(reqElem)
    }
  }, [selectedLanguage])

  // Handle syntax highlighting and scroll position when the language changes or
  // a cookie is set, changing the default language
  useEffect(() => {
    const reqElem = responseCodeExample.current
    const scrollElem = scrollRef.current

    // Reset scroll position to the top when switching between example response and
    // response schema
    if (scrollElem) {
      scrollElem.scrollTop = 0
    }
    // Do not highlight on the first render because the
    // intersection observer syntax highlighting
    // (ClientSideHighlightJS) will have already handled highlighting
    if (reqElem && !firstRender.current) {
      highlightElement(reqElem)
    }
  }, [selectedResponse])

  // Handle highlighting when there is more than one example and
  // the example changes.
  useEffect(() => {
    const reqElem = requestCodeExample.current
    if (reqElem) {
      highlightElement(reqElem)
    }

    const resElem = responseCodeExample.current
    if (resElem) {
      highlightElement(resElem)
    }
  }, [selectedExample])

  // Keep track of the first render so we can skip highlighting
  useEffect(() => {
    if (firstRender.current) {
      firstRender.current = false
    }
  }, [])

  const [isCopied, setCopied] = useClipboard(displayedExample[selectedLanguage] as string, {
    successDuration: 1400,
  })

  let displayedExampleResponse = JSON.stringify(displayedExample.response.example, null, 2)
  let displayedExampleSchema = JSON.stringify(displayedExample.response.schema, null, 2)

  if (isEnterpriseServer) {
    displayedExampleResponse =
      displayedExampleResponse && displayedExampleResponse.replaceAll('api.github.com', 'HOSTNAME')
    displayedExampleSchema =
      displayedExampleSchema && displayedExampleSchema.replaceAll('api.github.com', 'HOSTNAME')
  }

  return (
    <>
      <h3 className="mt-0 pt-0 h4" id={`${slug}--code-samples`}>
        <a href={`#${slug}--code-samples`}>{heading}</a>
      </h3>
      {isEnterpriseCloud && selectedLanguage === CodeSampleKeys.curl ? (
        <span className="f5">
          <InfoIcon className="d-inline mx-1" />
          <p
            className="d-inline"
            dangerouslySetInnerHTML={{ __html: t('data_residency_notice') }}
          />
        </span>
      ) : null}

      <h4 className="mt-3 mb-3 h5">
        {isSingleExample ? t('request_example') : t('request_examples')}
      </h4>
      {/* Display an example selector if more than one example */}
      {!isSingleExample && (
        <div className="pb-5 pt-2">
          <FormControl id="example-type-picker">
            <FormControl.Label visuallyHidden>Select the example type</FormControl.Label>
            <Select onChange={handleExampleSelection}>
              {exampleSelectOptions.map((option) => (
                <Select.Option key={option.languageIndex} value={option.languageIndex.toString()}>
                  {option.text}
                </Select.Option>
              ))}
            </Select>
          </FormControl>
        </div>
      )}

      {/* Request example section */}
      <div className="rounded-1 border">
        <div className="my-0 p-3">
          <RestMethod verb={operation.verb} requestPath={operation.requestPath} />
        </div>
        <div className="border-top d-inline-flex flex-justify-between width-full flex-items-center pt-2">
          <div className="d-inline-flex ml-2">
            <SegmentedControl
              className={styles.segmentedControl}
              aria-label={`Example language selector for ${operation.title}`}
            >
              {languageSelectOptions.map((optionKey) => (
                <SegmentedControl.Button
                  key={optionKey}
                  selected={optionKey === selectedLanguage}
                  onClick={(e: React.MouseEvent) => {
                    e.preventDefault()
                    handleLanguageSelection(optionKey)
                  }}
                  onKeyDown={(event: React.KeyboardEvent) => {
                    if (event.key === 'Enter') {
                      handleLanguageSelection(optionKey)
                    }
                  }}
                >
                  {t(`code_sample_options.${optionKey}`)}
                </SegmentedControl.Button>
              ))}
            </SegmentedControl>
          </div>
          <div className="mr-2">
            <IconButton
              icon={isCopied ? CheckIcon : CopyIcon}
              className="js-btn-copy btn-octicon"
              aria-label={`${t('button_text.copy_to_clipboard')} ${selectedLanguage} request example`}
              onClick={() => {
                setCopied()
                announce('Copied!')
              }}
            ></IconButton>
          </div>
        </div>

        {/* Example requests */}
        <div
          className={cx(
            styles.codeBlock,
            styles.requestCodeBlock,
            `border-top rounded-1 my-0 ${getLanguageHighlight(selectedLanguage)}`,
          )}
          data-highlight={getLanguageHighlight(selectedLanguage)}
          // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
          tabIndex={0}
        >
          <code ref={requestCodeExample}>{displayedExample[selectedLanguage]}</code>
        </div>
      </div>

      {/* Response section */}
      <h4
        className="mt-5 mb-2 h5"
        dangerouslySetInnerHTML={{
          __html: displayedExample.response.description || t('response'),
        }}
      ></h4>
      <div className="border rounded-1 pt-2">
        {displayedExample.response.schema ? (
          <SegmentedControl
            className={cx(styles.segmentedControl, 'mx-2')}
            aria-label={`Example response format selector for ${operation.title}`}
          >
            {responseSelectOptions.map((optionKey) => (
              <SegmentedControl.Button
                key={optionKey}
                selected={optionKey === selectedResponse}
                onClick={(e: React.MouseEvent) => {
                  e.preventDefault()
                  handleResponseSelection(optionKey)
                }}
                onKeyDown={(event: React.KeyboardEvent) => {
                  if (event.key === 'Enter') {
                    handleResponseSelection(optionKey)
                  }
                }}
              >
                {t(`response_options.${optionKey}`)}
              </SegmentedControl.Button>
            ))}
          </SegmentedControl>
        ) : null}
        <div className="">
          {/* Status code */}
          {displayedExample.response.statusCode && (
            <div className={cx(styles.codeBlock, 'rounded-1 p-3 my-0 color-bg-default')}>
              <code>{`Status: ${displayedExample.response.statusCode}`}</code>
            </div>
          )}
          {/* Example response */}
          {displayedExample.response.example && (
            <div
              ref={scrollRef}
              className={cx(
                styles.codeBlock,
                styles.responseCodeBlock,
                'border-top rounded-1 my-0',
              )}
              data-highlight={'json'}
              // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
              tabIndex={0}
            >
              <code ref={responseCodeExample}>
                {selectedResponse === ResponseKeys.example
                  ? displayedExampleResponse
                  : displayedExampleSchema}
              </code>
            </div>
          )}
        </div>
      </div>
    </>
  )
}
