'use client';

import { useState, useEffect, useRef } from 'react';
import { tagApi } from '../../lib/api';
import { Tag } from '../../types/api';

interface TagInputProps {
  tags: string[];
  onChange: (tags: string[]) => void;
  placeholder?: string;
}

// Fuzzy matching utilities
const levenshteinDistance = (str1: string, str2: string): number => {
  const matrix = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null));
  
  for (let i = 0; i <= str1.length; i++) matrix[0][i] = i;
  for (let j = 0; j <= str2.length; j++) matrix[j][0] = j;
  
  for (let j = 1; j <= str2.length; j++) {
    for (let i = 1; i <= str1.length; i++) {
      const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
      matrix[j][i] = Math.min(
        matrix[j][i - 1] + 1,
        matrix[j - 1][i] + 1,
        matrix[j - 1][i - 1] + indicator
      );
    }
  }
  
  return matrix[str2.length][str1.length];
};

const calculateSimilarity = (query: string, target: string): number => {
  const q = query.toLowerCase().trim();
  const t = target.toLowerCase().trim();
  
  // Don't match very short queries to avoid noise
  if (q.length < 2) return 0;
  
  // Exact match
  if (q === t) return 1.0;
  
  // Starts with match (high priority)
  if (t.startsWith(q)) return 0.95;
  
  // Contains match (word boundary preferred)
  if (t.includes(q)) {
    // Higher score if it's a word boundary match
    const words = t.split(/\s+|[-_]/);
    if (words.some(word => word.startsWith(q))) return 0.85;
    return 0.75;
  }
  
  // Common typo patterns
  // 1. Adjacent character swaps (e.g., "sicfi" -> "scifi")
  if (Math.abs(q.length - t.length) <= 1) {
    for (let i = 0; i < Math.min(q.length - 1, t.length - 1); i++) {
      const swapped = q.substring(0, i) + q[i + 1] + q[i] + q.substring(i + 2);
      if (swapped === t) return 0.9;
    }
  }
  
  // 2. Missing character (e.g., "fantsy" -> "fantasy")
  if (t.length === q.length + 1) {
    for (let i = 0; i <= t.length; i++) {
      const withMissing = t.substring(0, i) + t.substring(i + 1);
      if (withMissing === q) return 0.88;
    }
  }
  
  // 3. Extra character (e.g., "fantasy" -> "fantassy")
  if (q.length === t.length + 1) {
    for (let i = 0; i <= q.length; i++) {
      const withExtra = q.substring(0, i) + q.substring(i + 1);
      if (withExtra === t) return 0.88;
    }
  }
  
  // 4. Double letter corrections (e.g., "scii" -> "sci", "romace" -> "romance")
  const qNormalized = q.replace(/(.)\1+/g, '$1');
  const tNormalized = t.replace(/(.)\1+/g, '$1');
  if (qNormalized === tNormalized) return 0.87;
  
  // 5. Common letter substitutions (keyboard layout)
  const keyboardSubs: { [key: string]: string[] } = {
    'a': ['s', 'q'], 's': ['a', 'd', 'w'], 'd': ['s', 'f', 'e'], 'f': ['d', 'g', 'r'],
    'q': ['w', 'a'], 'w': ['q', 'e', 's'], 'e': ['w', 'r', 'd'], 'r': ['e', 't', 'f'],
    'z': ['x', 'a'], 'x': ['z', 'c', 's'], 'c': ['x', 'v', 'd'], 'v': ['c', 'b', 'f'],
    'i': ['u', 'o'], 'o': ['i', 'p'], 'u': ['y', 'i'], 'y': ['t', 'u'],
    'n': ['b', 'm'], 'm': ['n', 'j'], 'j': ['h', 'k'], 'k': ['j', 'l']
  };
  
  let substitutionScore = 0;
  if (q.length === t.length) {
    let matches = 0;
    for (let i = 0; i < q.length; i++) {
      if (q[i] === t[i]) {
        matches++;
      } else if (keyboardSubs[q[i]]?.includes(t[i]) || keyboardSubs[t[i]]?.includes(q[i])) {
        matches += 0.8; // Partial credit for keyboard mistakes
      }
    }
    substitutionScore = matches / q.length;
    if (substitutionScore > 0.8) return substitutionScore * 0.85;
  }
  
  // Levenshtein distance-based similarity (fallback)
  const distance = levenshteinDistance(q, t);
  const maxLength = Math.max(q.length, t.length);
  const similarity = 1 - (distance / maxLength);
  
  // Boost score for shorter strings with fewer differences
  if (maxLength <= 6 && distance <= 2) {
    return Math.min(0.8, similarity + 0.1);
  }
  
  // Only consider it a match if similarity is high enough
  return similarity > 0.65 ? similarity * 0.7 : 0;
};

export default function TagInput({ tags, onChange, placeholder = 'Add tags...' }: TagInputProps) {
  const [inputValue, setInputValue] = useState('');
  const [suggestions, setSuggestions] = useState<{name: string, similarity: number}[]>([]);
  const [showSuggestions, setShowSuggestions] = useState(false);
  const [activeSuggestionIndex, setActiveSuggestionIndex] = useState(-1);
  const [allTags, setAllTags] = useState<Tag[]>([]);
  const inputRef = useRef<HTMLInputElement>(null);
  const suggestionsRef = useRef<HTMLDivElement>(null);

  // Load all tags once for fuzzy matching
  useEffect(() => {
    const loadAllTags = async () => {
      try {
        const response = await tagApi.getTags({ page: 0, size: 1000 });
        setAllTags(response.content || []);
      } catch (error) {
        console.error('Failed to load all tags:', error);
      }
    };
    
    loadAllTags();
  }, []);

  useEffect(() => {
    const fetchSuggestions = async () => {
      if (inputValue.length > 0) {
        try {
          // First try backend autocomplete for exact/prefix matches
          const backendSuggestions = await tagApi.getTagAutocomplete(inputValue);
          
          // Apply fuzzy matching to all tags for better results
          const fuzzyMatches = allTags
            .map(tag => ({
              name: tag.name,
              similarity: calculateSimilarity(inputValue, tag.name)
            }))
            .filter(match => match.similarity > 0 && !tags.includes(match.name))
            .sort((a, b) => b.similarity - a.similarity);
          
          // Combine backend results with fuzzy matches, prioritizing backend results
          const combinedResults = new Map<string, {name: string, similarity: number}>();
          
          // Add backend results with high priority
          backendSuggestions.forEach(name => {
            if (!tags.includes(name)) {
              combinedResults.set(name, { name, similarity: 0.99 });
            }
          });
          
          // Add fuzzy matches that aren't already from backend
          fuzzyMatches.forEach(match => {
            if (!combinedResults.has(match.name)) {
              combinedResults.set(match.name, match);
            }
          });
          
          // Convert to array and limit results
          const finalSuggestions = Array.from(combinedResults.values())
            .sort((a, b) => b.similarity - a.similarity)
            .slice(0, 10);
          
          setSuggestions(finalSuggestions);
          setShowSuggestions(finalSuggestions.length > 0);
        } catch (error) {
          console.error('Failed to fetch tag suggestions:', error);
          setSuggestions([]);
          setShowSuggestions(false);
        }
      } else {
        setSuggestions([]);
        setShowSuggestions(false);
      }
    };

    const debounce = setTimeout(fetchSuggestions, 300);
    return () => clearTimeout(debounce);
  }, [inputValue, tags, allTags]);

  const addTag = async (tag: string) => {
    const trimmedTag = tag.trim().toLowerCase();
    if (!trimmedTag) return;
    
    try {
      // Resolve tag alias to canonical name
      const resolvedTag = await tagApi.resolveTag(trimmedTag);
      const finalTag = resolvedTag ? resolvedTag.name.toLowerCase() : trimmedTag;
      
      // Only add if not already present
      if (!tags.includes(finalTag)) {
        onChange([...tags, finalTag]);
      }
    } catch (error) {
      console.warn('Failed to resolve tag alias:', error);
      // Fall back to original tag if resolution fails
      if (!tags.includes(trimmedTag)) {
        onChange([...tags, trimmedTag]);
      }
    }
    
    setInputValue('');
    setShowSuggestions(false);
    setActiveSuggestionIndex(-1);
  };

  const removeTag = (tagToRemove: string) => {
    onChange(tags.filter(tag => tag !== tagToRemove));
  };

  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    switch (e.key) {
      case 'Enter':
      case ',':
        e.preventDefault();
        if (activeSuggestionIndex >= 0 && suggestions[activeSuggestionIndex]) {
          addTag(suggestions[activeSuggestionIndex].name);
        } else if (inputValue.trim()) {
          addTag(inputValue);
        }
        break;
        
      case 'Backspace':
        if (!inputValue && tags.length > 0) {
          removeTag(tags[tags.length - 1]);
        }
        break;
        
      case 'ArrowDown':
        e.preventDefault();
        setActiveSuggestionIndex(prev => 
          prev < suggestions.length - 1 ? prev + 1 : prev
        );
        break;
        
      case 'ArrowUp':
        e.preventDefault();
        setActiveSuggestionIndex(prev => prev > 0 ? prev - 1 : -1);
        break;
        
      case 'Escape':
        setShowSuggestions(false);
        setActiveSuggestionIndex(-1);
        break;
    }
  };

  const handleSuggestionClick = (suggestionName: string) => {
    addTag(suggestionName);
    inputRef.current?.focus();
  };

  return (
    <div className="relative">
      <div className="min-h-[2.5rem] w-full px-3 py-2 border rounded-lg theme-card theme-text theme-border focus-within:outline-none focus-within:ring-2 focus-within:ring-theme-accent focus-within:border-transparent">
        <div className="flex flex-wrap gap-2">
          {/* Existing Tags */}
          {tags.map((tag) => (
            <span
              key={tag}
              className="inline-flex items-center px-2 py-1 text-sm bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded"
            >
              {tag}
              <button
                type="button"
                onClick={() => removeTag(tag)}
                className="ml-1 text-blue-600 dark:text-blue-300 hover:text-blue-800 dark:hover:text-blue-100"
              >
                ×
              </button>
            </span>
          ))}
          
          {/* Input */}
          <input
            ref={inputRef}
            type="text"
            value={inputValue}
            onChange={(e) => setInputValue(e.target.value)}
            onKeyDown={handleKeyDown}
            onFocus={() => inputValue && setShowSuggestions(suggestions.length > 0)}
            onBlur={() => setTimeout(() => setShowSuggestions(false), 200)}
            placeholder={tags.length === 0 ? placeholder : ''}
            className="flex-1 min-w-[120px] bg-transparent outline-none"
          />
        </div>
      </div>

      {/* Suggestions Dropdown */}
      {showSuggestions && suggestions.length > 0 && (
        <div
          ref={suggestionsRef}
          className="absolute z-10 w-full mt-1 bg-white dark:bg-gray-800 border theme-border rounded-lg shadow-lg max-h-48 overflow-y-auto"
        >
          {suggestions.map((suggestion, index) => (
            <button
              key={suggestion.name}
              type="button"
              onClick={() => handleSuggestionClick(suggestion.name)}
              className={`w-full text-left px-3 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-700 ${
                index === activeSuggestionIndex
                  ? 'bg-blue-100 dark:bg-blue-900 text-blue-900 dark:text-blue-100'
                  : 'theme-text'
              } ${index === 0 ? 'rounded-t-lg' : ''} ${
                index === suggestions.length - 1 ? 'rounded-b-lg' : ''
              }`}
            >
              <div className="flex justify-between items-center">
                <span>{suggestion.name}</span>
                {suggestion.similarity < 0.95 && (
                  <span className="text-xs opacity-50 ml-2">
                    {(suggestion.similarity * 100).toFixed(0)}%
                  </span>
                )}
              </div>
            </button>
          ))}
        </div>
      )}

      <p className="mt-1 text-xs text-gray-500">
        Type and press Enter or comma to add tags. Supports fuzzy matching for typos.
      </p>
    </div>
  );
}