'use client';

import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { Plus, X, Check } from 'lucide-react';

import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { apiFetch } from '@/lib/api/client';
import { toastError } from '@/lib/api/toast-error';
import { cn } from '@/lib/utils';

interface Tag {
  id: string;
  name: string;
  color: string;
}

export interface InlineTagEditorProps {
  /** PUT endpoint for replacing the entity's tag list - body shape `{ tagIds: string[] }`. */
  endpoint: string;
  currentTags: Tag[];
  /** TanStack Query key to invalidate after a successful change. */
  invalidateKey: readonly unknown[];
  /** Hide the "+ Add tag" button (read-only mode). */
  readOnly?: boolean;
  /** Optional section heading rendered above the chips. When supplied and
   *  there are no tags configured port-wide AND none currently applied,
   *  the entire block (heading + editor) hides - keeps detail pages clean
   *  for ports that haven't set up tagging. */
  heading?: string;
  /** Optional wrapper class applied around heading + editor. */
  wrapperClassName?: string;
}

export function InlineTagEditor({
  endpoint,
  currentTags,
  invalidateKey,
  readOnly,
  heading,
  wrapperClassName,
}: InlineTagEditorProps) {
  const qc = useQueryClient();
  const [open, setOpen] = useState(false);
  const [search, setSearch] = useState('');

  // Always fetch so we can hide the editor entirely when no tags are
  // configured AND the entity has no tags already applied - keeps the
  // detail page clean for ports that haven't set up tagging yet. The
  // list is cheap, port-scoped, and cached for a minute.
  const { data: allTags } = useQuery<{ data: Tag[] }>({
    queryKey: ['tags'],
    queryFn: () => apiFetch('/api/v1/tags'),
    staleTime: 60_000,
  });

  const setTags = useMutation({
    mutationFn: (tagIds: string[]) => apiFetch(endpoint, { method: 'PUT', body: { tagIds } }),
    onSuccess: () => qc.invalidateQueries({ queryKey: invalidateKey }),
    onError: (err) => toastError(err),
  });

  // F16: inline "Create new tag: X" - was missing entirely. Reps had to
  // context-switch to Admin → Tags to create the tag, then come back.
  const createTag = useMutation({
    mutationFn: (name: string) =>
      apiFetch<{ data: Tag }>('/api/v1/tags', { method: 'POST', body: { name } }),
    onError: (err) => toastError(err),
  });

  function toggleTag(tagId: string) {
    const has = currentTags.some((t) => t.id === tagId);
    const nextIds = has
      ? currentTags.filter((t) => t.id !== tagId).map((t) => t.id)
      : [...currentTags.map((t) => t.id), tagId];
    setTags.mutate(nextIds);
  }

  function removeTag(tagId: string) {
    setTags.mutate(currentTags.filter((t) => t.id !== tagId).map((t) => t.id));
  }

  async function handleCreateAndAttach(name: string) {
    const trimmed = name.trim();
    if (!trimmed) return;
    const res = await createTag.mutateAsync(trimmed);
    const newTag = res.data;
    // Optimistically extend `allTags` so the new chip appears immediately.
    qc.invalidateQueries({ queryKey: ['tags'] });
    setTags.mutate([...currentTags.map((t) => t.id), newTag.id]);
    setSearch('');
  }

  const lowerSearch = search.trim().toLowerCase();
  const filtered = lowerSearch
    ? (allTags?.data ?? []).filter((t) => t.name.toLowerCase().includes(lowerSearch))
    : (allTags?.data ?? []);
  // Suggest "Create new tag: X" when the user typed something but no
  // exact-case-insensitive match exists.
  const exactMatch = (allTags?.data ?? []).some((t) => t.name.toLowerCase() === lowerSearch);
  const canCreate = !!lowerSearch && !exactMatch;

  // Hide the whole editor when the port has no tags configured AND this
  // entity has none applied. Once an admin adds the first tag in
  // Admin → Tags, the editor reappears on next mount/refetch.
  const portHasNoTags = allTags && allTags.data.length === 0;
  if (portHasNoTags && currentTags.length === 0) {
    return null;
  }

  const editor = (
    <div className="flex flex-wrap items-center gap-1.5">
      {currentTags.map((t) => (
        <span
          key={t.id}
          className="group inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium"
          style={{ backgroundColor: `${t.color}20`, color: t.color }}
        >
          {t.name}
          {!readOnly && (
            <button
              type="button"
              onClick={() => removeTag(t.id)}
              className="opacity-0 group-hover:opacity-100 transition-opacity hover:opacity-100"
              aria-label={`Remove tag ${t.name}`}
            >
              <X className="h-3 w-3" />
            </button>
          )}
        </span>
      ))}

      {!readOnly && (
        <Popover open={open} onOpenChange={setOpen}>
          <PopoverTrigger asChild>
            <Button
              type="button"
              size="sm"
              variant="ghost"
              className="h-6 px-2 text-xs text-muted-foreground hover:text-foreground"
            >
              <Plus className="h-3 w-3 mr-1" aria-hidden />
              {currentTags.length === 0 ? 'Add tag' : 'Add'}
            </Button>
          </PopoverTrigger>
          <PopoverContent className="w-64 p-0" align="start">
            <div className="border-b p-2">
              <Input
                type="text"
                value={search}
                onChange={(e) => setSearch(e.target.value)}
                onKeyDown={(e) => {
                  // Enter on a non-empty no-match → create and attach.
                  if (e.key === 'Enter' && canCreate && !createTag.isPending) {
                    e.preventDefault();
                    void handleCreateAndAttach(search);
                  }
                }}
                placeholder="Search or create…"
                className="h-7 text-xs"
                autoFocus
              />
            </div>
            <div className="max-h-64 overflow-y-auto py-1">
              {!allTags && <div className="px-3 py-2 text-xs text-muted-foreground">Loading…</div>}
              {filtered.map((t) => {
                const checked = currentTags.some((c) => c.id === t.id);
                return (
                  <button
                    key={t.id}
                    type="button"
                    onClick={() => toggleTag(t.id)}
                    className={cn(
                      'flex items-center gap-2 w-full px-3 py-1.5 text-sm text-left hover:bg-muted/60',
                    )}
                  >
                    <span
                      className="inline-block w-2.5 h-2.5 rounded-full shrink-0"
                      style={{ backgroundColor: t.color }}
                    />
                    <span className="flex-1 truncate">{t.name}</span>
                    {checked && <Check className="h-3.5 w-3.5 text-primary" aria-hidden />}
                  </button>
                );
              })}
              {canCreate && (
                <button
                  type="button"
                  disabled={createTag.isPending}
                  onClick={() => void handleCreateAndAttach(search)}
                  className="flex w-full items-center gap-2 border-t px-3 py-1.5 text-sm text-left hover:bg-muted/60"
                >
                  <Plus className="h-3.5 w-3.5 shrink-0" aria-hidden />
                  <span className="flex-1 truncate">
                    Create new tag: <strong>{search.trim()}</strong>
                  </span>
                </button>
              )}
              {filtered.length === 0 && !canCreate && !!allTags && (
                <div className="px-3 py-2 text-xs text-muted-foreground">
                  {allTags.data.length === 0
                    ? 'No tags defined yet. Type a name to create one.'
                    : 'No matching tags.'}
                </div>
              )}
            </div>
          </PopoverContent>
        </Popover>
      )}
    </div>
  );

  if (!heading) return editor;

  return (
    <div className={cn('space-y-1', wrapperClassName)}>
      <h3 className="text-sm font-medium mb-2">{heading}</h3>
      {editor}
    </div>
  );
}
