import { useMemo, useState, useRef, useEffect } from 'react';
import { useToolList } from '../../hooks/useTools';
import type { ToolCatalogEntry } from '../../api';
import { HelpText } from './HelpText';

export interface ToolTagInputProps {
  value: string[];
  onChange: (tools: string[]) => void;
  disabled?: boolean;
}

/**
 * Piece `allowed_tools` editor backed by the runtime tool catalog
 * (`GET /api/tools`, see src/bridge/tools-api.ts).
 *
 * Behaviour:
 *  - Groups tools by `source` then `category` (builtin core/web/.../mcp:<server>).
 *  - Renders a `scope` badge (global/piece/user) on every entry.
 *  - Unavailable entries (e.g. MCP server offline) are shown disabled with a
 *    warning badge — they are NOT auto-removed from the piece, the user has to
 *    delete them explicitly. This matches the design contract that a transient
 *    MCP outage must never silently drop tools from a piece.
 *  - Tools already on the piece but missing from the catalog appear under an
 *    "unknown" group with the same disabled+warning treatment.
 *  - Selecting an unavailable catalog tool is still allowed (the user might be
 *    preparing for a server that's about to come back online).
 */
export function ToolTagInput({ value, onChange, disabled = false }: ToolTagInputProps) {
  const { data: catalog } = useToolList();
  const [input, setInput] = useState('');
  const [showDropdown, setShowDropdown] = useState(false);
  const [highlightIndex, setHighlightIndex] = useState(0);
  const containerRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLInputElement>(null);

  const catalogByName = useMemo(() => {
    const m = new Map<string, ToolCatalogEntry>();
    for (const t of catalog ?? []) m.set(t.name, t);
    return m;
  }, [catalog]);

  // Suggestions: catalog entries not already in `value`, filtered by input
  // substring. Unavailable entries stay in the suggestion list (user might
  // want to "pre-attach" a tool for a server they expect to come online).
  const suggestions = useMemo(() => {
    const q = input.toLowerCase();
    return (catalog ?? [])
      .filter((t) => !value.includes(t.name))
      .filter((t) => t.name.toLowerCase().includes(q));
  }, [catalog, value, input]);

  // Groups for the suggestion dropdown.
  // Group key format:
  //  builtin → 'builtin:<category>'
  //  meta    → 'meta'
  //  mcp     → 'mcp:<serverId|category>'
  const groupedSuggestions = useMemo(() => {
    const groups = new Map<string, { label: string; entries: ToolCatalogEntry[] }>();
    for (const t of suggestions) {
      const { key, label } = groupKeyForCatalogEntry(t);
      const g = groups.get(key);
      if (g) g.entries.push(t);
      else groups.set(key, { label, entries: [t] });
    }
    return Array.from(groups.entries()).map(([key, v]) => ({ key, ...v }));
  }, [suggestions]);

  // Flat list of suggestions in displayed order — used to map keyboard
  // highlight index back to the actual entry.
  const flatSuggestions = useMemo(
    () => groupedSuggestions.flatMap((g) => g.entries),
    [groupedSuggestions],
  );

  useEffect(() => {
    setHighlightIndex(0);
  }, [input]);

  useEffect(() => {
    const handleClickOutside = (e: MouseEvent) => {
      if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
        setShowDropdown(false);
      }
    };
    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, []);

  const addTool = (tool: string) => {
    if (!value.includes(tool)) onChange([...value, tool]);
    setInput('');
    setShowDropdown(false);
    inputRef.current?.focus();
  };

  const removeTool = (tool: string) => {
    onChange(value.filter((t) => t !== tool));
  };

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter' && flatSuggestions.length > 0 && showDropdown) {
      e.preventDefault();
      const pick = flatSuggestions[highlightIndex] ?? flatSuggestions[0];
      if (pick) addTool(pick.name);
    } else if (e.key === 'ArrowDown') {
      e.preventDefault();
      setHighlightIndex((i) => Math.min(i + 1, flatSuggestions.length - 1));
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      setHighlightIndex((i) => Math.max(i - 1, 0));
    } else if (e.key === 'Escape') {
      setShowDropdown(false);
    } else if (e.key === 'Backspace' && input === '' && value.length > 0) {
      removeTool(value[value.length - 1]);
    }
  };

  return (
    <div>
      <label className="block text-xs font-medium text-slate-600 mb-1">allowed_tools</label>
      <div ref={containerRef} className="relative">
        <div className="flex flex-wrap gap-1 p-2 border border-slate-300 rounded-lg min-h-[38px] focus-within:ring-2 focus-within:ring-accent-ring focus-within:border-accent">
          {value.map((tool) => {
            const entry = catalogByName.get(tool);
            return (
              <SelectedToolChip
                key={tool}
                name={tool}
                entry={entry}
                onRemove={() => removeTool(tool)}
                readOnly={disabled}
              />
            );
          })}
          {!disabled && (
            <input
              ref={inputRef}
              type="text"
              value={input}
              onChange={(e) => {
                setInput(e.target.value);
                setShowDropdown(true);
              }}
              onFocus={() => setShowDropdown(true)}
              onKeyDown={handleKeyDown}
              placeholder={value.length === 0 ? 'ツール名を入力...' : ''}
              className="flex-1 min-w-[120px] text-sm outline-none bg-transparent"
            />
          )}
        </div>
        {!disabled && showDropdown && groupedSuggestions.length > 0 && (
          <div className="absolute z-10 mt-1 w-full max-h-72 overflow-y-auto bg-white border border-slate-200 rounded-lg shadow-lg">
            {groupedSuggestions.map((g) => (
              <div key={g.key}>
                <div className="sticky top-0 px-3 py-1 text-[10px] font-semibold uppercase tracking-wide text-slate-500 bg-slate-50 border-b border-slate-100">
                  {g.label}
                </div>
                {g.entries.map((t) => {
                  const flatIdx = flatSuggestions.indexOf(t);
                  const highlighted = flatIdx === highlightIndex;
                  return (
                    <button
                      key={t.name}
                      type="button"
                      onClick={() => addTool(t.name)}
                      title={t.available ? undefined : t.reason ?? 'unavailable'}
                      className={`w-full text-left px-3 py-1.5 text-sm flex items-center gap-2 ${
                        highlighted ? 'bg-accent-soft text-accent' : 'text-slate-700 hover:bg-slate-50'
                      } ${t.available ? '' : 'opacity-70'}`}
                    >
                      <span className="flex-1 truncate">{t.name}</span>
                      <ScopeBadge scope={t.scope} />
                      {!t.available && (
                        <Badge color="amber">{t.reason ?? 'unavailable'}</Badge>
                      )}
                    </button>
                  );
                })}
              </div>
            ))}
          </div>
        )}
      </div>
      <HelpText>
        ここに列挙したツールのみ LLM に提示されます。
        オフラインの MCP ツールや未知のツールも自動削除されず、明示的に削除するまで残ります。
      </HelpText>
    </div>
  );
}

/**
 * Build a stable group key + human label for a catalog entry. MCP tools are
 * grouped per server id so the editor can show e.g. "MCP · github" sections.
 */
function groupKeyForCatalogEntry(t: ToolCatalogEntry): { key: string; label: string } {
  if (t.source === 'meta') return { key: 'meta', label: 'meta (always available)' };
  if (t.source === 'mcp') {
    const id = t.serverId ?? t.category.replace(/^mcp:/, '');
    return { key: `mcp:${id}`, label: `mcp · ${id}` };
  }
  return { key: `builtin:${t.category}`, label: `builtin · ${t.category}` };
}

function SelectedToolChip({
  name,
  entry,
  onRemove,
  readOnly = false,
}: {
  name: string;
  entry: ToolCatalogEntry | undefined;
  onRemove: () => void;
  readOnly?: boolean;
}) {
  const isUnknown = !entry;
  const isUnavailable = entry ? !entry.available : false;
  // Visual stack:
  //  - normal tool          → slate chip
  //  - unavailable in catalog → amber chip + reason badge
  //  - unknown (not in catalog at all) → amber chip + "unknown" badge
  const tone =
    isUnknown || isUnavailable
      ? 'bg-amber-50 text-amber-800 border border-amber-200'
      : 'bg-slate-100 text-slate-700';
  const tip = isUnknown
    ? 'このツールは現在のカタログに存在しません。明示削除するまで保持されます。'
    : isUnavailable
      ? (entry?.reason ?? 'unavailable')
      : undefined;
  return (
    <span
      className={`inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded ${tone}`}
      title={tip}
    >
      <span>{name}</span>
      {entry && <ScopeBadge scope={entry.scope} dim />}
      {isUnknown && <Badge color="amber">unknown</Badge>}
      {!isUnknown && isUnavailable && <Badge color="amber">{entry?.reason ?? 'offline'}</Badge>}
      {!readOnly && (
        <button
          type="button"
          onClick={onRemove}
          className="text-current opacity-60 hover:opacity-100"
          aria-label={`remove ${name}`}
        >
          &times;
        </button>
      )}
    </span>
  );
}

function ScopeBadge({ scope, dim }: { scope: 'global' | 'piece' | 'user'; dim?: boolean }) {
  const color: 'slate' | 'blue' | 'emerald' =
    scope === 'global' ? 'slate' : scope === 'user' ? 'emerald' : 'blue';
  return (
    <Badge color={color} dim={dim}>
      {scope}
    </Badge>
  );
}

function Badge({
  color,
  dim,
  children,
}: {
  color: 'slate' | 'blue' | 'emerald' | 'amber' | 'red';
  dim?: boolean;
  children: React.ReactNode;
}) {
  const cls: Record<typeof color, string> = {
    slate: 'bg-slate-100 text-slate-600',
    blue: 'bg-blue-50 text-blue-700',
    emerald: 'bg-emerald-50 text-emerald-700',
    amber: 'bg-amber-50 text-amber-700',
    red: 'bg-red-50 text-red-700',
  };
  return (
    <span
      className={`inline-block px-1.5 py-0.5 rounded text-[10px] font-medium leading-none ${cls[color]} ${dim ? 'opacity-70' : ''}`}
    >
      {children}
    </span>
  );
}
