import { useState, useRef, useCallback, useEffect, type PointerEvent as RPointerEvent, type ChangeEvent } from 'react';
import { SendHorizontal, Mic, Square, Trash2, Paperclip, Camera, X } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import type { Attachment } from '../../hooks/useChat';
import { useSpeechRecognition } from '../../hooks/useSpeechRecognition';

interface Props {
  onSend: (msg: string, attachments?: Attachment[], audioData?: string) => void;
  onStop: () => void;
  streaming: boolean;
  whisperEnabled?: boolean;
  onTranscribe?: (audio: string) => Promise<{ transcript?: string }>;
  onRecordingChange?: (recording: boolean) => void;
  onAudioReady?: (audioData: string) => void;
}

function formatTime(s: number) {
  const mins = Math.floor(s / 60);
  const secs = s % 60;
  return `${mins}:${secs.toString().padStart(2, '0')}`;
}

/** Compress an image to fit under maxBytes while staying visually clear for an LLM. */
function compressImage(dataUrl: string, maxBytes = 4 * 1024 * 1024): Promise<string> {
  return new Promise((resolve) => {
    const img = new Image();
    img.onload = () => {
      const MAX_DIM = 1600;
      let { width, height } = img;

      // Scale down if larger than MAX_DIM on either axis
      if (width > MAX_DIM || height > MAX_DIM) {
        const ratio = Math.min(MAX_DIM / width, MAX_DIM / height);
        width = Math.round(width * ratio);
        height = Math.round(height * ratio);
      }

      const canvas = document.createElement('canvas');
      canvas.width = width;
      canvas.height = height;
      const ctx = canvas.getContext('2d')!;
      ctx.drawImage(img, 0, 0, width, height);

      // Try quality levels until under maxBytes
      for (const q of [0.8, 0.6, 0.4, 0.3]) {
        const result = canvas.toDataURL('image/jpeg', q);
        const size = Math.round((result.length - 'data:image/jpeg;base64,'.length) * 0.75);
        if (size <= maxBytes) { resolve(result); return; }
      }
      // Fallback: lowest quality
      resolve(canvas.toDataURL('image/jpeg', 0.2));
    };
    img.onerror = () => resolve(dataUrl); // pass through on error
    img.src = dataUrl;
  });
}

const DRAFT_KEY = 'bloby_draft';

export default function InputBar({ onSend, onStop, streaming, whisperEnabled, onTranscribe, onRecordingChange, onAudioReady }: Props) {
  const { start: startSpeech, stop: stopSpeech, abort: abortSpeech, isSupported: webSpeechSupported } = useSpeechRecognition();
  const voiceEnabled = whisperEnabled || webSpeechSupported;
  const [text, setText] = useState(() => {
    try { return localStorage.getItem(DRAFT_KEY) || ''; } catch { return ''; }
  });
  const [attachments, setAttachments] = useState<Attachment[]>([]);
  const draftTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const [isRecording, _setIsRecording] = useState(false);
  const setIsRecording = useCallback((v: boolean) => {
    _setIsRecording(v);
    onRecordingChange?.(v);
  }, [onRecordingChange]);
  const [recordingTime, setRecordingTime] = useState(0);
  const hasText = text.trim().length > 0;
  const hasContent = hasText || attachments.length > 0;

  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const fileRef = useRef<HTMLInputElement>(null);
  const cameraRef = useRef<HTMLInputElement>(null);
  const trashRef = useRef<HTMLDivElement>(null);
  const micRef = useRef<HTMLDivElement>(null);
  const startXRef = useRef(0);
  const dragRef = useRef(0);
  const holdTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const isHolding = useRef(false);
  const pointerIsDown = useRef(false);
  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const mediaRecorderRef = useRef<MediaRecorder | null>(null);
  const audioChunksRef = useRef<Blob[]>([]);
  const streamRef = useRef<MediaStream | null>(null);

  // Auto-resize textarea up to 4 lines, then scroll
  useEffect(() => {
    const el = textareaRef.current;
    if (!el) return;
    el.style.height = '0px';
    // 4 lines ~ 5.5rem = 88px at text-sm (14px * 1.625 line-height * 4)
    el.style.height = `${Math.min(el.scrollHeight, 88)}px`;
  }, [text]);

  // Debounced draft save to localStorage
  useEffect(() => {
    if (draftTimerRef.current) clearTimeout(draftTimerRef.current);
    draftTimerRef.current = setTimeout(() => {
      try { localStorage.setItem(DRAFT_KEY, text); } catch {}
    }, 500);
    return () => { if (draftTimerRef.current) clearTimeout(draftTimerRef.current); };
  }, [text]);

  // Recording timer
  useEffect(() => {
    if (!isRecording) return;
    intervalRef.current = setInterval(() => setRecordingTime((t) => t + 1), 1000);
    return () => { if (intervalRef.current) clearInterval(intervalRef.current); };
  }, [isRecording]);

  const stopRecording = useCallback(async (cancelled: boolean) => {
    if (intervalRef.current) clearInterval(intervalRef.current);
    if (holdTimerRef.current) { clearTimeout(holdTimerRef.current); holdTimerRef.current = null; }
    isHolding.current = false;

    const recorder = mediaRecorderRef.current;
    const stream = streamRef.current;

    if (cancelled) {
      stream?.getTracks().forEach((t) => t.stop());
      mediaRecorderRef.current = null;
      streamRef.current = null;
      audioChunksRef.current = [];
      abortSpeech();
    } else if (recorder && recorder.state !== 'inactive') {
      // Whisper path: stop MediaRecorder and use its audio
      recorder.onstop = async () => {
        stream?.getTracks().forEach((t) => t.stop());
        const blob = new Blob(audioChunksRef.current, { type: 'audio/webm' });
        audioChunksRef.current = [];
        mediaRecorderRef.current = null;
        streamRef.current = null;

        if (blob.size < 1000) return;

        const fileReader = new FileReader();
        fileReader.onloadend = async () => {
          const dataUrl = fileReader.result as string;
          const base64 = dataUrl.split(',')[1];
          if (!base64) return;

          // Show pending audio bubble immediately (before transcription)
          onAudioReady?.(dataUrl);

          const pendingAtts = attachments.length > 0 ? attachments : undefined;
          try {
            let data: { transcript?: string };
            if (onTranscribe) {
              data = await onTranscribe(base64);
            } else {
              const res = await fetch('/api/whisper/transcribe', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ audio: base64 }),
              });
              data = await res.json();
            }
            onSend(data.transcript?.trim() || '', pendingAtts, dataUrl);
          } catch (err) {
            console.error('[InputBar] Whisper transcription error:', err);
            onSend('', pendingAtts, dataUrl);
          }
          if (pendingAtts) setAttachments([]);
        };
        fileReader.readAsDataURL(blob);
      };
      recorder.stop();
    } else {
      // Web Speech API path (no MediaRecorder): get transcript directly
      stream?.getTracks().forEach((t) => t.stop());
      mediaRecorderRef.current = null;
      streamRef.current = null;
      audioChunksRef.current = [];

      try {
        const transcript = await stopSpeech();
        if (transcript.trim()) {
          const pendingAtts = attachments.length > 0 ? attachments : undefined;
          onSend(transcript.trim(), pendingAtts);
          if (pendingAtts) setAttachments([]);
        }
      } catch (err) {
        console.error('[InputBar] Web Speech stop error:', err);
      }
    }

    if (micRef.current) micRef.current.style.transform = '';
    setIsRecording(false);
    setRecordingTime(0);
    dragRef.current = 0;
  }, [onSend, onTranscribe, attachments, whisperEnabled, abortSpeech, stopSpeech]);

  // ── File handling ──

  const addFile = useCallback((file: File) => {
    const isImage = file.type.startsWith('image/');
    const isPdf = file.type === 'application/pdf';
    if (!isImage && !isPdf) return;

    const reader = new FileReader();
    reader.onload = async (e) => {
      let preview = e.target?.result as string;

      // Compress images to stay under API limits
      if (isImage) {
        preview = await compressImage(preview);
      }

      setAttachments((prev) => [
        ...prev,
        {
          id: Math.random().toString(36).slice(2),
          type: isImage ? 'image' : 'file',
          name: file.name,
          preview,
        },
      ]);
    };
    reader.readAsDataURL(file);
  }, []);

  const handleFileChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
    const files = e.target.files;
    if (!files) return;
    for (let i = 0; i < files.length; i++) addFile(files[i]);
    e.target.value = '';
  }, [addFile]);

  const removeAttachment = useCallback((id: string) => {
    setAttachments((prev) => prev.filter((a) => a.id !== id));
  }, []);

  // Handle paste for images
  useEffect(() => {
    const handlePaste = (e: ClipboardEvent) => {
      if (!e.clipboardData?.items) return;
      for (const item of e.clipboardData.items) {
        if (item.type.startsWith('image/')) {
          e.preventDefault();
          const file = item.getAsFile();
          if (file) addFile(file);
        }
      }
    };
    document.addEventListener('paste', handlePaste);
    return () => document.removeEventListener('paste', handlePaste);
  }, [addFile]);

  const handleSend = () => {
    if (!hasContent) return;
    onSend(text, attachments.length > 0 ? attachments : undefined);
    setText('');
    setAttachments([]);
    try { localStorage.removeItem(DRAFT_KEY); } catch {}
    requestAnimationFrame(() => textareaRef.current?.focus());
  };

  // ── Device detection ──
  const isMobile = 'ontouchstart' in window || navigator.maxTouchPoints > 0;

  // ── Start recording helper (shared by desktop click & mobile hold) ──
  const beginRecording = useCallback(async () => {
    if (!voiceEnabled) return;
    try {
      if (whisperEnabled) {
        const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
        if (!isMobile && !pointerIsDown.current) {
          // Desktop: pointer already released is fine (click), keep going
        } else if (isMobile && !pointerIsDown.current) {
          stream.getTracks().forEach((t) => t.stop());
          return;
        }
        streamRef.current = stream;
        const mimeType = MediaRecorder.isTypeSupported('audio/webm;codecs=opus') ? 'audio/webm;codecs=opus' : 'audio/webm';
        const recorder = new MediaRecorder(stream, { mimeType });
        audioChunksRef.current = [];
        recorder.ondataavailable = (ev) => {
          if (ev.data.size > 0) audioChunksRef.current.push(ev.data);
        };
        mediaRecorderRef.current = recorder;
        recorder.start();
      } else {
        startSpeech(!isMobile); // desktop: continuous=true, mobile: continuous=false
        if (isMobile && !pointerIsDown.current) {
          abortSpeech();
          return;
        }
      }

      isHolding.current = true;
      setIsRecording(true);
      setRecordingTime(0);
    } catch (err) {
      console.error('[InputBar] recording setup failed:', err);
    }
  }, [voiceEnabled, whisperEnabled, startSpeech, abortSpeech]);

  // ── Mic pointer handlers ──
  const handleMicDown = useCallback((e: RPointerEvent) => {
    e.preventDefault();

    if (!isMobile) {
      // Desktop: click-to-toggle
      if (isRecording) {
        stopRecording(false);
        return;
      }
      pointerIsDown.current = true;
      beginRecording();
      return;
    }

    // Mobile: press-and-hold
    pointerIsDown.current = true;
    startXRef.current = e.clientX;
    dragRef.current = 0;
    (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);

    holdTimerRef.current = setTimeout(() => {
      try { navigator.vibrate?.(50); } catch {}
      beginRecording();
    }, 200);
  }, [isMobile, isRecording, voiceEnabled, beginRecording, stopRecording]);

  const handleMicMove = useCallback((e: RPointerEvent) => {
    if (!isMobile || !isHolding.current) return;
    const dx = Math.min(0, e.clientX - startXRef.current);
    dragRef.current = dx;
    if (micRef.current) micRef.current.style.transform = `translateX(${dx}px)`;

    if (trashRef.current) {
      const trashRect = trashRef.current.getBoundingClientRect();
      const trashCenterX = trashRect.left + trashRect.width / 2;
      if (Math.abs(e.clientX - trashCenterX) < 36) {
        stopRecording(true);
      }
    }
  }, [isMobile, stopRecording]);

  const handleMicUp = useCallback(() => {
    pointerIsDown.current = false;
    if (holdTimerRef.current) { clearTimeout(holdTimerRef.current); holdTimerRef.current = null; }
    // Desktop: don't stop on release (toggle mode)
    if (!isMobile) return;
    // Mobile: stop on release
    if (!isHolding.current) return;
    stopRecording(false);
  }, [isMobile, stopRecording]);

  const handleMicCancel = useCallback(() => {
    pointerIsDown.current = false;
    if (holdTimerRef.current) { clearTimeout(holdTimerRef.current); holdTimerRef.current = null; }
    if (isHolding.current) stopRecording(true);
  }, [stopRecording]);

  return (
    <div className="shrink-0 p-3 relative">
      {/* ── Normal input (always mounted to keep keyboard alive) ── */}
      <div
        className={`flex flex-col transition-opacity duration-100 ${
          isRecording ? 'opacity-0 pointer-events-none' : 'opacity-100'
        }`}
      >
        {/* ── Attachment previews ── */}
        <AnimatePresence>
          {attachments.length > 0 && (
            <motion.div
              initial={{ height: 0, opacity: 0 }}
              animate={{ height: 'auto', opacity: 1 }}
              exit={{ height: 0, opacity: 0 }}
              transition={{ duration: 0.15 }}
              className="overflow-hidden"
            >
              <div className="flex gap-2 px-3 pt-2.5 pb-2 flex-wrap bg-white/[0.04] border border-white/[0.08] rounded-2xl mb-2">
                {attachments.map((att) => (
                  <div key={att.id} className="relative group flex-shrink-0">
                    {att.type === 'image' ? (
                      <img
                        src={att.preview}
                        alt={att.name}
                        className="w-14 h-14 rounded-lg object-cover border border-white/10"
                      />
                    ) : (
                      <div className="w-14 h-14 rounded-lg bg-white/5 border border-white/10 flex flex-col items-center justify-center gap-0.5 px-1">
                        <svg className="w-5 h-5 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
                          <path strokeLinecap="round" strokeLinejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
                        </svg>
                        <span className="text-[8px] text-muted-foreground truncate w-full text-center">{att.name.split('.').pop()}</span>
                      </div>
                    )}
                    <button
                      onClick={() => removeAttachment(att.id)}
                      className="absolute -top-1.5 -right-1.5 w-4 h-4 bg-black/80 border border-white/20 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"
                    >
                      <X className="w-2.5 h-2.5 text-white" />
                    </button>
                  </div>
                ))}
              </div>
            </motion.div>
          )}
        </AnimatePresence>

        <div className="flex items-end gap-2">
          <div className="flex-1 flex items-end bg-white rounded-2xl px-4">
            <textarea
              ref={textareaRef}
              value={text}
              onChange={(e) => setText(e.target.value)}
              onKeyDown={(e) => {
                // Desktop: Enter sends, Shift+Enter inserts newline
                // Mobile: Enter always inserts newline (no hardware keyboard)
                const isMobile = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
                if (e.key === 'Enter' && !e.shiftKey && !isMobile) {
                  e.preventDefault();
                  if (hasContent) handleSend();
                }
              }}
              placeholder="Type a message..."
              rows={1}
              className="flex-1 resize-none bg-transparent text-gray-900 py-3 text-sm outline-none placeholder:text-gray-400 overflow-y-auto"
              style={{ maxHeight: 88 }}
            />
            {/* Hidden file inputs for native PWA pickers */}
            <input ref={fileRef} type="file" className="hidden" accept="image/*,.pdf" multiple onChange={handleFileChange} />
            <input ref={cameraRef} type="file" className="hidden" accept="image/*" capture="environment" onChange={handleFileChange} />
            <button
              type="button"
              onClick={() => fileRef.current?.click()}
              className="shrink-0 p-1.5 mb-2 text-gray-400 hover:text-gray-600 transition-colors"
            >
              <Paperclip className="h-4 w-4" />
            </button>
            <button
              type="button"
              onClick={() => cameraRef.current?.click()}
              className="shrink-0 p-1.5 mb-2 text-gray-400 hover:text-gray-600 transition-colors"
            >
              <Camera className="h-4 w-4" />
            </button>
          </div>
          {/* Stop button removed — v2 long-lived conversation model lets users always chat.
              Users can say "stop" or "cancel" naturally if needed.
          {streaming && !hasText ? (
            <button
              onClick={onStop}
              className="flex items-center justify-center h-12 w-12 shrink-0 rounded-full bg-destructive text-destructive-foreground transition-colors"
            >
              <Square className="h-4 w-4" />
            </button>
          ) : */}
          {hasText ? (
            <button
              onClick={handleSend}
              className="flex items-center justify-center h-12 w-12 shrink-0 rounded-full bg-white text-gray-900 transition-colors hover:bg-gray-100"
            >
              <SendHorizontal className="h-4 w-4" />
            </button>
          ) : (
            <div
              className={`flex items-center justify-center h-12 w-12 shrink-0 rounded-full transition-colors touch-none select-none ${
                voiceEnabled
                  ? 'bg-gradient-brand text-white cursor-pointer'
                  : 'bg-gray-200 text-gray-400 cursor-default'
              }`}
              title={!voiceEnabled ? 'Voice input requires Chrome, Edge, or Safari. Enable Whisper in Settings for any browser.' : undefined}
              onPointerDown={voiceEnabled ? handleMicDown : undefined}
              onPointerMove={voiceEnabled ? handleMicMove : undefined}
              onPointerUp={voiceEnabled ? handleMicUp : undefined}
              onPointerCancel={voiceEnabled ? handleMicCancel : undefined}
            >
              <Mic className="h-4 w-4" />
            </div>
          )}
        </div>
      </div>

      {/* ── Recording overlay (absolute so no layout shift) ── */}
      <AnimatePresence>
        {isRecording && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            transition={{ duration: 0.1 }}
            className="absolute inset-0 p-3 flex items-center touch-none"
          >
            <div className="flex items-center w-full gap-2">
              {/* Left: pulsing dot + timer (no bg) */}
              <div className="flex items-center gap-2 shrink-0">
                <span className="relative flex h-2 w-2 shrink-0">
                  <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-destructive opacity-75" />
                  <span className="relative inline-flex rounded-full h-2 w-2 bg-destructive" />
                </span>
                <span className="text-sm font-medium tabular-nums">
                  {formatTime(recordingTime)}
                </span>
              </div>

              {/* Pill: content depends on desktop vs mobile */}
              <div className="flex-1 flex items-center bg-muted rounded-full h-12 pl-1 pr-0.5">
                {isMobile ? (
                  <>
                    <div
                      ref={trashRef}
                      className="flex items-center justify-center h-9 w-9 shrink-0 rounded-full bg-destructive/10 text-destructive"
                    >
                      <Trash2 className="h-4 w-4" />
                    </div>
                    <div className="flex-1 flex justify-center min-w-0">
                      <motion.span
                        className="text-[13px] whitespace-nowrap select-none font-medium"
                        style={{
                          backgroundImage: 'linear-gradient(90deg, #999 0%, #999 35%, #fff 50%, #999 65%, #999 100%)',
                          backgroundSize: '200% 100%',
                          WebkitBackgroundClip: 'text',
                          WebkitTextFillColor: 'transparent',
                          backgroundClip: 'text',
                        }}
                        animate={{ backgroundPosition: ['200% center', '-200% center'] }}
                        transition={{ duration: 2.5, repeat: Infinity, ease: 'linear' }}
                      >
                        ◄◄ slide to cancel
                      </motion.span>
                    </div>
                    {/* Draggable mic (DOM transform for 60fps) */}
                    <div
                      ref={micRef}
                      className="shrink-0 touch-none select-none will-change-transform relative z-10"
                      style={{ transition: 'none' }}
                      onPointerDown={handleMicDown}
                      onPointerMove={handleMicMove}
                      onPointerUp={handleMicUp}
                      onPointerCancel={handleMicCancel}
                    >
                      <div
                        className="flex items-center justify-center h-11 w-11 rounded-full bg-destructive text-destructive-foreground"
                        style={{ animation: 'mic-pulse-scale 2s ease-in-out infinite' }}
                      >
                        <Mic className="h-5 w-5" />
                      </div>
                    </div>
                  </>
                ) : (
                  <>
                    <button
                      onClick={() => stopRecording(true)}
                      className="flex items-center justify-center h-9 w-9 shrink-0 rounded-full bg-destructive/10 text-destructive hover:bg-destructive/20 transition-colors"
                    >
                      <Trash2 className="h-4 w-4" />
                    </button>
                    <div className="flex-1 flex justify-center min-w-0">
                      <span className="text-[13px] whitespace-nowrap select-none font-medium text-muted-foreground">
                        Click mic to stop
                      </span>
                    </div>
                    <button
                      onClick={() => stopRecording(false)}
                      className="shrink-0"
                    >
                      <div className="flex items-center justify-center h-11 w-11 rounded-full bg-destructive text-destructive-foreground hover:bg-destructive/90 transition-colors">
                        <Square className="h-4 w-4" />
                      </div>
                    </button>
                  </>
                )}
              </div>
            </div>
          </motion.div>
        )}
      </AnimatePresence>

    </div>
  );
}
