import { useState } from 'react';
import { Check, ChevronRight, Loader2, ShieldAlert, X } from 'lucide-react';
import type { ToolCall, ToolResult } from '@/api/types';
import { linkifyPaths } from '@/lib/linkify-paths';
import { isMcpTool } from '@/lib/tool-utils';
import { DiffSnippet } from './DiffSnippet';
import { McpPermissionDialog } from './McpPermissionDialog';
import { McpResponseDisplay } from './McpResponseDisplay';

// v1.8.2: cap on the inline arg-summary length. Expanded view shows full
// args + full result, so this is purely a single-line render budget.
const ARG_SUMMARY_MAX = 60;

export interface ToolRun {
  call: ToolCall;
  // null while the call is in flight or the matching tool result hasn't
  // arrived yet on the WS stream.
  result: ToolResult | null;
}

function truncate(s: string, n: number): string {
  return s.length > n ? s.slice(0, n - 1) + '…' : s;
}

// Per-tool argument summary mapping from the v1.8.2 spec. Goal is a single
// scannable line that surfaces the *what* (path / pattern) without
// overwhelming the chat with full JSON.
function formatToolArgs(name: string, args: Record<string, unknown>): string {
  if (name === 'view_file') {
    const path = String(args.path ?? '');
    const start = args.start_line;
    const end = args.end_line;
    if (typeof start === 'number' && typeof end === 'number') {
      return truncate(`${path}:${start}-${end}`, ARG_SUMMARY_MAX);
    }
    if (typeof start === 'number') {
      return truncate(`${path}:${start}`, ARG_SUMMARY_MAX);
    }
    return truncate(path, ARG_SUMMARY_MAX);
  }
  if (name === 'list_dir') {
    return truncate(String(args.path ?? '.'), ARG_SUMMARY_MAX);
  }
  if (name === 'grep') {
    const pattern = String(args.pattern ?? '');
    const path = args.path ? ` ${String(args.path)}` : '';
    return truncate(`"${pattern}"${path}`, ARG_SUMMARY_MAX);
  }
  if (name === 'find_files') {
    return truncate(String(args.pattern ?? ''), ARG_SUMMARY_MAX);
  }
  if (name === 'git_status') {
    return '';
  }
  if (name === 'skill_use') {
    // Schema (apps/server/src/services/tools.ts SkillUseInput) uses `name`;
    // fall back to `skill_name` defensively in case a model emits that key.
    return truncate(
      String(args.name ?? (args as { skill_name?: unknown }).skill_name ?? '<unknown>'),
      ARG_SUMMARY_MAX,
    );
  }
  // Unknown tool — surface first arg value or the literal {} so the user can
  // see something happened. Forward-compatible with future tools.
  const keys = Object.keys(args);
  if (keys.length === 0) return '{}';
  const first = keys[0]!;
  return truncate(`${first}: ${String(args[first])}`, ARG_SUMMARY_MAX);
}

export function runStatus(run: ToolRun): 'pending' | 'success' | 'error' {
  if (run.result === null) return 'pending';
  if (run.result.error) return 'error';
  return 'success';
}

// Path-shaped substrings in tool output get a click handler via the shared
// linkifyPaths util (lib/linkify-paths.tsx).

interface Props {
  run: ToolRun;
  // When rendered inside a ToolCallGroup the line is already nested under a
  // shared header, so the leading arrow is dropped to avoid double indent.
  insideGroup?: boolean;
  chatId?: string;
}

export function ToolCallLine({ run, insideGroup, chatId }: Props) {
  const [open, setOpen] = useState(false);
  const [approveOpen, setApproveOpen] = useState(false);
  const status = runStatus(run);
  const args = run.call.args ?? {};
  const summary = formatToolArgs(run.call.name, args);

  const needsApproval = run.result?.error?.startsWith('requires approval:') === true;

  return (
    <div className="text-xs">
      <button
        type="button"
        tabIndex={0}
        onClick={() => setOpen((v) => !v)}
        onKeyDown={(e) => {
          if (e.key === 'Enter' || e.key === ' ') {
            e.preventDefault();
            setOpen((v) => !v);
          } else if (e.key === 'Escape') {
            setOpen(false);
          }
        }}
        className="flex items-center gap-1.5 w-full text-left hover:bg-muted/40 rounded px-1 py-0.5 -mx-1 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
      >
        {/* BooCode 2.0: glowing activity indicator (was ↳ / >_) */}
        {!insideGroup && (
          <span
            className="size-1.5 rounded-full bg-primary shrink-0"
            style={{ boxShadow: '0 0 6px var(--primary)' }}
            aria-hidden
          />
        )}
        <ChevronRight
          className={`size-3 text-muted-foreground/60 shrink-0 motion-reduce:transition-none transition-transform ${open ? 'rotate-90' : ''}`}
        />
        <span className="font-mono text-foreground/90 shrink-0">{run.call.name}</span>
        {summary && (
          <span className="font-mono text-muted-foreground truncate min-w-0 flex-1">
            {summary}
          </span>
        )}
        {!summary && <span className="flex-1" />}
        <span className="shrink-0 ml-1">
          {status === 'pending' && (
            <Loader2 className="size-3 text-muted-foreground animate-spin" aria-label="running" />
          )}
          {status === 'success' && (
            <Check className="size-3 text-emerald-500" aria-label="success" />
          )}
          {status === 'error' && (
            <X className="size-3 text-destructive" aria-label="error" />
          )}
        </span>
      </button>
      {open && (
        <div className="ml-5 mt-1 mb-1 space-y-1">
          <pre className="text-[10px] text-muted-foreground font-mono whitespace-pre-wrap break-all bg-muted/30 rounded px-2 py-1">
            {JSON.stringify(args, null, 2)}
          </pre>
          {run.result && isMcpTool(run.call.name) ? (
            <McpResponseDisplay toolCall={run.call} toolResult={run.result} />
          ) : run.result ? (
            <pre className="text-[11px] font-mono whitespace-pre-wrap bg-muted/30 rounded px-2 py-1 max-h-72 overflow-y-auto">
              {run.result.error ? (
                needsApproval ? (
                  <span className="flex flex-col gap-2">
                    <span className="text-amber-600 dark:text-amber-400">
                      This tool requires your approval
                    </span>
                    {chatId && (
                      <span>
                        <button
                          type="button"
                          onClick={() => setApproveOpen(true)}
                          className="inline-flex items-center gap-1 rounded bg-amber-500/10 px-2 py-1 text-xs font-medium text-amber-600 hover:bg-amber-500/20 dark:text-amber-400"
                        >
                          <ShieldAlert className="size-3" />
                          Approve
                        </button>
                      </span>
                    )}
                  </span>
                ) : (
                  <span className="text-destructive">{run.result.error}</span>
                )
              ) : (
                linkifyPaths(
                  typeof run.result.output === 'string'
                    ? run.result.output
                    : JSON.stringify(run.result.output, null, 2)
                )
              )}
              {run.result.truncated && (
                <div className="text-muted-foreground/60 mt-1">— output truncated —</div>
              )}
            </pre>
          ) : null}
          {needsApproval && chatId && (
            <McpPermissionDialog
              toolCallId={run.call.id}
              toolName={run.call.name}
              toolArgs={run.call.args ?? {}}
              chatId={chatId}
              open={approveOpen}
              onClose={() => setApproveOpen(false)}
            />
          )}
          {run.result?.diff && <DiffSnippet diff={run.result.diff} />}
        </div>
      )}
    </div>
  );
}
