'use client';

import { useEffect, useMemo, useRef, useState } from 'react';
import {
  Ban,
  FilePenLine,
  MessagesSquare,
  Send,
  Terminal,
  TriangleAlert,
} from 'lucide-react';
import { toast } from 'sonner';

import type { Doc } from '@spoon/backend/convex/_generated/dataModel.js';
import { Badge, Button, Textarea } from '@spoon/ui';

import { DiffFileView, useDiffTheme } from './diff-file-view';
import { parseDiffFileForPath } from './diff-utils';

type ActivityFilter = 'all' | 'chat' | 'activity' | 'files' | 'errors';

const filters: { value: ActivityFilter; label: string }[] = [
  { value: 'all', label: 'All' },
  { value: 'chat', label: 'Chat' },
  { value: 'activity', label: 'Tools' },
  { value: 'files', label: 'Files' },
  { value: 'errors', label: 'Errors' },
];

const formatEventTime = (value: number) =>
  new Date(value).toLocaleTimeString([], {
    hour: 'numeric',
    minute: '2-digit',
    second: '2-digit',
  });

const eventIcon = (event: Doc<'agentJobEvents'>) => {
  if (event.level === 'error') return <TriangleAlert className='size-3' />;
  if (event.phase === 'edit') return <FilePenLine className='size-3' />;
  if (event.phase === 'check' || event.phase === 'test') {
    return <Terminal className='size-3' />;
  }
  return <MessagesSquare className='size-3' />;
};

export const AgentThread = ({
  jobId,
  messages,
  events,
  interactions,
  workspaceChanges,
  disabled,
  agentTurnActive,
  onOpenFile,
  onOpenDiff,
}: {
  jobId: string;
  messages: Doc<'agentJobMessages'>[];
  events: Doc<'agentJobEvents'>[];
  interactions: Doc<'agentInteractionRequests'>[];
  workspaceChanges: Doc<'agentWorkspaceChanges'>[];
  disabled: boolean;
  agentTurnActive: boolean;
  onOpenFile: (path: string) => void;
  onOpenDiff: (path: string) => void;
}) => {
  const [content, setContent] = useState('');
  const [sending, setSending] = useState(false);
  const [replying, setReplying] = useState<string>();
  const [filter, setFilter] = useState<ActivityFilter>('all');
  const diffTheme = useDiffTheme();
  const scrollRef = useRef<HTMLDivElement>(null);
  const chatMessages = useMemo(
    () =>
      messages.filter((message) => {
        if (message.role === 'system') return false;
        if (message.role === 'tool') return false;
        if (message.role === 'assistant' && !message.content.trim()) {
          return message.status === 'streaming' && agentTurnActive;
        }
        return true;
      }),
    [agentTurnActive, messages],
  );
  const toolMessages = useMemo(
    () =>
      messages.filter(
        (message) => message.role === 'tool' && message.content.trim(),
      ),
    [messages],
  );
  const failedMessages = useMemo(
    () => chatMessages.filter((message) => message.status === 'failed'),
    [chatMessages],
  );
  const errorEvents = useMemo(
    () => events.filter((event) => event.level === 'error'),
    [events],
  );
  const visibleMessages =
    filter === 'activity' || filter === 'files' || filter === 'errors'
      ? filter === 'errors'
        ? failedMessages
        : []
      : chatMessages;
  const visibleToolMessages =
    filter === 'all' || filter === 'activity' ? toolMessages : [];
  const visibleEvents = filter === 'errors' ? errorEvents : [];
  const visibleChanges =
    filter === 'chat' || filter === 'activity' || filter === 'errors'
      ? []
      : workspaceChanges;

  useEffect(() => {
    const node = scrollRef.current;
    if (!node) return;
    const distanceFromBottom =
      node.scrollHeight - node.scrollTop - node.clientHeight;
    if (distanceFromBottom < 160 || agentTurnActive) {
      if (typeof node.scrollTo === 'function') {
        node.scrollTo({ top: node.scrollHeight, behavior: 'smooth' });
      } else {
        node.scrollTop = node.scrollHeight;
      }
    }
  }, [
    agentTurnActive,
    events.length,
    interactions.length,
    messages.length,
    workspaceChanges.length,
  ]);

  const send = async () => {
    if (!content.trim()) return;
    setSending(true);
    try {
      const response = await fetch(`/api/agent-jobs/${jobId}/message`, {
        method: 'POST',
        body: JSON.stringify({ content }),
      });
      if (!response.ok) throw new Error(await response.text());
      setContent('');
    } catch (error) {
      console.error(error);
      toast.error('Could not send message.');
    } finally {
      setSending(false);
    }
  };

  const abort = async () => {
    try {
      const response = await fetch(`/api/agent-jobs/${jobId}/agent/abort`, {
        method: 'POST',
      });
      if (!response.ok) throw new Error(await response.text());
      toast.success('Agent turn aborted.');
    } catch (error) {
      console.error(error);
      toast.error('Could not abort agent.');
    }
  };

  const reply = async (
    interaction: Doc<'agentInteractionRequests'>,
    responseValue: string,
  ) => {
    setReplying(interaction._id);
    try {
      const response = await fetch(
        `/api/agent-jobs/${jobId}/interactions/${interaction._id}/reply`,
        {
          method: 'POST',
          body: JSON.stringify({
            externalRequestId: interaction.externalRequestId,
            response: responseValue,
          }),
        },
      );
      if (!response.ok) throw new Error(await response.text());
      toast.success('Response sent.');
    } catch (error) {
      console.error(error);
      toast.error('Could not answer interaction.');
    } finally {
      setReplying(undefined);
    }
  };

  return (
    <div className='flex h-full min-h-0 flex-col overflow-hidden'>
      <div className='border-border flex flex-none items-start justify-between gap-3 border-b p-3'>
        <div>
          <div className='flex items-center gap-2'>
            <h2 className='text-sm font-semibold'>Agent thread</h2>
            {agentTurnActive ? (
              <Badge variant='secondary'>Working</Badge>
            ) : null}
          </div>
          <p className='text-muted-foreground text-xs'>
            Messages, tool activity, and requests persist with this workspace.
          </p>
        </div>
        <Button
          type='button'
          variant='outline'
          size='sm'
          disabled={disabled || !agentTurnActive}
          onClick={abort}
        >
          <Ban className='size-3' />
          Abort
        </Button>
      </div>
      <div className='border-border flex flex-none gap-1 overflow-x-auto border-b px-3 py-2'>
        {filters.map((item) => (
          <Button
            key={item.value}
            type='button'
            variant={filter === item.value ? 'secondary' : 'ghost'}
            size='sm'
            className='h-7 flex-none text-xs'
            onClick={() => setFilter(item.value)}
          >
            {item.label}
          </Button>
        ))}
      </div>
      <div
        ref={scrollRef}
        className='min-h-0 flex-1 space-y-3 overflow-y-auto overscroll-contain p-3'
      >
        {(filter === 'all' || filter === 'chat') && interactions.length > 0
          ? interactions.map((interaction) => (
              <article
                key={interaction._id}
                className='border-primary/40 bg-primary/5 rounded-md border p-3 text-sm'
              >
                <div className='mb-2 flex items-center justify-between gap-2'>
                  <span className='font-medium'>{interaction.title}</span>
                  <Badge variant='outline' className='capitalize'>
                    {interaction.status}
                  </Badge>
                </div>
                <p className='text-sm whitespace-pre-wrap'>
                  {interaction.body}
                </p>
                {interaction.status === 'pending' ? (
                  <div className='mt-3 flex gap-2'>
                    <Button
                      type='button'
                      size='sm'
                      disabled={replying === interaction._id}
                      onClick={() => void reply(interaction, 'once')}
                    >
                      Approve
                    </Button>
                    <Button
                      type='button'
                      size='sm'
                      variant='outline'
                      disabled={replying === interaction._id}
                      onClick={() => void reply(interaction, 'reject')}
                    >
                      Reject
                    </Button>
                  </div>
                ) : null}
              </article>
            ))
          : null}
        {visibleMessages.map((message) => (
          <article
            key={message._id}
            className={
              message.role === 'user'
                ? 'border-border bg-muted ml-6 rounded-md border p-3 text-sm'
                : message.status === 'failed'
                  ? 'border-destructive/40 bg-destructive/5 rounded-md border p-3 text-sm'
                  : 'border-border bg-background rounded-md border p-3 text-sm'
            }
          >
            <div className='mb-2 flex items-center justify-between gap-2'>
              <span className='font-medium'>
                {message.role === 'assistant' ? 'Agent' : 'You'}
              </span>
              {message.status === 'failed' || message.status === 'streaming' ? (
                <Badge
                  variant={
                    message.status === 'failed' ? 'destructive' : 'outline'
                  }
                  className='capitalize'
                >
                  {message.status === 'streaming' ? 'Working' : 'Failed'}
                </Badge>
              ) : null}
            </div>
            <p className='whitespace-pre-wrap'>
              {message.content ||
                (message.status === 'streaming' ? 'Working...' : '')}
            </p>
          </article>
        ))}
        {visibleToolMessages.map((message) => (
          <article
            key={message._id}
            className='border-border bg-background rounded-md border p-3 text-sm'
          >
            <div className='mb-2 flex items-center gap-2'>
              <Terminal className='text-primary size-4' />
              <span className='font-medium'>Tool</span>
              {message.status === 'streaming' ? (
                <Badge variant='outline'>Running</Badge>
              ) : null}
            </div>
            <pre className='text-muted-foreground max-h-56 overflow-auto text-xs whitespace-pre-wrap'>
              {message.content}
            </pre>
          </article>
        ))}
        {visibleChanges.map((change) => {
          const changedFile = parseDiffFileForPath(change.diff, change.path);
          const hasDiff = Boolean(changedFile && !changedFile.isBinary);
          const hasRenderableHunk = Boolean(
            changedFile && hasDiff && changedFile.hunkText.includes('@@'),
          );
          return (
            <article
              key={change._id}
              className='border-border bg-background rounded-md border p-3 text-sm'
            >
              <div className='flex items-center justify-between gap-3'>
                <div className='min-w-0'>
                  <div className='flex items-center gap-2'>
                    <FilePenLine className='text-primary size-4 flex-none' />
                    <span className='truncate font-mono text-xs'>
                      {change.path}
                    </span>
                  </div>
                  <p className='text-muted-foreground mt-1 text-xs capitalize'>
                    {change.source} {change.changeType}
                    {changedFile ? (
                      <span className='ml-2 font-mono normal-case'>
                        <span className='text-emerald-500'>
                          +{changedFile.additions}
                        </span>{' '}
                        <span className='text-red-500'>
                          −{changedFile.deletions}
                        </span>
                      </span>
                    ) : null}
                  </p>
                </div>
                <div className='flex flex-none items-center gap-2'>
                  {hasDiff ? (
                    <Button
                      type='button'
                      variant='outline'
                      size='sm'
                      onClick={() => onOpenDiff(change.path)}
                    >
                      View diff
                    </Button>
                  ) : null}
                  {change.path !== '.' ? (
                    <Button
                      type='button'
                      variant='outline'
                      size='sm'
                      onClick={() => onOpenFile(change.path)}
                    >
                      Open
                    </Button>
                  ) : null}
                </div>
              </div>
              {hasRenderableHunk && changedFile ? (
                <details className='mt-3'>
                  <summary className='text-muted-foreground cursor-pointer text-xs'>
                    File diff
                  </summary>
                  <div className='border-border mt-2 max-h-72 overflow-auto rounded border'>
                    <DiffFileView
                      file={changedFile}
                      mode='unified'
                      theme={diffTheme}
                      fontSize={11}
                    />
                  </div>
                </details>
              ) : null}
            </article>
          );
        })}
        {visibleEvents.slice(-80).map((event) => (
          <article
            key={event._id}
            className={
              event.level === 'error'
                ? 'border-destructive/40 bg-destructive/5 rounded-md border p-2 text-xs'
                : 'border-border text-muted-foreground rounded-md border border-dashed p-2 text-xs'
            }
          >
            <div className='flex items-center justify-between gap-2'>
              <span className='flex min-w-0 items-center gap-1 font-medium capitalize'>
                {eventIcon(event)}
                {event.phase} / {event.level}
              </span>
              <span>{formatEventTime(event.createdAt)}</span>
            </div>
            <p className='mt-1 whitespace-pre-wrap'>{event.message}</p>
            {event.metadata ? (
              <details className='mt-2'>
                <summary className='cursor-pointer'>Details</summary>
                <pre className='bg-muted mt-1 max-h-40 overflow-auto rounded p-2 whitespace-pre-wrap'>
                  {event.metadata}
                </pre>
              </details>
            ) : null}
          </article>
        ))}
        {visibleMessages.length === 0 &&
        visibleToolMessages.length === 0 &&
        visibleEvents.length === 0 &&
        visibleChanges.length === 0 &&
        (filter !== 'chat' || interactions.length === 0) ? (
          <p className='text-muted-foreground p-3 text-sm'>
            No {filter === 'all' ? 'agent activity' : filter} has been recorded
            yet.
          </p>
        ) : null}
      </div>
      <div className='border-border flex-none space-y-2 border-t p-3'>
        <Textarea
          value={content}
          placeholder='Ask the agent to inspect, explain, or change this fork.'
          disabled={disabled || sending}
          onChange={(event) => setContent(event.target.value)}
          onKeyDown={(event) => {
            if (event.key === 'Enter' && !event.shiftKey) {
              event.preventDefault();
              void send();
            }
          }}
        />
        <Button
          type='button'
          className='w-full'
          disabled={disabled || sending || !content.trim()}
          onClick={send}
        >
          <Send className='size-4' />
          {sending ? 'Sending...' : 'Send'}
        </Button>
      </div>
    </div>
  );
};
