import type { KeyboardEvent, RefObject } from 'react';
import { useState, useRef } from 'react';
import { Loader2, Mic, Send, Square } from 'lucide-react';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import type { ModelOption } from '@/state/useModels';
import type { ChatMessage, Attachment } from './types';
import { ModelSelector } from './ModelSelector';
import { AttachmentList } from './AttachmentList';
import { AttachButton } from './AttachButton';
import { WebpageDialog } from './WebpageDialog';

const blobToWav = async (blob: Blob): Promise<Blob> => {
  const ctx = new AudioContext();
  const buf = await ctx.decodeAudioData(await blob.arrayBuffer());
  await ctx.close();

  const samples = buf.getChannelData(0);
  const len = samples.length;
  const sr = buf.sampleRate;
  const ab = new ArrayBuffer(44 + len * 2);
  const v = new DataView(ab);

  const s = (o: number, str: string) => {
    for (let i = 0; i < str.length; i++) v.setUint8(o + i, str.charCodeAt(i));
  };
  s(0, 'RIFF');
  v.setUint32(4, 36 + len * 2, true);
  s(8, 'WAVE');
  s(12, 'fmt ');
  v.setUint32(16, 16, true);
  v.setUint16(20, 1, true);
  v.setUint16(22, 1, true);
  v.setUint32(24, sr, true);
  v.setUint32(28, sr * 2, true);
  v.setUint16(32, 2, true);
  v.setUint16(34, 16, true);
  s(36, 'data');
  v.setUint32(40, len * 2, true);

  for (let i = 0; i < len; i++) {
    const val = Math.max(-1, Math.min(1, samples[i]!));
    v.setInt16(44 + i * 2, val < 0 ? val * 0x8000 : val * 0x7fff, true);
  }

  return new Blob([ab], { type: 'audio/wav' });
};

type InputAreaProps = {
  input: string;
  onInputChange: (value: string) => void;
  onKeyDown: (ev: KeyboardEvent<HTMLTextAreaElement>) => void;
  onSend: () => void;
  onStop: () => void;
  isGenerating: boolean;
  isConnected: boolean;
  commandFeedback: string | null;
  textareaRef: RefObject<HTMLTextAreaElement | null>;
  messages: ChatMessage[];
  availableModels: ModelOption[];
  selectedModel: string | null;
  onModelChange: (modelId: string) => void;
  model: string | null;
  attachments: Attachment[];
  onAttachWebpage: (url: string) => void;
  onAttachImage: (file: File) => void;
  onRemoveAttachment: (index: number) => void;
};

export const InputArea = ({
  input,
  onInputChange,
  onKeyDown,
  onSend,
  onStop,
  isGenerating,
  isConnected,
  commandFeedback,
  textareaRef,
  messages,
  availableModels,
  selectedModel,
  onModelChange,
  model,
  attachments,
  onAttachWebpage,
  onAttachImage,
  onRemoveAttachment,
}: InputAreaProps) => {
  const [urlDialogOpen, setUrlDialogOpen] = useState(false);
  const [recording, setRecording] = useState(false);
  const [transcribing, setTranscribing] = useState(false);
  const mediaRecorderRef = useRef<MediaRecorder | null>(null);
  const chunksRef = useRef<Blob[]>([]);

  const handleMicClick = async () => {
    if (recording) {
      const recorder = mediaRecorderRef.current;
      if (!recorder) return;

      setRecording(false);

      try {
        if (recorder.state === 'inactive') {
          recorder.stream.getTracks().forEach((t) => t.stop());
          return;
        }

        const blob = await new Promise<Blob>((resolve, reject) => {
          const timeout = setTimeout(() => reject(new Error('Recording stop timed out')), 5000);
          recorder.onstop = () => {
            clearTimeout(timeout);
            resolve(new Blob(chunksRef.current, { type: recorder.mimeType }));
            chunksRef.current = [];
          };
          recorder.stop();
        });

        recorder.stream.getTracks().forEach((t) => t.stop());

        if (blob.size === 0) {
          toast.error('No audio was captured');
          return;
        }

        setTranscribing(true);
        try {
          const wav = await blobToWav(blob);
          const formData = new FormData();
          formData.append('file', wav, 'recording.wav');
          formData.append('temperature', '0.0');
          formData.append('temperature_inc', '0.2');
          formData.append('response_format', 'json');

          const res = await fetch('http://macmini:8178/inference', { method: 'POST', body: formData });
          if (!res.ok) throw new Error(`Whisper returned ${res.status}`);
          const json = await res.json();
          if (json.error) throw new Error(json.error);
          const text = (json.text ?? '').trim();
          if (text) onInputChange(input + (input.length > 0 ? ' ' : '') + text);
        } finally {
          setTranscribing(false);
        }
      } catch (err) {
        recorder.stream?.getTracks().forEach((t) => t.stop());
        chunksRef.current = [];
        toast.error(err instanceof Error ? err.message : 'Recording failed');
      }
      return;
    }

    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      const recorder = new MediaRecorder(stream);
      mediaRecorderRef.current = recorder;
      chunksRef.current = [];

      recorder.ondataavailable = (ev) => {
        if (ev.data.size > 0) chunksRef.current.push(ev.data);
      };

      recorder.start(250);
      setRecording(true);
    } catch (err) {
      toast.error(err instanceof Error ? err.message : 'Could not access microphone');
    }
  };

  return (
    <div className="shrink-0 border-t border-duck-dark/10 bg-background/60 p-2 md:p-3">
      {commandFeedback && (
        <div className="mb-2 px-3 py-1.5 text-xs text-duck-teal bg-duck-teal/10 rounded-md">{commandFeedback}</div>
      )}

      <AttachmentList attachments={attachments} onRemove={onRemoveAttachment} />

      <div className="flex items-end gap-1 md:gap-2">
        <AttachButton
          onAttachImage={onAttachImage}
          onAttachWebpage={() => setUrlDialogOpen(true)}
        />
        <button
          type="button"
          disabled={transcribing}
          onClick={handleMicClick}
          className="relative shrink-0 h-7 w-7 md:h-9 md:w-9 flex items-center justify-center rounded-lg text-duck-dark/40 hover:text-duck-dark/70 hover:bg-duck-dark/5 transition-colors cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
        >
          {transcribing ? (
            <Loader2 className="h-4 w-4 animate-spin" />
          ) : recording ? (
            <>
              <span className="absolute inset-0 rounded-lg animate-ping bg-red-400/30" />
              <Square className="h-3.5 w-3.5 text-red-500" />
            </>
          ) : (
            <Mic className="h-4 w-4" />
          )}
        </button>

        <textarea
          ref={textareaRef}
          value={input}
          onChange={(ev) => onInputChange(ev.target.value)}
          onKeyDown={onKeyDown}
          onPaste={(ev) => {
            const items = ev.clipboardData?.items;
            if (!items) return;
            for (const item of Array.from(items)) {
              if (item.type.startsWith('image/')) {
                ev.preventDefault();
                const file = item.getAsFile();
                if (file) onAttachImage(file);
                return;
              }
            }
          }}
          placeholder="Type a message..."
          rows={1}
          className="min-w-0 flex-1 resize-none rounded-lg border border-duck-dark/20 bg-background/80 px-2 py-1.5 md:px-3 md:py-2 text-base md:text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-2 focus:ring-duck-teal/30 focus:border-duck-teal/50"
        />
        {isGenerating ? (
          <Button onClick={onStop} variant="destructive" size="icon" className="shrink-0 h-7 w-7 md:h-9 md:w-9 cursor-pointer">
            <Square className="h-4 w-4" />
          </Button>
        ) : (
          <Button
            onClick={onSend}
            disabled={!input.trim() || !isConnected}
            size="icon"
            className="shrink-0 h-7 w-7 md:h-9 md:w-9 bg-duck-teal hover:bg-duck-teal/90 cursor-pointer disabled:opacity-40"
          >
            <Send className="h-4 w-4" />
          </Button>
        )}
      </div>
      <ModelSelector
        messages={messages}
        availableModels={availableModels}
        selectedModel={selectedModel}
        onModelChange={onModelChange}
        model={model}
        isConnected={isConnected}
        isGenerating={isGenerating}
      />

      <WebpageDialog
        open={urlDialogOpen}
        onOpenChange={setUrlDialogOpen}
        onSubmit={onAttachWebpage}
      />
    </div>
  );
};
