import { useEffect, useState } from 'react';
import { Box, Button, Typography } from '@mui/material';
import ArrowRightIcon from '@mui/icons-material/ArrowRight';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import { useDispatch } from 'react-redux';
import { setAlert } from '../redux/features/providers/providersSlice';
import { dispatchAlert } from '../redux/Actions/ProvidersAction';

const styles = {
	viewer: {
		backgroundColor: '#1f2430',
		borderRadius: 1,
		p: 2,
		maxHeight: 520,
		overflow: 'auto',
		fontFamily: 'Menlo, Consolas, monospace',
		fontSize: 13,
		lineHeight: 1.7,
	},
	branch: { borderLeft: '1px solid #3a4152', ml: '9px', pl: 1.5 },
	row: { display: 'flex', alignItems: 'flex-start' },
	caret: { color: '#9aa4bf', cursor: 'pointer', display: 'flex', flexShrink: 0 },
	spacer: { width: 20, flexShrink: 0 },
	key: { color: '#79b8ff' },
	string: { color: '#8ddb9d', wordBreak: 'break-word' },
	number: { color: '#c6a0f6' },
	boolean: { color: '#e5a24f' },
	count: { color: '#767e94', fontStyle: 'italic' },
	punctuation: { color: '#767e94' },
};

const isBranch = value => value !== null && typeof value === 'object';

const Leaf = ({ value }) => {
	if (typeof value === 'string')
		return (
			<Box component='span' sx={styles.string}>
				"{value}"
			</Box>
		);

	if (typeof value === 'number')
		return (
			<Box component='span' sx={styles.number}>
				{value}
			</Box>
		);

	return (
		<Box component='span' sx={styles.boolean}>
			{String(value)}
		</Box>
	);
};

/**
 * One key in the carrier's reply. Objects and arrays open two levels deep so the shape of
 * the response is visible without scrolling; the signal prop carries Expand all / Collapse
 * all without taking individual carets away from the reader.
 */
const Node = ({ label, value, depth, signal }) => {
	const [open, setOpen] = useState(depth < 2);

	useEffect(() => {
		if (signal) setOpen(signal.expand);
	}, [signal]);

	if (!isBranch(value)) {
		return (
			<Box sx={styles.row}>
				<Box sx={styles.spacer} />
				<Box component='span'>
					<Box component='span' sx={styles.key}>
						{label}
					</Box>
					<Box component='span' sx={styles.punctuation}>
						:{' '}
					</Box>
					<Leaf value={value} />
				</Box>
			</Box>
		);
	}

	const entries = Array.isArray(value)
		? value.map((item, index) => [String(index), item])
		: Object.entries(value);

	return (
		<Box>
			<Box sx={styles.row}>
				<Box
					sx={styles.caret}
					onClick={() => setOpen(!open)}
					title={open ? 'Collapse' : 'Expand'}>
					{open ? (
						<ArrowDropDownIcon fontSize='small' />
					) : (
						<ArrowRightIcon fontSize='small' />
					)}
				</Box>
				<Box component='span'>
					<Box component='span' sx={styles.key}>
						{label}
					</Box>
					<Box component='span' sx={styles.punctuation}>
						: {Array.isArray(value) ? '[' : '{'}{' '}
					</Box>
					<Box component='span' sx={styles.count}>
						{entries.length} {entries.length === 1 ? 'item' : 'items'}
					</Box>
				</Box>
			</Box>

			{open && (
				<Box sx={styles.branch}>
					{entries.map(([key, item]) => (
						<Node
							key={key}
							label={key === '' ? '""' : key}
							value={item}
							depth={depth + 1}
							signal={signal}
						/>
					))}
				</Box>
			)}
		</Box>
	);
};

const LogResponseTree = ({ response }) => {
	const [signal, setSignal] = useState(null);
	const [copied, setCopied] = useState(false);
	const dispatch = useDispatch();

	const confirmCopy = () => {
		setCopied(true);
		dispatch(setAlert(dispatchAlert(true, 'Response JSON copied to the clipboard.', 'success')));
		setTimeout(() => setCopied(false), 1600);
	};

	/**
	 * Copy the response JSON
	 *
	 * The clipboard API only exists on a store served over HTTPS, so a temporary field and
	 * execCommand carry the copy everywhere else.
	 */
	const handleCopy = async () => {
		const json = JSON.stringify(response ?? {}, null, 2);

		try {
			await navigator.clipboard.writeText(json);
			confirmCopy();
		} catch (error) {
			const field = document.createElement('textarea');
			field.value = json;
			field.style.position = 'fixed';
			field.style.opacity = '0';
			document.body.appendChild(field);
			field.select();

			const copiedFromField = document.execCommand('copy');
			document.body.removeChild(field);

			if (copiedFromField) {
				confirmCopy();
				return;
			}

			dispatch(
				setAlert(dispatchAlert(true, 'Unable to copy the response. Please copy it manually.', 'error'))
			);
		}
	};

	const hasResponse = isBranch(response) && Object.keys(response).length > 0;

	return (
		<>
			<Box display='flex' gap={1} mb={2}>
				<Button
					size='small'
					variant='outlined'
					sx={{ textTransform: 'capitalize' }}
					onClick={() => setSignal({ expand: true, id: Date.now() })}>
					Expand all
				</Button>
				<Button
					size='small'
					variant='outlined'
					sx={{ textTransform: 'capitalize' }}
					onClick={() => setSignal({ expand: false, id: Date.now() })}>
					Collapse all
				</Button>
				<Button
					size='small'
					variant='outlined'
					sx={{ textTransform: 'capitalize' }}
					onClick={handleCopy}>
					{copied ? 'Copied' : 'Copy JSON'}
				</Button>
			</Box>

			<Box sx={styles.viewer}>
				{hasResponse ? (
					<Node label='root' value={response} depth={0} signal={signal} />
				) : (
					<Typography variant='body2' sx={{ color: '#9aa4bf' }}>
						The carrier returned no response.
					</Typography>
				)}
			</Box>
		</>
	);
};

export default LogResponseTree;
