import { useEffect, useRef, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import {
  Bot,
  Brain,
  CheckCircle2,
  CircleStop,
  Database,
  Loader2,
  MessageCircleMore,
  Send,
  Sparkles,
  XCircle,
} from 'lucide-react';
import { get } from '@/lib/api';
import { useIsMobile } from '@/hooks/use-media-query';
import { usePiggyCurrentContext } from '@/lib/piggy-context';
import {
  streamPiggyChat,
  type PiggyChatContext,
  type PiggyChatEvent,
  type PiggyChatTurn,
  type PiggyStatus,
} from '@/lib/piggy-chat';
import { Badge, Button, EmptyState, cn } from './ui';
import {
  Drawer,
  DrawerContent,
  DrawerDescription,
  DrawerHeader,
  DrawerTitle,
} from './ui/drawer';
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
} from './ui/sheet';
import { Textarea } from './ui/textarea';

interface ToolStep {
  id: string;
  name: string;
  arguments: unknown;
  state: 'running' | 'succeeded' | 'failed';
  error?: string;
}

interface TranscriptMessage {
  id: string;
  role: 'user' | 'assistant';
  content: string;
  reasoning?: string;
  tools?: ToolStep[];
  error?: string;
  pending?: boolean;
}

export function PiggyAskButton({
  context,
  prompt,
  label = 'Ask Piggy',
  variant = 'outline',
}: {
  context?: PiggyChatContext;
  prompt?: string;
  label?: string;
  variant?: React.ComponentProps<typeof Button>['variant'];
}) {
  const [open, setOpen] = useState(false);
  const status = usePiggyStatus();
  const unavailable = status.data && !status.data.canUse;
  // An explicit prop always wins. Every existing call site passes the record
  // the user pressed the button on, and the ambient page context is a guess
  // that must never displace it.
  const ambient = usePiggyCurrentContext();
  return (
    <>
      <Button
        type="button"
        variant={variant}
        disabled={Boolean(unavailable)}
        title={unavailable ? 'Piggy is disabled or this credential lacks read access.' : undefined}
        onClick={() => setOpen(true)}
      >
        <MessageCircleMore aria-hidden />
        {label}
      </Button>
      <ResponsivePiggyChat
        open={open}
        onOpenChange={setOpen}
        context={context ?? ambient}
        initialPrompt={prompt}
      />
    </>
  );
}

export function PiggyChatWorkspace() {
  const status = usePiggyStatus();
  if (status.isLoading) return <div className="h-96 animate-pulse rounded-xl bg-surface-2" />;
  if (!status.data?.canUse) {
    return (
      <EmptyState
        icon={<Bot />}
        title="Piggy is unavailable"
        description={
          status.data?.enabled
            ? 'This credential does not have read access.'
            : 'An administrator must enable the isolated Piggy runtime. No question is sent while this state is shown.'
        }
      />
    );
  }
  return <PiggyChatPanel className="h-[calc(100dvh-12rem)] min-h-[32rem] rounded-xl border border-border bg-surface" />;
}

export function ResponsivePiggyChat({
  open,
  onOpenChange,
  context,
  initialPrompt,
}: {
  open: boolean;
  onOpenChange(open: boolean): void;
  context?: PiggyChatContext;
  initialPrompt?: string;
}) {
  // The same breakpoint the shell switches navigation at. It used to be `md`,
  // which meant a 900px tablet got the desktop side sheet sliding in behind
  // the phone tab bar it was still showing.
  const desktop = !useIsMobile();
  if (desktop) {
    return (
      <Sheet open={open} onOpenChange={onOpenChange}>
        <SheetContent side="right" className="flex h-dvh w-full flex-col p-0 sm:max-w-xl">
          <SheetHeader className="border-b border-border px-5 py-4 pt-[max(1rem,var(--safe-top))]">
            <SheetTitle>Ask Piggy</SheetTitle>
            <SheetDescription>{context ? `Working from ${contextLabel(context)}` : 'Working from your PIG workspace'}</SheetDescription>
          </SheetHeader>
          <PiggyChatPanel context={context} initialPrompt={initialPrompt} className="min-h-0 flex-1" />
        </SheetContent>
      </Sheet>
    );
  }
  return (
    <Drawer open={open} onOpenChange={onOpenChange}>
      <DrawerContent className="h-[92dvh]">
        <DrawerHeader className="border-b border-border px-4 pb-3 pt-2 text-left">
          <DrawerTitle>Ask Piggy</DrawerTitle>
          <DrawerDescription>{context ? `Working from ${contextLabel(context)}` : 'Working from your PIG workspace'}</DrawerDescription>
        </DrawerHeader>
        <PiggyChatPanel context={context} initialPrompt={initialPrompt} className="min-h-0 flex-1" />
      </DrawerContent>
    </Drawer>
  );
}

/**
 * The transcript and composer. Width-agnostic on purpose — it is used at a
 * full page, in a 36rem sheet, in a phone drawer and in the 22rem dock.
 *
 * `compact` is for the dock only. At 22rem the ordinary spacing does not fail,
 * it just crowds: the assistant avatar takes a tenth of the line, a user
 * bubble at 88% leaves no gutter to read the alignment from, and the
 * suggestion buttons wrap to three lines each.
 */
export function PiggyChatPanel({
  context,
  initialPrompt = '',
  className,
  compact = false,
}: {
  context?: PiggyChatContext;
  initialPrompt?: string;
  className?: string;
  compact?: boolean;
}) {
  const [messages, setMessages] = useState<TranscriptMessage[]>([]);
  const [draft, setDraft] = useState(initialPrompt);
  const [running, setRunning] = useState(false);
  const abortRef = useRef<AbortController | null>(null);
  const bottomRef = useRef<HTMLDivElement | null>(null);

  useEffect(() => bottomRef.current?.scrollIntoView({ behavior: running ? 'auto' : 'smooth' }), [messages, running]);
  useEffect(() => () => abortRef.current?.abort(), []);

  const send = async () => {
    const message = draft.trim();
    if (!message || running) return;
    const user: TranscriptMessage = { id: crypto.randomUUID(), role: 'user', content: message };
    const assistantId = crypto.randomUUID();
    const history: PiggyChatTurn[] = messages
      .filter((entry) => entry.content.trim())
      .slice(-20)
      .map((entry) => ({ role: entry.role, content: entry.content }));
    setMessages((current) => [
      ...current,
      user,
      { id: assistantId, role: 'assistant', content: '', reasoning: '', tools: [], pending: true },
    ]);
    setDraft('');
    setRunning(true);
    const abort = new AbortController();
    abortRef.current = abort;

    try {
      for await (const event of streamPiggyChat({ message, history, context }, abort.signal)) {
        setMessages((current) =>
          current.map((entry) =>
            entry.id === assistantId ? applyEvent(entry, event) : entry,
          ),
        );
      }
    } catch (error) {
      if (!abort.signal.aborted) {
        setMessages((current) =>
          current.map((entry) =>
            entry.id === assistantId
              ? { ...entry, pending: false, error: error instanceof Error ? error.message : 'Piggy chat failed.' }
              : entry,
          ),
        );
      }
    } finally {
      abortRef.current = null;
      setRunning(false);
    }
  };

  return (
    <div className={cn('flex min-h-0 flex-col', className)}>
      <div className={cn('min-h-0 flex-1 overflow-y-auto py-5', compact ? 'px-3' : 'px-4 sm:px-5')}>
        {messages.length === 0 ? (
          <div className="mx-auto flex h-full max-w-md flex-col items-center justify-center text-center">
            <div className={cn('flex items-center justify-center rounded-2xl bg-accent-subtle text-accent-fg', compact ? 'size-10' : 'size-12')}><Sparkles aria-hidden /></div>
            <h2 className="mt-4 font-semibold">What should we inspect?</h2>
            <p className={cn('mt-1 text-muted', compact ? 'text-xs leading-5' : 'text-sm')}>Piggy reads only through scoped PIG tools. It has no shell, filesystem or browser access, and this chat cannot write CRM records.</p>
            <div className="mt-4 grid w-full gap-2">
              {(context && context.type !== 'page'
                ? ['Summarise this record', 'What needs attention?', 'Which terms or dates matter most?']
                : ['What needs attention across the book?', 'Summarise active commitments', 'Which renewals are approaching?']
              ).map((suggestion) => (
                <button key={suggestion} type="button" className={cn('min-h-11 rounded-lg border border-border px-3 py-2 text-left hover:bg-surface-2', compact ? 'text-xs leading-5' : 'text-sm')} onClick={() => setDraft(suggestion)}>{suggestion}</button>
              ))}
            </div>
          </div>
        ) : (
          <div className="flex flex-col gap-4">
            {messages.map((message) => <ChatMessage key={message.id} message={message} compact={compact} />)}
            <div ref={bottomRef} />
          </div>
        )}
      </div>

      <form className={cn('border-t border-border bg-surface', compact ? 'p-3' : 'p-3 sm:p-4')} onSubmit={(event) => { event.preventDefault(); void send(); }}>
        {context ? <Badge className="mb-2 max-w-full truncate"><Database aria-hidden /> {contextLabel(context)}</Badge> : null}
        <div className="flex items-end gap-2">
          <Textarea
            value={draft}
            onChange={(event) => setDraft(event.target.value)}
            onKeyDown={(event) => {
              if (event.key === 'Enter' && !event.shiftKey) {
                event.preventDefault();
                void send();
              }
            }}
            className="min-h-11 max-h-36 resize-none"
            placeholder="Ask about capacity, margin, paper or next actions…"
            aria-label="Message Piggy"
          />
          {running ? (
            <Button type="button" size="icon" variant="outline" aria-label="Stop Piggy" onClick={() => abortRef.current?.abort()}><CircleStop aria-hidden /></Button>
          ) : (
            <Button type="submit" size="icon" variant="primary" disabled={!draft.trim()} aria-label="Send message"><Send aria-hidden /></Button>
          )}
        </div>
        <p className="mt-2 text-center text-[11px] leading-4 text-muted">{compact ? 'Read-only session' : 'Read-only session · Check source records before acting on material terms.'}</p>
      </form>
    </div>
  );
}

/** A page context has no id and its `type` is the literal 'page', which reads
 *  as nothing useful in a badge — show the route the dock is following. */
function contextLabel(context: PiggyChatContext): string {
  if (context.label) return context.label;
  if (context.type === 'page') return context.route === '/' ? 'Overview' : context.route.slice(1);
  return context.type.replaceAll('_', ' ');
}

function ChatMessage({ message, compact = false }: { message: TranscriptMessage; compact?: boolean }) {
  if (message.role === 'user') {
    return <div className={cn('ml-auto rounded-2xl rounded-br-md bg-primary py-3 text-sm text-accent-on', compact ? 'max-w-[94%] px-3' : 'max-w-[88%] px-4')}><p className="whitespace-pre-wrap">{message.content}</p></div>;
  }
  return (
    <div className={cn('flex', compact ? 'gap-2' : 'gap-3')}>
      <div className={cn('flex shrink-0 items-center justify-center rounded-xl bg-accent-subtle text-accent-fg', compact ? 'size-7 [&>svg]:size-4' : 'size-9')}><Bot aria-hidden /></div>
      <div className="min-w-0 flex-1">
        {message.reasoning ? (
          <details className="mb-2 rounded-lg bg-surface-2 text-xs text-muted">
            <summary className="flex min-h-11 cursor-pointer items-center gap-2 px-3 py-2 font-medium"><Brain aria-hidden /> Reasoning</summary>
            <p className="whitespace-pre-wrap px-3 pb-3">{message.reasoning}</p>
          </details>
        ) : null}
        {message.tools?.length ? <ToolTimeline tools={message.tools} /> : null}
        {message.content ? <p className="whitespace-pre-wrap text-sm leading-6">{message.content}</p> : null}
        {message.pending && !message.content ? <div className="flex min-h-11 items-center gap-2 text-sm text-muted"><Loader2 className="animate-spin" aria-hidden /> Piggy is checking PIG…</div> : null}
        {message.error ? <div className="mt-2 flex items-start gap-2 rounded-lg bg-danger/10 p-3 text-sm text-danger"><XCircle className="shrink-0" aria-hidden /> {message.error}</div> : null}
      </div>
    </div>
  );
}

function ToolTimeline({ tools }: { tools: ToolStep[] }) {
  return (
    <div className="mb-3 flex flex-col gap-1.5" aria-label="Piggy tool activity">
      {tools.map((tool) => (
        <details key={tool.id} className="rounded-lg border border-border text-xs">
          <summary className="flex min-h-11 cursor-pointer items-center gap-2 px-3 py-2">
            {tool.state === 'running' ? <Loader2 className="animate-spin text-muted" aria-hidden /> : tool.state === 'succeeded' ? <CheckCircle2 className="text-positive" aria-hidden /> : <XCircle className="text-danger" aria-hidden />}
            <span className="font-medium">{toolLabel(tool.name)}</span>
            <span className="ml-auto text-muted">{tool.state === 'running' ? 'Running' : tool.state === 'succeeded' ? 'Complete' : 'Failed'}</span>
          </summary>
          <pre className="overflow-x-auto border-t border-border p-3 text-[11px] text-muted">{tool.error ?? JSON.stringify(tool.arguments, null, 2)}</pre>
        </details>
      ))}
    </div>
  );
}

function applyEvent(message: TranscriptMessage, event: PiggyChatEvent): TranscriptMessage {
  if (event.type === 'content_delta') return { ...message, content: message.content + event.delta };
  if (event.type === 'reasoning_delta') return { ...message, reasoning: (message.reasoning ?? '') + event.delta };
  if (event.type === 'tool_call') return { ...message, tools: [...(message.tools ?? []), { id: event.id, name: event.name, arguments: event.arguments, state: 'running' }] };
  if (event.type === 'tool_result') return { ...message, tools: (message.tools ?? []).map((tool) => tool.id === event.id ? { ...tool, state: event.ok ? 'succeeded' : 'failed', error: event.error } : tool) };
  if (event.type === 'done') return { ...message, pending: false };
  if (event.type === 'error') return { ...message, pending: false, error: event.message };
  return message;
}

/**
 * The availability gate. Exported because anything that renders a chat surface
 * — the workspace page, the ask button, the dock — has to check it first:
 * `/api/piggy/chat` answers 503 when the runtime is off, and a panel that
 * renders without asking shows a composer that cannot send.
 */
export function usePiggyStatus() {
  return useQuery({ queryKey: ['piggy', 'status'], queryFn: () => get<PiggyStatus>('/api/piggy/status'), staleTime: 60_000, retry: false });
}

function toolLabel(name: string): string {
  return name.replace(/^pig_/, '').replaceAll('_', ' ').replace(/\b\w/g, (letter) => letter.toUpperCase());
}
