import { useCallback, useEffect, useRef, useState } from 'react';
import QRCode from 'qrcode';
import { Upload, Play, Pause } from 'lucide-react';
import {
  chunkFile,
  crc32,
  encodeData,
  encodeMeta,
  newSessionId,
  DEFAULT_CHUNK_BYTES,
} from './protocol';

// Sender: a file becomes a looping carousel of QR frames.
//
// The loop never ends on its own. A receiver joining halfway simply picks up what it can and fills the
// gaps on later passes, which is what replaces fountain coding here — see protocol.ts for why.
//
// The meta frame is re-emitted every META_EVERY frames rather than only at the start, so a receiver that
// arrives late learns the filename and total without waiting for a full cycle.

const META_EVERY = 12;

/** Frames per second. Faster is not better: past ~10 a phone camera starts missing more than it gains. */
const SPEEDS = [4, 6, 8, 12] as const;

export const QrSender = () => {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const [file, setFile] = useState<{ name: string; mime: string; bytes: Uint8Array } | null>(null);
  const [frames, setFrames] = useState<string[]>([]);
  const [index, setIndex] = useState(0);
  const [playing, setPlaying] = useState(true);
  const [fps, setFps] = useState<number>(6);

  const onPick = useCallback(async (picked: File) => {
    const bytes = new Uint8Array(await picked.arrayBuffer());
    setFile({ name: picked.name, mime: picked.type, bytes });

    const chunks = chunkFile(bytes, DEFAULT_CHUNK_BYTES);
    const sid = newSessionId();
    const meta = encodeMeta({
      sid,
      total: chunks.length,
      size: bytes.length,
      crc32: crc32(bytes),
      name: picked.name,
      mime: picked.type,
    });

    // Build the whole carousel up front — encoding per tick would jitter the frame rate.
    const built: string[] = [];
    chunks.forEach((chunk, i) => {
      if (i % META_EVERY === 0) built.push(meta);
      built.push(encodeData(sid, i, chunk));
    });
    built.push(meta);

    setFrames(built);
    setIndex(0);
    setPlaying(true);
  }, []);

  // Advance the carousel.
  useEffect(() => {
    if (!playing || frames.length === 0) return;
    const timer = setInterval(() => setIndex((i) => (i + 1) % frames.length), 1000 / fps);
    return () => clearInterval(timer);
  }, [playing, frames.length, fps]);

  // Draw the current frame.
  useEffect(() => {
    const canvas = canvasRef.current;
    const text = frames[index];
    if (!canvas || !text) return;
    // Error correction L: these frames are short-lived and repeated forever, so spending QR capacity on
    // recovery buys less than keeping the code sparse enough to scan quickly.
    QRCode.toCanvas(canvas, text, { errorCorrectionLevel: 'L', margin: 2, width: 512 }).catch(() => {
      /* a frame that fails to render is skipped; the next tick draws the following one */
    });
  }, [frames, index]);

  return (
    <div className="flex h-full w-full flex-col items-center gap-3 overflow-y-auto p-3">
      <label className="flex w-full cursor-pointer items-center justify-center gap-2 rounded-md border border-dashed border-duck-dark/25 py-3 text-sm text-duck-dark/70 hover:border-duck-teal hover:text-duck-teal">
        <Upload className="h-4 w-4" />
        {file ? file.name : 'Choose a file to send'}
        <input
          type="file"
          className="hidden"
          onChange={(ev) => {
            const picked = ev.target.files?.[0];
            if (picked) void onPick(picked);
          }}
        />
      </label>

      {frames.length > 0 && (
        <>
          <canvas ref={canvasRef} className="max-w-full rounded bg-white" />

          <div className="flex items-center gap-3 text-xs text-duck-dark/60">
            <button
              type="button"
              onClick={() => setPlaying((p) => !p)}
              className="flex cursor-pointer items-center gap-1 hover:text-duck-teal"
            >
              {playing ? <Pause className="h-3.5 w-3.5" /> : <Play className="h-3.5 w-3.5" />}
              {playing ? 'Pause' : 'Play'}
            </button>
            <span>
              frame {index + 1} / {frames.length}
            </span>
            <span>{file ? `${(file.bytes.length / 1024).toFixed(1)} KB` : ''}</span>
          </div>

          <div className="flex items-center gap-1 text-xs">
            <span className="text-duck-dark/40">fps</span>
            {SPEEDS.map((s) => (
              <button
                key={s}
                type="button"
                onClick={() => setFps(s)}
                className={`cursor-pointer rounded px-1.5 py-0.5 ${
                  fps === s ? 'bg-duck-teal/15 text-duck-teal' : 'text-duck-dark/50 hover:text-duck-teal'
                }`}
              >
                {s}
              </button>
            ))}
          </div>

          <p className="text-center text-[10px] leading-relaxed text-duck-dark/40">
            Keep this on screen until the receiver reports 100%. It loops forever — a missed frame is
            picked up on the next pass.
          </p>
        </>
      )}
    </div>
  );
};
