import { useState } from 'react';
import { Box, Button, Table, TableBody, TableCell, TableRow, Typography, useTheme } from '@mui/material';
import DifferenceOutlinedIcon from '@mui/icons-material/DifferenceOutlined';

import { darkThemeText, fontFamilyMonospace, gray, green, red } from '../../themes/common';

import CommonDialog from './Dialog';

const PREVIEW_LENGTH = 40;

interface DiffLine {
  content: string;
  lineNumber: string;
  operation: string;
}

export const parseDiff = (details: readonly string[]): DiffLine[] =>
  details.map(line => {
    const [operation, lineNumber, ...rest] = line.replace(/^"|"$/g, '').split(',');
    return {
      operation,
      lineNumber: lineNumber ?? '',
      content: rest.join(',').replace(/\\"/g, '"')
    };
  });

export const isDiff = (details?: readonly string[]): boolean => !!details?.length && /^[+-]/.test(details[0]);

const diffColors = (isDark: boolean) =>
  isDark
    ? {
        text: darkThemeText,
        lineNumber: gray[300],
        addition: { number: green[800], code: green[900] },
        deletion: { number: red[800], code: red[900] }
      }
    : {
        text: gray[900],
        lineNumber: gray[600],
        addition: { number: green[100], code: green[50] },
        deletion: { number: red[100], code: red[50] }
      };

export const DiffTable = ({ details }: { details: readonly string[] }) => {
  const colors = diffColors(useTheme().palette.mode === 'dark');
  // `html.dark body *` colors every descendant, so `&&` is needed to win.
  const cellReset = {
    border: 'none',
    padding: 0,
    verticalAlign: 'top',
    lineHeight: '20px',
    fontFamily: fontFamilyMonospace,
    '&&, && *': { color: colors.text }
  };
  const lineNumberSx = {
    ...cellReset,
    '&&, && *': { color: colors.lineNumber },
    fontSize: 12,
    minWidth: 20,
    px: '10px',
    textAlign: 'right' as const,
    userSelect: 'none' as const,
    whiteSpace: 'nowrap' as const,
    width: '1%'
  };

  return (
    <Table size="small" sx={{ border: 'none', backgroundColor: 'transparent', width: '100%' }}>
      <TableBody>
        {parseDiff(details).map((line, i) => {
          const added = line.operation === '+';
          const rowColors = added ? colors.addition : colors.deletion;
          return (
            <TableRow key={i}>
              <TableCell sx={{ ...lineNumberSx, backgroundColor: rowColors.number }}>
                {added ? '' : line.lineNumber}
              </TableCell>
              <TableCell sx={{ ...lineNumberSx, backgroundColor: rowColors.number }}>
                {added ? line.lineNumber : ''}
              </TableCell>
              <TableCell sx={{ ...cellReset, backgroundColor: rowColors.code, px: '10px' }}>
                {line.operation}
              </TableCell>
              <TableCell sx={{ ...cellReset, backgroundColor: rowColors.code, pr: '10px' }}>
                <Box component="span" sx={{ whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }}>
                  {line.content}
                </Box>
              </TableCell>
            </TableRow>
          );
        })}
      </TableBody>
    </Table>
  );
};

interface DiffCellProps {
  details: readonly string[];
  title?: string;
}

/**
 * Table cell for a diff: a truncated preview with a button that appears on
 * hover and opens the full diff in a dialog.
 */
export const DiffCell = ({ details, title }: DiffCellProps) => {
  const [open, setOpen] = useState(false);
  const [first = ''] = details;
  const preview = first.length > PREVIEW_LENGTH ? `${first.slice(0, PREVIEW_LENGTH)}...` : first;

  return (
    <Box
      sx={{
        display: 'flex',
        alignItems: 'center',
        gap: 1,
        '&:hover .diff-reveal': { opacity: 1 }
      }}
    >
      <Typography variant="body2" component="span">
        {preview}
      </Typography>
      <Button
        className="diff-reveal"
        size="small"
        variant="outlined"
        color="inherit"
        startIcon={<DifferenceOutlinedIcon fontSize="small" />}
        onClick={() => setOpen(true)}
        sx={{
          flexShrink: 0,
          minWidth: 0,
          opacity: 0,
          padding: '2px 6px',
          transition: 'opacity 0.15s',
          '&:focus-visible': { opacity: 1 }
        }}
      >
        Show diff
      </Button>
      <CommonDialog
        open={open}
        onClose={() => setOpen(false)}
        title={title}
        size="md"
        fullWidth
        actionButtons={
          <Button onClick={() => setOpen(false)} variant="outlined" color="inherit">
            Close
          </Button>
        }
      >
        <DiffTable details={details} />
      </CommonDialog>
    </Box>
  );
};
