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

import type { JSX } from 'react';

import { Button } from '@redocly/theme/components/Button/Button';
import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDownIcon';

export function JsonValue(props: {
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
  value: any;
  level: number;
  standalone?: boolean; // if true, will render the opening and closing punctuation (when it's an item inside array or top-level)
  defaultExpandLevel: number;
  expandAllSignal: boolean | undefined;
}): React.ReactNode {
  const { standalone, defaultExpandLevel, level, value, expandAllSignal } = props;
  const indent = ' '.repeat(level * 2);

  const [isExpandedState, setExpanded] = React.useState(
    expandAllSignal ?? level < defaultExpandLevel,
  );
  const isExpanded = expandAllSignal !== undefined && level > 0 ? expandAllSignal : isExpandedState;
  useEffect(() => {
    if (expandAllSignal !== undefined) {
      if (expandAllSignal === false && level === 0) {
        return;
      }
      setExpanded(expandAllSignal);
    }
  }, [expandAllSignal, level]);

  const keys: string[] =
    (value && Object.keys(value).filter((key) => value[key] !== undefined)) || []; // filter out undefined properties

  const valueType = typeof value;
  const isPrimitive = (valueType !== 'object' && value !== null) || keys.length === 0;

  if (isPrimitive) {
    return (
      <>
        {standalone ? indent : null}
        <span className={'token ' + valueType}>{JSON.stringify(value)}</span>
      </>
    );
  }

  const openingPunctuation = Array.isArray(value) ? '[' : '{';
  const closingPunctuation = Array.isArray(value) ? ']' : '}';

  const valueElement = Array.isArray(value)
    ? value.map((item, index) => (
        <React.Fragment key={index}>
          <JsonValue
            value={item}
            level={level + 1}
            standalone={true}
            defaultExpandLevel={defaultExpandLevel}
            expandAllSignal={expandAllSignal}
          />
          {index < value.length - 1 ? ',\n' : null}
        </React.Fragment>
      ))
    : keys.map((key, index) => (
        <React.Fragment key={key}>
          <JsonKeyValue
            name={key}
            value={value[key]}
            level={level + 1}
            defaultExpandLevel={defaultExpandLevel}
            expandAllSignal={expandAllSignal}
          />
          {index < keys.length - 1 ? ',\n' : null}
        </React.Fragment>
      ));

  const openingPunctuationElement = standalone ? (
    <>
      {indent}
      {level > 0 ? (
        <ExpandIcon onClick={() => setExpanded((v) => !v)} isExpanded={isExpanded} />
      ) : null}
      {openingPunctuation}
      {isExpanded ? '\n' : null}
    </>
  ) : null;

  const closingPunctuationElement = standalone ? (
    <>
      {isExpanded ? '\n' : ''}
      {isExpanded ? indent : ''}
      {closingPunctuation}
    </>
  ) : null;

  return (
    <>
      {openingPunctuationElement}
      {isExpanded || !standalone ? valueElement : ' … '}
      {closingPunctuationElement}
    </>
  );
}

function JsonKeyValue(props: {
  name: string;
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
  value: any;
  level: number;
  defaultExpandLevel: number;
  expandAllSignal: boolean | undefined;
}): React.ReactNode {
  const { defaultExpandLevel, level, name, value, expandAllSignal } = props;
  const indent = ' '.repeat(level * 2);
  const keys: string[] =
    (value && Object.keys(value).filter((key) => value[key] !== undefined)) || []; // filter out undefined properties

  const isCollapsible = typeof value === 'object' && value !== null && keys.length > 0;
  const [isExpandedState, setExpanded] = React.useState(
    expandAllSignal ?? level < defaultExpandLevel,
  );
  const isExpanded = expandAllSignal !== undefined ? expandAllSignal : isExpandedState;

  useEffect(() => {
    if (expandAllSignal !== undefined) {
      setExpanded(expandAllSignal);
    }
  }, [expandAllSignal]);

  const openingPunctuation = Array.isArray(value) ? '[' : '{';
  const closingPunctuation = Array.isArray(value) ? ']' : '}';

  const valueElement = isCollapsible ? (
    <>
      {openingPunctuation}
      {isExpanded ? (
        <>
          {'\n'}
          <JsonValue
            value={value}
            level={level}
            defaultExpandLevel={defaultExpandLevel}
            expandAllSignal={expandAllSignal}
          />
          {'\n'}
        </>
      ) : (
        ' … '
      )}
      {isExpanded ? indent : null}
      {closingPunctuation}
    </>
  ) : (
    <JsonValue
      value={value}
      level={level}
      defaultExpandLevel={defaultExpandLevel}
      expandAllSignal={expandAllSignal}
    />
  );

  return (
    <>
      {indent}
      {isCollapsible ? (
        <ExpandIcon onClick={() => setExpanded((v) => !v)} isExpanded={isExpanded} />
      ) : null}
      <span className="property token string">{`"${name}"`}</span>: {valueElement}
    </>
  );
}

function ExpandIcon({
  isExpanded,
  onClick,
}: {
  onClick: () => void;
  isExpanded: boolean;
}): JSX.Element {
  return (
    <ExpandButton
      aria-label="collapse"
      className={isExpanded ? 'expanded' : ''}
      size="small"
      variant="text"
      onClick={onClick}
      icon={<ChevronDownIcon color="var(--text-color-helper)" />}
    />
  );
}

const ExpandButton = styled(Button)`
  position: absolute;
  left: -10px;
  user-select: none;

  & > svg {
    transform: rotate(270deg);
  }

  &.expanded > svg {
    transform: none;
  }

  &&.button-size-small {
    --button-icon-size: 14px;
    --button-icon-padding: 3px;

    margin-left: 0;
  }
`;
