import { useCallback, useEffect, useRef, useState } from 'react';
import {
  Alert,
  Box,
  Button,
  Group,
  Image,
  Loader,
  Paper,
  SegmentedControl,
  Stack,
  Text,
  Title,
} from '@mantine/core';
import {
  IconInfoCircle,
  IconPencil,
  IconTrash,
  IconUpload,
  IconWriting,
} from '@tabler/icons-react';
import { useTranslation } from 'react-i18next';
import { notify } from '../feedback/notify';
import { useErrorHandler } from '../feedback/use-error-handler';
import { toCanvasPoint } from '../input/canvas-point';

/** Mirrors the API's own limits (`ProfileService.saveSignature`). */
const ACCEPTED = ['image/png', 'image/jpeg'];
const MAX_BYTES = 2 * 1024 * 1024;

/**
 * The drawing surface's backing-store size.
 *
 * Fixed rather than matched to the rendered element: this is what gets printed
 * on a Seaman Book, so the stored image must not vary with the width of the
 * browser window it happened to be drawn in. The canvas is displayed at
 * whatever width the layout gives it and scaled to these dimensions.
 */
const PAD_WIDTH = 800;
const PAD_HEIGHT = 260;

/**
 * Captures a specimen signature, drawn or uploaded.
 *
 * Drawing is a plain canvas with pointer events — one element and ~40 lines,
 * where a signature-pad dependency would be a package to keep patched. Pointer
 * events (not mouse + touch separately) cover mouse, finger and stylus in one
 * set of handlers.
 */
export interface SignaturePadProps {
  /** Short-lived link to the signature on file, or null when there is none. */
  currentUrl: string | null;
  isLoading: boolean;
  isUploading: boolean;
  isDeleting: boolean;
  onUpload: (file: File) => Promise<unknown>;
  onDelete: () => Promise<unknown>;
}

export function SignaturePad({
  currentUrl,
  isLoading,
  isUploading,
  isDeleting,
  onUpload,
  onDelete,
}: SignaturePadProps) {
  const { t } = useTranslation();
  const { handleError } = useErrorHandler();
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const drawing = useRef(false);
  // Whether anything has actually been drawn — a blank canvas still encodes to
  // a valid PNG, so without this "Save" would happily store an empty image.
  const [hasInk, setHasInk] = useState(false);
  const [mode, setMode] = useState<'draw' | 'upload'>('draw');

  const busy = isUploading || isDeleting;

  const context = useCallback(() => {
    const canvas = canvasRef.current;
    const ctx = canvas?.getContext('2d');
    if (!ctx) return null;
    ctx.lineWidth = 2.5;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.strokeStyle = '#111';
    return ctx;
  }, []);

  // The stored signature is flattened onto white before upload, so a canvas
  // left transparent would print as a black box on some renderers.
  const clear = useCallback(() => {
    const ctx = context();
    if (!ctx) return;
    ctx.fillStyle = '#fff';
    ctx.fillRect(0, 0, PAD_WIDTH, PAD_HEIGHT);
    setHasInk(false);
  }, [context]);

  useEffect(() => {
    if (mode === 'draw') clear();
  }, [mode, clear]);

  const pointAt = (event: React.PointerEvent<HTMLCanvasElement>) =>
    toCanvasPoint(
      event.clientX,
      event.clientY,
      event.currentTarget.getBoundingClientRect(),
      { width: PAD_WIDTH, height: PAD_HEIGHT },
    );

  const onPointerDown = (event: React.PointerEvent<HTMLCanvasElement>) => {
    const ctx = context();
    if (!ctx) return;
    // Keeps strokes tracking the pointer when it leaves the canvas mid-signature
    // rather than ending the line at the edge.
    event.currentTarget.setPointerCapture(event.pointerId);
    drawing.current = true;
    const { x, y } = pointAt(event);
    ctx.beginPath();
    ctx.moveTo(x, y);
    // A tap with no movement should still leave a mark (a dot on an "i").
    ctx.lineTo(x, y);
    ctx.stroke();
    setHasInk(true);
  };

  const onPointerMove = (event: React.PointerEvent<HTMLCanvasElement>) => {
    if (!drawing.current) return;
    const ctx = context();
    if (!ctx) return;
    const { x, y } = pointAt(event);
    ctx.lineTo(x, y);
    ctx.stroke();
  };

  const onPointerUp = () => {
    drawing.current = false;
  };

  const save = async (file: File) => {
    try {
      await onUpload(file);
      notify.success(t('profile.signature.saved'));
      if (mode === 'draw') clear();
    } catch (error) {
      handleError(error);
    }
  };

  const saveDrawing = () => {
    const canvas = canvasRef.current;
    if (!canvas || !hasInk) return;
    canvas.toBlob((blob) => {
      if (!blob) {
        notify.error(t('profile.signature.drawFailed'));
        return;
      }
      void save(new File([blob], 'signature.png', { type: 'image/png' }));
    }, 'image/png');
  };

  // Validated here as well as server-side so the reason is immediate and the
  // user is not made to wait on an upload that is going to be rejected.
  const onFile = (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    // Lets the same file be picked again after a rejection.
    event.target.value = '';
    if (!file) return;
    if (!ACCEPTED.includes(file.type)) {
      notify.error(t('profile.signature.badType'));
      return;
    }
    if (file.size > MAX_BYTES) {
      notify.error(t('profile.signature.tooLarge'));
      return;
    }
    void save(file);
  };

  const handleDelete = async () => {
    try {
      await onDelete();
      notify.success(t('profile.signature.removed'));
    } catch (error) {
      handleError(error);
    }
  };

  return (
    <Stack gap="lg">
      <div>
        <Title order={5}>{t('profile.signature.title')}</Title>
        <Text size="sm" c="dimmed">
          {t('profile.signature.description')}
        </Text>
      </div>

      <Alert icon={<IconInfoCircle size={16} />} color="blue" variant="light">
        {t('profile.signature.reissueNotice')}
      </Alert>

      {isLoading ? (
        <Group justify="center" py="xl">
          <Loader size="sm" />
        </Group>
      ) : currentUrl ? (
        <Paper p="md" radius="md" withBorder>
          <Stack gap="sm">
            <Text size="sm" fw={500}>
              {t('profile.signature.current')}
            </Text>
            <Image
              src={currentUrl}
              alt={t('profile.signature.currentAlt')}
              fit="contain"
              h={120}
              bg="white"
            />
            <Group>
              <Button
                variant="light"
                color="red"
                size="xs"
                leftSection={<IconTrash size={16} />}
                onClick={handleDelete}
                loading={isDeleting}
              >
                {t('profile.signature.remove')}
              </Button>
            </Group>
          </Stack>
        </Paper>
      ) : (
        <Text size="sm" c="dimmed">
          {t('profile.signature.none')}
        </Text>
      )}

      <SegmentedControl
        value={mode}
        onChange={(value) => setMode(value as 'draw' | 'upload')}
        data={[
          { value: 'draw', label: t('profile.signature.modeDraw') },
          { value: 'upload', label: t('profile.signature.modeUpload') },
        ]}
      />

      {mode === 'draw' ? (
        <Stack gap="sm">
          <Box
            component="canvas"
            ref={canvasRef}
            width={PAD_WIDTH}
            height={PAD_HEIGHT}
            onPointerDown={onPointerDown}
            onPointerMove={onPointerMove}
            onPointerUp={onPointerUp}
            onPointerCancel={onPointerUp}
            style={{
              width: '100%',
              height: 'auto',
              aspectRatio: `${PAD_WIDTH} / ${PAD_HEIGHT}`,
              border: '1px dashed var(--mantine-color-gray-4)',
              borderRadius: 'var(--mantine-radius-md)',
              background: '#fff',
              // Stops the browser panning/zooming the page mid-stroke on touch.
              touchAction: 'none',
              cursor: 'crosshair',
            }}
          />
          <Group>
            <Button
              leftSection={<IconWriting size={16} />}
              onClick={saveDrawing}
              loading={isUploading}
              disabled={!hasInk || busy}
            >
              {t('profile.signature.save')}
            </Button>
            <Button
              variant="default"
              leftSection={<IconPencil size={16} />}
              onClick={clear}
              disabled={!hasInk || busy}
            >
              {t('profile.signature.clear')}
            </Button>
          </Group>
        </Stack>
      ) : (
        <Stack gap="sm">
          <Button
            component="label"
            variant="light"
            leftSection={<IconUpload size={16} />}
            loading={isUploading}
            disabled={busy}
            style={{ alignSelf: 'flex-start' }}
          >
            {t('profile.signature.choose')}
            <input
              type="file"
              hidden
              accept={ACCEPTED.join(',')}
              onChange={onFile}
            />
          </Button>
          <Text size="xs" c="dimmed">
            {t('profile.signature.fileHint')}
          </Text>
        </Stack>
      )}
    </Stack>
  );
}
