import { useState, useRef } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw';
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
import { Volume2, Loader2, Square } from 'lucide-react';
import type { ChatMessage } from '../types';
import { ToolActivity } from './ToolActivity';
import { QuestionActivity } from './QuestionActivity';
import { getRawUrl } from '../../FileViewer/file-types';
import { useFilesAPI } from '../../../hooks/useFilesAPI';
import { CopyButton } from './CopyButton';

const sanitizeSchema = {
  ...defaultSchema,
  tagNames: (defaultSchema.tagNames ?? []).filter((tag) => tag !== 'script' && tag !== 'iframe' && tag !== 'object' && tag !== 'embed' && tag !== 'form'),
  attributes: {
    ...defaultSchema.attributes,
    '*': (defaultSchema.attributes?.['*'] ?? []).filter((attr) => typeof attr === 'string' && !attr.startsWith('on')),
  },
};

// Matches absolute image file paths, e.g. /home/user/pic.png or /tmp/photo.jpg
const IMAGE_PATH_RE = /(\/(?:home\/[^/\s]+\/)?[^\s`"'<>\n\r[\]()]+\.(?:png|jpg|jpeg|gif|webp|svg|bmp|ico))/gi;

function pathToImageUrl(filePath: string): string {
  // Strip /home/{username} prefix — the file browser root=home serves from the user's home dir
  const relative = filePath.replace(/^\/home\/[^/]+/, '') || '/';
  return getRawUrl(relative, 'home');
}

function injectImages(text: string): string {
  // Split on fenced code blocks and inline code so we don't touch paths inside backticks
  const parts = text.split(/(```[\s\S]*?```|`[^`\n]+`)/g);
  return parts
    .map((part, i) => {
      if (i % 2 === 1) return part; // inside code — leave untouched
      return part.replace(IMAGE_PATH_RE, (match) => {
        const filename = match.split('/').pop() ?? 'image';
        return `![${filename}](${pathToImageUrl(match)})`;
      });
    })
    .join('');
}

const FRONTMATTER_RE = /^<frontmatter>([\s\S]*?)<\/frontmatter>\s*/;

const CollapsibleBlock = ({ label, content }: { label: string; content: string }) => (
  <div className="flex justify-center">
    <details className="max-w-[85%] rounded-2xl bg-duck-dark/5 border border-duck-dark/10 px-4 py-2 text-sm">
      <summary className="text-xs text-duck-dark/50 hover:text-duck-dark/70 cursor-pointer select-none">
        {label}
      </summary>
      <div className="mt-1.5 text-xs text-duck-dark/60 whitespace-pre-wrap border-l-2 border-duck-dark/15 pl-2 max-h-48 overflow-y-auto">
        {content}
      </div>
    </details>
  </div>
);

type TtsState = 'idle' | 'loading' | 'playing';

const ReadAloudButton = ({ id, text }: { id: string; text: string }) => {
  const [state, setState] = useState<TtsState>('idle');
  const audioRef = useRef<HTMLAudioElement | null>(null);
  const api = useFilesAPI('user-data');

  const handleClick = async () => {
    if (state === 'playing') {
      audioRef.current?.pause();
      audioRef.current = null;
      setState('idle');
      return;
    }
    if (state === 'loading') return;

    setState('loading');
    try {
      const { audioPath, audioRoot } = await api.ttsText(id, text);
      const url = getRawUrl(audioPath, audioRoot);
      const audio = new Audio(url);
      audioRef.current = audio;
      audio.onended = () => { audioRef.current = null; setState('idle'); };
      audio.onerror = () => { audioRef.current = null; setState('idle'); };
      await audio.play();
      setState('playing');
    } catch {
      setState('idle');
    }
  };

  return (
    <button
      type="button"
      onClick={handleClick}
      className="p-1 rounded text-duck-dark dark:text-white opacity-60 hover:opacity-100 transition-opacity cursor-pointer"
    >
      {state === 'loading' && <Loader2 className="h-3.5 w-3.5 animate-spin" />}
      {state === 'playing' && <Square className="h-3.5 w-3.5" />}
      {state === 'idle' && <Volume2 className="h-3.5 w-3.5" />}
    </button>
  );
};

type MessageBubbleProps = {
  message: ChatMessage;
  onAnswer?: (text: string) => void;
};

export const MessageBubble = ({ message, onAnswer }: MessageBubbleProps) => {
  switch (message.role) {
    case 'user': {
      const rawText = typeof message.text === 'string' ? message.text : '';
      const match = rawText.match(FRONTMATTER_RE);
      const metadata = match ? match[1]!.trim() : null;
      const text = match ? rawText.slice(match[0]!.length) : rawText;
      return (
        <>
          {metadata && <CollapsibleBlock label="Frontmatter" content={metadata} />}
          <div className="flex justify-end">
            <div className="max-w-[80%] rounded-2xl rounded-tr-sm bg-duck-yellow/10 border border-duck-yellow/20 px-4 py-2.5 text-sm text-duck-dark">
              {message.images?.map((img, i) => (
                <img key={i} src={img.dataUrl} alt={img.filename} className="max-w-full max-h-64 rounded-lg mb-2" />
              ))}
              <div className="whitespace-pre-wrap">{text}</div>
            </div>
          </div>
        </>
      );
    }

    case 'system':
      return <CollapsibleBlock label="System prompt" content={message.text} />;

    case 'assistant': {
      const assistantText = typeof message.text === 'string' ? message.text : '';
      if (!assistantText) return null;
      return (
        <div className="flex justify-start group">
          <div className="max-w-[85%]">
            <div className="rounded-2xl rounded-tl-sm bg-muted/50 border border-border/50 px-4 py-2.5">
              <div className="chat-md">
                <ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw, [rehypeSanitize, sanitizeSchema]]}>
                  {injectImages(assistantText)}
                </ReactMarkdown>
              </div>
              <div className="flex justify-end -mb-1 -mr-1 gap-0.5">
                <CopyButton text={assistantText} />
                <ReadAloudButton id={message.id ?? crypto.randomUUID()} text={assistantText} />
              </div>
            </div>
          </div>
        </div>
      );
    }

    case 'tool':
      if (message.toolName === 'question' && onAnswer) {
        return <QuestionActivity message={message} onAnswer={onAnswer} />;
      }
      return <ToolActivity message={message} />;

    case 'result':
      return (
        <div className="flex justify-center py-1">
          <span className="text-xs text-duck-dark/40">
            Done · ${message.cost.totalUSD.toFixed(3)} · {message.cost.inputTokens + message.cost.outputTokens} tokens
          </span>
        </div>
      );

    case 'error':
      return (
        <div className="flex justify-start group">
          <div className="relative max-w-[80%] rounded-2xl bg-red-50 dark:bg-red-950/50 border border-red-200 dark:border-red-800 px-4 py-2.5 text-sm text-red-700 dark:text-red-300">
            {message.text}
            <div className="flex justify-end -mb-1 -mr-1">
              <CopyButton text={message.text} />
            </div>
          </div>
        </div>
      );
  }
};

type StreamingBubbleProps = {
  text: string;
};

export const StreamingBubble = ({ text }: StreamingBubbleProps) => {
  if (!text) return null;
  return (
    <div className="flex justify-start">
      <div className="max-w-[85%] rounded-2xl rounded-tl-sm bg-muted/50 border border-border/50 px-4 py-2.5">
        <div className="chat-md">
          <ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw, [rehypeSanitize, sanitizeSchema]]}>
            {injectImages(text)}
          </ReactMarkdown>
          <span className="inline-block w-2 h-4 bg-duck-teal/60 animate-pulse ml-0.5 align-middle" />
        </div>
      </div>
    </div>
  );
};
