import React, { useEffect, useState } from 'react';
import styled from 'styled-components';

import type { JSX } from 'react';
import type { CodeBlockControlsProps } from '@redocly/theme/components/CodeBlock/CodeBlockControls';
import type { ReportDialogProps } from '@redocly/theme/components/Feedback/ReportDialog';

import { addLineNumbers } from '@redocly/theme/core/utils';
import { useModalScrollLock, useReportDialog, useThemeHooks } from '@redocly/theme/core/hooks';
import { ReportDialog } from '@redocly/theme/components/Feedback/ReportDialog';
import { CodeBlockContainer } from '@redocly/theme/components/CodeBlock/CodeBlockContainer';
import { CodeBlockControls } from '@redocly/theme/components/CodeBlock/CodeBlockControls';

export type CodeBlockProps = {
  lang?: string;
  source?: string;
  externalSource?: ExternalSource;
  header?: CodeBlockControlsProps;
  dataTestId?: string;
  className?: string;
  tabs?: CodeBlockItems;
  dropdown?: CodeBlockItems;
  withLineNumbers?: boolean;
  startLineNumber?: number;
  highlightedHtml?: string;
  skipHighlight?: boolean;
  codeBlockRef?: (instance: HTMLPreElement | null) => void;
  codeBlockMaxHeight?: string;
  hideCodeColors?: boolean;
  wrapContents?: boolean;
  [key: string]: unknown;
};

type UnstableExternalCodeSample = {
  lang: string;
  label?: string;
  get: (source: ExternalSource) => string;
};

export type CodeBlockItems = {
  items: { name: string; lang?: string; id: string }[];
  onChange: (id: string) => void;
  value: string;
};

type ExternalSource = {
  sample: UnstableExternalCodeSample;
  exampleName?: string;
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
  pathParams?: any;
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
  properties?: any;
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
  operation?: any;
};

export function CodeBlock({
  lang,
  source,
  externalSource,
  header,
  dataTestId = 'source-code',
  codeBlockRef,
  highlightedHtml,
  withLineNumbers,
  startLineNumber,
  className,
  codeBlockMaxHeight,
  tabs,
  dropdown,
  hideCodeColors,
  wrapContents = false,
  children,
  ...rest
}: React.PropsWithChildren<CodeBlockProps>): JSX.Element {
  const [sourceCode, setSourceCode] = useState<string>(
    (source || externalSource?.sample?.get?.(externalSource)) ?? '',
  );
  const { useCodeHighlight } = useThemeHooks();
  const { highlight } = useCodeHighlight() || {};

  const highlightedCode = highlightedHtml
    ? withLineNumbers
      ? addLineNumbers(highlightedHtml, startLineNumber)
      : highlightedHtml
    : children
      ? null
      : highlight?.(sourceCode, lang, {
          withLineNumbers,
          startLineNumber,
          highlight: rest['data-highlight'] as string | undefined,
        });

  // The same initial value should be returned for ssr and frontend to avoid issues
  // Because we don't have session storage in ssr and can't get the security details there
  // Issue for more details https://github.com/Redocly/reference-docs/issues/888
  useEffect(() => {
    const _source = source || externalSource?.sample?.get?.(externalSource);
    if (_source) {
      setSourceCode(_source);
    }
  }, [source, externalSource]);

  const { reportDialog, reportButton } = useReportDialog();

  useModalScrollLock(Boolean(reportDialog.visible));

  const controls = header?.controls && {
    ...header?.controls,
    report: { ...header?.controls?.report, props: reportButton.props },
    copy: header?.controls?.copy ? { ...header?.controls?.copy, data: sourceCode } : undefined,
  };

  return (
    <CodeBlockWrapper data-component-name="CodeBlock/CodeBlock" className={className}>
      <ContainerWrapper>
        <CodeBlockControls
          tabs={tabs}
          dropdown={dropdown}
          className={header?.className}
          title={header?.title}
          controls={controls}
        />
        <CodeBlockContainer
          ref={codeBlockRef}
          withLineNumbers={withLineNumbers}
          dangerouslySetInnerHTML={
            highlightedCode
              ? {
                  __html: highlightedCode,
                }
              : undefined
          }
          /* eslint-disable-next-line no-warning-comments */
          suppressHydrationWarning // TODO: investigate issue
          data-testid={dataTestId}
          hideCodeColors={hideCodeColors}
          maxHeight={codeBlockMaxHeight}
          wrapContents={wrapContents}
          tabIndex={0}
        >
          {children}
        </CodeBlockContainer>
        {reportDialog.visible && (
          <ReportDialog
            {...(reportDialog.props as ReportDialogProps)}
            location={sourceCode}
            lang={lang}
          />
        )}
      </ContainerWrapper>
    </CodeBlockWrapper>
  );
}

const ContainerWrapper = styled.div`
  display: grid; // prevents content to overstretch
  position: relative;
`;

const CodeBlockWrapper = styled.div`
  border: 1px solid var(--border-color-secondary);
  border-radius: var(--border-radius);
  background-color: var(--code-block-bg-color);
  margin: 0 0 var(--spacing-sm);

  --md-pre-margin: 0;
`;
