/* eslint-disable react/prop-types */
import React, { useEffect, useRef, useState } from 'react';
import classNames from 'clsx';
import { useTheme } from '@mui/material/styles';

import copyToClipboard from '@/modules/@common/helpers/copy';

import {
  Box,
  Button,
  Checkbox,
  FormControl,
  FormControlLabel,
  FormLabel,
  InputLabel,
  MenuItem,
  Paper,
  Radio,
  RadioGroup,
  Select,
  Stack,
  Switch,
  TextField,
  Tooltip,
  Typography,
} from '@/mui/components';

import styles from './Playground.module.scss';

const getState = (propState) => propState.reduce((acc, c) => ({ ...acc, [c.stateId]: c.selected }), {});

function Playground(props) {
  const theme = useTheme();
  const ref = useRef(null);
  const [state, setState] = useState(getState(props.initialState));
  const [codeHTML, setCodeHTML] = useState(null);

  const onChange = (id, value) => {
    let nextState = state;

    setState((p) => {
      nextState = {
        ...p,
        [id]: value,
      };

      return nextState;
    });

    props.onChange?.(nextState, id, value, setState);
  };

  const getDependenceData = (dependOn) => {
    let isDepend = false;
    let dependFields = [];

    if (typeof dependOn === 'string' && !state[dependOn]) {
      isDepend = true;
      dependFields = [dependOn];
    } else if (Array.isArray(dependOn) && dependOn.some((d) => !state[d])) {
      isDepend = true;
      dependFields = [dependOn.filter((d) => !state[d])];
    }

    return { isDepend, dependFields };
  };

  const getRadioBlock = ({ id, stateId, options, dependOn }) => {
    const { isDepend, dependFields } = getDependenceData(dependOn);
    const component = (
      <FormControl key={stateId}>
        <FormLabel id="demo-radio-buttons-group-label">{stateId}</FormLabel>
        <RadioGroup
          value={state[stateId]}
          name={id}
          onChange={(event) => onChange(stateId, event.target.value)}
          disabled={isDepend}
        >
          {options.map((v) => (
            <FormControlLabel key={v} value={v} control={<Radio color="primary" />} label={v} />
          ))}
        </RadioGroup>
      </FormControl>
    );

    if (isDepend) {
      return <Tooltip title={`${dependFields} is required`}>{component}</Tooltip>;
    }

    return component;
  };

  const getCheckboxBlock = ({ stateId, dependOn }) => {
    const { isDepend, dependFields } = getDependenceData(dependOn);

    const component = (
      <FormControlLabel
        key={stateId}
        control={
          <Checkbox
            color="primary"
            checked={state[stateId]}
            onChange={() => onChange(stateId, !state[stateId])}
            disabled={isDepend}
          />
        }
        labelPlacement="end"
        label={stateId}
      />
    );

    if (isDepend) {
      return <Tooltip title={`${dependFields} is required`}>{component}</Tooltip>;
    }

    return component;
  };

  const getSwitchBlock = ({ stateId, dependOn }) => {
    const { isDepend, dependFields } = getDependenceData(dependOn);
    const component = (
      <FormControlLabel
        key={stateId}
        control={
          <Switch
            color="primary"
            checked={state[stateId]}
            onChange={() => onChange(stateId, !state[stateId])}
            disabled={isDepend}
          />
        }
        labelPlacement="end"
        label={stateId}
      />
    );

    if (isDepend) {
      return <Tooltip title={`${dependFields} is required`}>{component}</Tooltip>;
    }

    return component;
  };

  const getSelectBlock = ({ stateId, options, dependOn }) => {
    const { isDepend, dependFields } = getDependenceData(dependOn);

    const component = (
      <FormControl key={stateId} className={styles.Input}>
        <InputLabel id={stateId}>{stateId}</InputLabel>
        <Select
          labelId={stateId}
          value={state[stateId]}
          label={stateId}
          displayEmpty
          onChange={(event) => onChange(stateId, event.target.value)}
          disabled={isDepend}
        >
          {options.map((v) => {
            const label = v.label ?? v;
            const value = v.value ?? v;

            return (
              <MenuItem key={value} value={value}>
                {label}
              </MenuItem>
            );
          })}
        </Select>
      </FormControl>
    );

    if (isDepend) {
      return <Tooltip title={`${dependFields} is required`}>{component}</Tooltip>;
    }

    return component;
  };

  const getInputTextBlock = ({ stateId, dependOn }) => {
    const { isDepend, dependFields } = getDependenceData(dependOn);
    const component = (
      <FormControl key={stateId} className={styles.Input}>
        <TextField
          key={stateId}
          label={stateId}
          value={state[stateId]}
          variant="outlined"
          placeholder="text"
          type="text"
          onChange={(event) => onChange(stateId, event.target.value)}
          disabled={isDepend}
        />
      </FormControl>
    );

    if (isDepend) {
      return <Tooltip title={`${dependFields} is required`}>{component}</Tooltip>;
    }

    return component;
  };

  const getInputNumberBlock = ({ stateId, dependOn }) => {
    const { isDepend, dependFields } = getDependenceData(dependOn);
    const component = (
      <FormControl key={stateId} className={styles.Input}>
        <TextField
          key={stateId}
          label={stateId}
          value={state[stateId]}
          variant="outlined"
          placeholder="size"
          type="number"
          onChange={(event) => {
            onChange(stateId, parseInt(event.target.value, 10) || 0);
          }}
          disabled={isDepend}
        />
      </FormControl>
    );

    if (isDepend) {
      return <Tooltip title={`${dependFields} is required`}>{component}</Tooltip>;
    }

    return component;
  };

  const independentState = { ...state };
  props.initialState
    .filter((c) => !!c.dependOn)
    .forEach((c) => {
      const { isDepend } = getDependenceData(c.dependOn);
      if (isDepend) {
        delete independentState[c.stateId];
      }
    });

  const { component, code } = props.renderCallback(independentState);

  useEffect(() => {
    import('prismjs').then((Prism$) => {
      setCodeHTML({
        __html: Prism$.default.highlight(code, Prism$.default.languages.javascript, 'javascript'),
      });
    });
  }, [code]);

  return (
    <Paper elevation={2} className={styles.Wrapper}>
      <div className={styles.ContentWrapper}>
        {props.title && <Typography variant="h4">{props.title}</Typography>}
        <Paper
          elevation={0}
          sx={{
            display: 'flex',
            justifyContent: 'flex-start',
            alignItems: props.alignItems ?? 'center',
            minHeight: props.minHeight ?? '120px',
            maxWidth: !props.fullWidth ? (props.maxWidth ?? 800) : '100%',
            pt: 4,
            pb: 4,
            mb: 8,
          }}
        >
          {component}
        </Paper>
        <Stack spacing={theme.spacing(3, 2)} direction="row" flexWrap="wrap">
          {props.initialState
            .slice()
            .sort((a, b) => a.stateId.localeCompare(b.stateId))
            .map((c) => {
              const getComponent = {
                select: getSelectBlock,
                'input-text': getInputTextBlock,
                'input-number': getInputNumberBlock,
              }[c.type];

              return getComponent && <React.Fragment key={c.stateId}>{getComponent(c)}</React.Fragment>;
            })}
        </Stack>
        <Box sx={{ pb: 2 }} />
        <Stack spacing={theme.spacing(1, 2)} direction="row" flexWrap="wrap">
          {props.initialState
            .slice()
            .sort((a, b) => a.stateId.localeCompare(b.stateId))
            .map((c) => {
              const getComponent = {
                radio: getRadioBlock,
                checkbox: getCheckboxBlock,
                switch: getSwitchBlock,
              }[c.type];

              return getComponent && <React.Fragment key={c.stateId}>{getComponent(c)}</React.Fragment>;
            })}
        </Stack>
      </div>
      <Box sx={{ position: 'relative', width: '100%' }} className={styles.CodeWrapper}>
        <pre className={classNames(styles.Pre, 'language-javascript')} tabIndex={-1}>
          <code
            ref={ref}
            className={classNames(styles.Code, 'language-javascript')}
            dangerouslySetInnerHTML={codeHTML}
          />
        </pre>
        <div className={styles.CodeCopy}>
          <Button
            size="small"
            variant="outlined"
            color="success"
            onClick={() => copyToClipboard(ref.current.textContent)}
          >
            Copy
          </Button>
        </div>
      </Box>
    </Paper>
  );
}

export default Playground;
