'use client';

import { signOutToPath } from '@/lib/auth-client';
import { useRouter, useSearchParams } from 'next/navigation';
import { AnimatePresence, motion } from 'framer-motion';
import { CheckSquare, Plus, Square } from 'lucide-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import Modal from '@/app/components/Modal';
import QRCodeModal from '@/app/components/QRCodeModal';
import CreateLinkDialog from '@/app/components/profile/CreateLinkDialog';
import EditLinkDialog from '@/app/components/profile/EditLinkDialog';
import ApiErrorAlert from '@/app/components/ApiErrorAlert';
import { useToast } from '@/app/components/ToastProvider';
import { getResponseErrorTextWithRequestId } from '@/lib/api-client';
import { DEFAULT_PLAN_FEATURES, PAGE_SIZE, SORT_LABELS } from './_links/constants';
import { FilterToolbar } from './_links/FilterToolbar';
import { BulkActionsBar } from './_links/BulkActionsBar';
import { LinkRow } from './_links/LinkRow';
import { linksListCache, requestLinksListPage } from './_links/requestCache';
import type {
  LinkItem,
  LinksPeriod,
  Folder as FolderType,
  ProfileLinksTabProps,
  Tag as TagType,
} from './_links/types';
import {
  buildLinksListCacheKey,
  isAbortError,
  isFreshLinksCache,
  isUnauthorizedResponse,
  writeTagIdsToParams,
} from './_links/utils';

export type { ProfileLinksTabProps } from './_links/types';

type MenuKey = 'folder' | 'tag' | 'period' | 'sort' | null;

export default function ProfileLinksTab({
  cacheScope,
  folderId,
  isFavorite,
  tagIds,
  searchQuery,
  sortBy,
  folders,
  tags,
  totalLinks,
  favoriteCount,
  planState,
  onDataChanged,
  onOpenSettingsModal,
  onOpenStats,
}: ProfileLinksTabProps) {
  const router = useRouter();
  const searchParams = useSearchParams();
  const toast = useToast();
  const planFeatures = planState?.features ?? DEFAULT_PLAN_FEATURES;

  const [links, setLinks] = useState<LinkItem[]>([]);
  const [localTags, setLocalTags] = useState<TagType[]>(tags);
  const [localFolders, setLocalFolders] = useState<FolderType[]>(folders);
  const [isLoading, setIsLoading] = useState(true);
  const [isLoadingMore, setIsLoadingMore] = useState(false);
  const [hasMore, setHasMore] = useState(false);
  const [currentPage, setCurrentPage] = useState(1);
  const [loadError, setLoadError] = useState('');
  const [showFolderMenu, setShowFolderMenu] = useState(false);
  const [showTagMenu, setShowTagMenu] = useState(false);
  const [showPeriodMenu, setShowPeriodMenu] = useState(false);
  const [showSortMenu, setShowSortMenu] = useState(false);
  const [showBulkFolderMenu, setShowBulkFolderMenu] = useState(false);
  const [showBulkTagsMenu, setShowBulkTagsMenu] = useState(false);
  const [newFolderName, setNewFolderName] = useState('');
  const [showAddFolderInput, setShowAddFolderInput] = useState(false);
  const [isCreatingFolder, setIsCreatingFolder] = useState(false);
  const [folderActionError, setFolderActionError] = useState('');
  const [openFolderActionsId, setOpenFolderActionsId] = useState<string | null>(null);
  const [editingFolderId, setEditingFolderId] = useState<string | null>(null);
  const [editingFolderName, setEditingFolderName] = useState('');
  const [isRenamingFolderId, setIsRenamingFolderId] = useState<string | null>(null);
  const [isDeletingFolderId, setIsDeletingFolderId] = useState<string | null>(null);
  const [newTagName, setNewTagName] = useState('');
  const [showAddTagInput, setShowAddTagInput] = useState(false);
  const [isCreatingTag, setIsCreatingTag] = useState(false);
  const [tagActionError, setTagActionError] = useState('');
  const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(searchQuery.trim());
  const [openTagActionsId, setOpenTagActionsId] = useState<string | null>(null);
  const [editingTagId, setEditingTagId] = useState<string | null>(null);
  const [editingTagName, setEditingTagName] = useState('');
  const [isRenamingTagId, setIsRenamingTagId] = useState<string | null>(null);
  const [isDeletingTagId, setIsDeletingTagId] = useState<string | null>(null);
  const [selectedLinkIds, setSelectedLinkIds] = useState<string[]>([]);
  const [isBulkSubmitting, setIsBulkSubmitting] = useState(false);
  const [openSettingsLinkId, setOpenSettingsLinkId] = useState<string | null>(null);
  const [qrModal, setQrModal] = useState<{ isOpen: boolean; url: string; title: string | null }>({
    isOpen: false,
    url: '',
    title: null,
  });
  const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
  const [editLink, setEditLink] = useState<LinkItem | null>(null);
  const [deleteTarget, setDeleteTarget] = useState<LinkItem | null>(null);
  const [isDeleting, setIsDeleting] = useState(false);
  const [isBulkDeleteConfirmOpen, setIsBulkDeleteConfirmOpen] = useState(false);

  const abortRef = useRef<AbortController | null>(null);
  const requestIdRef = useRef(0);

  const selectedPeriod = useMemo<LinksPeriod>(() => {
    const rawPeriod = searchParams.get('period');
    if (rawPeriod === '7' || rawPeriod === '30' || rawPeriod === '90' || rawPeriod === '365') {
      return rawPeriod;
    }
    return 'all';
  }, [searchParams]);

  const linksQueryKey = useMemo(
    () =>
      buildLinksListCacheKey({
        cacheScope,
        folderId,
        isFavorite,
        tagIds,
        searchQuery: debouncedSearchQuery,
        sortBy,
        selectedPeriod,
      }),
    [cacheScope, debouncedSearchQuery, folderId, isFavorite, selectedPeriod, sortBy, tagIds]
  );

  const visibleLinks = links;
  const activeFolderLabel = useMemo(() => {
    if (!folderId) return 'Все папки';
    return localFolders.find((folder) => folder.id === folderId)?.name || 'Все папки';
  }, [folderId, localFolders]);
  const activeTagLabel = useMemo(() => {
    if (tagIds.length === 0) return 'Теги';
    if (tagIds.length === 1) {
      return localTags.find((tag) => tag.id === tagIds[0])?.name || 'Теги';
    }
    return `Теги (${tagIds.length})`;
  }, [localTags, tagIds]);
  const selectedVisibleIds = useMemo(
    () => visibleLinks.map((link) => link.id).filter((id) => selectedLinkIds.includes(id)),
    [selectedLinkIds, visibleLinks]
  );
  const allVisibleSelected = visibleLinks.length > 0 && selectedVisibleIds.length === visibleLinks.length;
  const sortLabel = SORT_LABELS[sortBy];
  const createFlag = searchParams.get('create') === '1';
  const settingsFlag = searchParams.get('settings') === '1';

  const updateParams = useCallback((mutate: (params: URLSearchParams) => void) => {
    const params = new URLSearchParams(searchParams.toString());
    params.delete('create');
    params.delete('settings');
    mutate(params);
    router.replace(params.toString() ? `/profile/links?${params.toString()}` : '/profile/links', { scroll: false });
  }, [router, searchParams]);

  const handleOpenStats = useCallback((linkId: string) => {
    if (onOpenStats) {
      onOpenStats(linkId);
      return;
    }

    router.push(`/profile/links/${linkId}`, { scroll: false });
  }, [onOpenStats, router]);

  const fetchLinks = useCallback(
    async (page = 1, append = false, options?: { force?: boolean; showLoader?: boolean }) => {
      const requestId = ++requestIdRef.current;
      const force = options?.force ?? false;
      const showLoader = options?.showLoader ?? true;
      const controller = new AbortController();
      const requestKey = `${linksQueryKey}|${page}`;

      const params = new URLSearchParams();
      if (folderId) params.set('folderId', folderId);
      if (isFavorite) params.set('isFavorite', 'true');
      tagIds.forEach((id) => params.append('tagIds', id));
      if (debouncedSearchQuery) params.set('search', debouncedSearchQuery);
      params.set('sort', sortBy);
      if (selectedPeriod !== 'all') params.set('period', selectedPeriod);
      params.set('page', String(page));
      params.set('limit', String(PAGE_SIZE));
      const requestUrl = `/api/links?${params.toString()}`;

      if (!append) {
        abortRef.current?.abort();
        abortRef.current = controller;

        const cachedEntry = linksListCache.get(linksQueryKey);
        if (!force && cachedEntry && isFreshLinksCache(cachedEntry.updatedAt)) {
          setLinks(cachedEntry.links);
          setCurrentPage(cachedEntry.currentPage);
          setHasMore(cachedEntry.hasMore);
          setLoadError('');
          setIsLoading(false);
          return;
        }

        if (!force && cachedEntry) {
          setLinks(cachedEntry.links);
          setCurrentPage(cachedEntry.currentPage);
          setHasMore(cachedEntry.hasMore);
          setLoadError('');
          setIsLoading(false);
        } else if (showLoader) {
          setIsLoading(true);
        }
      } else {
        setIsLoadingMore(true);
      }

      try {
        const { response, data } = await requestLinksListPage(requestKey, requestUrl, controller.signal, force);

        if (!response.ok) {
          if (isUnauthorizedResponse(response)) {
            await signOutToPath('/login');
            return;
          }
          throw new Error(await getResponseErrorTextWithRequestId(response, 'Не удалось загрузить ссылки'));
        }

        if (requestId !== requestIdRef.current) return;

        const nextLinks = data.links || [];
        let mergedLinks: LinkItem[] = nextLinks;

        setLinks((prev) => {
          mergedLinks = append ? [...prev, ...nextLinks] : nextLinks;
          return mergedLinks;
        });

        const nextHasMore = Boolean(data.meta?.hasMore);
        setCurrentPage(page);
        setHasMore(nextHasMore);
        setLoadError('');

        linksListCache.set(linksQueryKey, {
          links: mergedLinks,
          currentPage: page,
          hasMore: nextHasMore,
          updatedAt: Date.now(),
        });
      } catch (error) {
        if (isAbortError(error)) return;
        const message = error instanceof Error ? error.message : 'Не удалось загрузить ссылки';
        setLoadError(message);
      } finally {
        if (!append) {
          if (requestId === requestIdRef.current) {
            setIsLoading(false);
          }
        } else {
          setIsLoadingMore(false);
        }
      }
    },
    [debouncedSearchQuery, folderId, isFavorite, linksQueryKey, selectedPeriod, sortBy, tagIds]
  );

  useEffect(() => {
    void fetchLinks(1, false, { showLoader: true });
  }, [fetchLinks]);

  useEffect(() => {
    setOpenSettingsLinkId((current) => {
      if (!current) return null;
      return links.some((item) => item.id === current) ? current : null;
    });
  }, [links]);

  useEffect(() => {
    setLocalTags(tags);
  }, [tags]);

  useEffect(() => {
    setLocalFolders(folders);
  }, [folders]);

  useEffect(() => {
    if (isLoading) return;

    linksListCache.set(linksQueryKey, {
      links,
      currentPage,
      hasMore,
      updatedAt: Date.now(),
    });
  }, [currentPage, hasMore, isLoading, links, linksQueryKey]);

  useEffect(() => {
    const timer = window.setTimeout(() => {
      const next = searchQuery.trim();
      setDebouncedSearchQuery((current) => (current === next ? current : next));
    }, 220);

    return () => window.clearTimeout(timer);
  }, [searchQuery]);

  useEffect(() => {
    if (!(
      showFolderMenu
      || showTagMenu
      || showPeriodMenu
      || showSortMenu
      || showBulkFolderMenu
      || showBulkTagsMenu
      || openSettingsLinkId
    )) {
      return;
    }

    const close = () => {
      setShowFolderMenu(false);
      setShowTagMenu(false);
      setShowPeriodMenu(false);
      setShowSortMenu(false);
      setShowBulkFolderMenu(false);
      setShowBulkTagsMenu(false);
      setOpenFolderActionsId(null);
      setOpenTagActionsId(null);
      setOpenSettingsLinkId(null);
    };

    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === 'Escape') {
        close();
      }
    };

    window.addEventListener('click', close);
    window.addEventListener('keydown', handleKeyDown);
    return () => {
      window.removeEventListener('click', close);
      window.removeEventListener('keydown', handleKeyDown);
    };
  }, [openSettingsLinkId, showBulkFolderMenu, showBulkTagsMenu, showFolderMenu, showPeriodMenu, showSortMenu, showTagMenu]);

  useEffect(() => {
    if (showFolderMenu) {
      setFolderActionError('');
      return;
    }

    setNewFolderName('');
    setShowAddFolderInput(false);
    setOpenFolderActionsId(null);
    setEditingFolderId(null);
    setEditingFolderName('');
    setFolderActionError('');
  }, [showFolderMenu]);

  useEffect(() => {
    if (showTagMenu) {
      setTagActionError('');
      return;
    }

    setNewTagName('');
    setShowAddTagInput(false);
    setOpenTagActionsId(null);
    setEditingTagId(null);
    setEditingTagName('');
    setTagActionError('');
  }, [showTagMenu]);

  useEffect(() => {
    if (visibleLinks.length === 0) {
      setSelectedLinkIds([]);
      return;
    }

    setSelectedLinkIds((current) => current.filter((id) => visibleLinks.some((link) => link.id === id)));
  }, [visibleLinks]);

  useEffect(() => {
    if (selectedLinkIds.length > 0) return;
    setShowBulkFolderMenu(false);
    setShowBulkTagsMenu(false);
  }, [selectedLinkIds]);

  useEffect(() => () => {
    abortRef.current?.abort();
  }, []);

  useEffect(() => {
    if (!createFlag && !settingsFlag) return;

    if (createFlag) {
      setIsCreateDialogOpen(true);
    }
    if (settingsFlag) {
      onOpenSettingsModal?.();
    }

    updateParams((params) => {
      params.delete('create');
      params.delete('settings');
    });
  }, [createFlag, settingsFlag, onOpenSettingsModal, updateParams]);

  const handleToggleFavorite = useCallback(async (link: LinkItem) => {
    try {
      const response = await fetch('/api/links/favorite', {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          id: link.id,
          isFavorite: !link.isFavorite,
        }),
      });

      if (!response.ok) {
        throw new Error(await getResponseErrorTextWithRequestId(response, 'Не удалось обновить избранное'));
      }

      setLinks((prev) => {
        if (isFavorite && link.isFavorite) {
          return prev.filter((item) => item.id !== link.id);
        }
        return prev.map((item) => (
          item.id === link.id ? { ...item, isFavorite: !item.isFavorite } : item
        ));
      });
    } catch (error) {
      toast.error(error instanceof Error ? error.message : 'Не удалось обновить избранное');
    }
  }, [isFavorite, toast]);

  const handleCopy = useCallback(async (value: string) => {
    try {
      await navigator.clipboard.writeText(value);
      toast.success('Ссылка скопирована');
    } catch {
      toast.error('Не удалось скопировать ссылку');
    }
  }, [toast]);

  const handleDelete = useCallback(async () => {
    if (!deleteTarget) return;
    const targetId = deleteTarget.id;
    setIsDeleting(true);
    try {
      const response = await fetch(`/api/links?id=${targetId}`, { method: 'DELETE' });
      if (!response.ok) {
        throw new Error(await getResponseErrorTextWithRequestId(response, 'Не удалось удалить ссылку'));
      }
      setDeleteTarget(null);
      setLinks((prev) => prev.filter((link) => link.id !== targetId));
      setSelectedLinkIds((prev) => prev.filter((id) => id !== targetId));
      toast.success('Ссылка удалена');

      void fetchLinks(1, false, { force: true, showLoader: false });
      if (onDataChanged) {
        void Promise.resolve(onDataChanged());
      }
    } catch (error) {
      toast.error(error instanceof Error ? error.message : 'Не удалось удалить ссылку');
    } finally {
      setIsDeleting(false);
    }
  }, [deleteTarget, fetchLinks, onDataChanged, toast]);

  const handleLinksCreated = useCallback(async () => {
    void fetchLinks(1, false, { force: true, showLoader: false });
    if (onDataChanged) {
      void Promise.resolve(onDataChanged());
    }
  }, [fetchLinks, onDataChanged]);

  const handleLinksUpdated = useCallback(async () => {
    void fetchLinks(1, false, { force: true, showLoader: false });
  }, [fetchLinks]);

  const refreshTagsInBackground = useCallback(async () => {
    try {
      const response = await fetch('/api/tags', { cache: 'no-store' });
      if (!response.ok) {
        if (isUnauthorizedResponse(response)) {
          await signOutToPath('/login');
        }
        return;
      }

      const data = await response.json() as { tags?: TagType[] };
      setLocalTags(data.tags || []);
    } catch {
      // ignore background refresh errors
    }
  }, []);

  const handleCreateFolder = useCallback(async () => {
    const trimmedName = newFolderName.trim();
    if (!trimmedName || isCreatingFolder) return;

    if (localFolders.some((folder) => folder.name.trim().toLowerCase() === trimmedName.toLowerCase())) {
      setFolderActionError('Папка с таким названием уже существует');
      return;
    }

    setIsCreatingFolder(true);
    setFolderActionError('');

    try {
      const response = await fetch('/api/folders', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: trimmedName }),
      });

      if (!response.ok) {
        if (isUnauthorizedResponse(response)) {
          await signOutToPath('/login');
          return;
        }

        throw new Error(await getResponseErrorTextWithRequestId(response, 'Не удалось создать папку'));
      }

      const data = await response.json() as { folder?: FolderType };
      const createdFolder = data.folder;

      if (createdFolder) {
        setLocalFolders((prev) => {
          const alreadyExists = prev.some((item) => item.id === createdFolder.id);
          if (alreadyExists) return prev;

          const next = [...prev, {
            ...createdFolder,
            _count: createdFolder._count ?? { links: 0 },
          }];
          next.sort((a, b) => a.name.localeCompare(b.name, 'ru-RU'));
          return next;
        });
      }

      setShowAddFolderInput(false);
      setNewFolderName('');
      if (onDataChanged) {
        void Promise.resolve(onDataChanged());
      }
      toast.success('Папка создана');
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Не удалось создать папку';
      setFolderActionError(message);
      toast.error(message);
    } finally {
      setIsCreatingFolder(false);
    }
  }, [isCreatingFolder, localFolders, newFolderName, onDataChanged, toast]);

  const startRenameFolder = useCallback((folder: FolderType) => {
    setEditingFolderId(folder.id);
    setEditingFolderName(folder.name);
    setOpenFolderActionsId(null);
    setFolderActionError('');
  }, []);

  const cancelRenameFolder = useCallback(() => {
    setEditingFolderId(null);
    setEditingFolderName('');
    setFolderActionError('');
  }, []);

  const handleRenameFolder = useCallback(async () => {
    if (!editingFolderId) return;

    const trimmedName = editingFolderName.trim();
    if (!trimmedName || isRenamingFolderId) return;

    if (
      localFolders.some(
        (folder) => folder.id !== editingFolderId && folder.name.trim().toLowerCase() === trimmedName.toLowerCase()
      )
    ) {
      setFolderActionError('Папка с таким названием уже существует');
      return;
    }

    setIsRenamingFolderId(editingFolderId);
    setFolderActionError('');

    try {
      const response = await fetch(`/api/folders?id=${encodeURIComponent(editingFolderId)}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: trimmedName }),
      });

      if (!response.ok) {
        if (isUnauthorizedResponse(response)) {
          await signOutToPath('/login');
          return;
        }

        throw new Error(await getResponseErrorTextWithRequestId(response, 'Не удалось переименовать папку'));
      }

      const data = await response.json() as { folder?: FolderType };
      const renamedFolder = data.folder;

      if (renamedFolder) {
        setLocalFolders((prev) => {
          const next = prev.map((folder) => (
            folder.id === renamedFolder.id
              ? { ...folder, name: renamedFolder.name }
              : folder
          ));
          next.sort((a, b) => a.name.localeCompare(b.name, 'ru-RU'));
          return next;
        });

        setLinks((prev) => prev.map((link) => (
          link.folder?.id === renamedFolder.id
            ? { ...link, folder: { id: renamedFolder.id, name: renamedFolder.name } }
            : link
        )));
      }

      setEditingFolderId(null);
      setEditingFolderName('');
      setOpenFolderActionsId(null);
      if (onDataChanged) {
        void Promise.resolve(onDataChanged());
      }
      toast.success('Папка переименована');
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Не удалось переименовать папку';
      setFolderActionError(message);
      toast.error(message);
    } finally {
      setIsRenamingFolderId(null);
    }
  }, [editingFolderId, editingFolderName, isRenamingFolderId, localFolders, onDataChanged, toast]);

  const handleDeleteFolder = useCallback(async (folder: FolderType) => {
    if (isDeletingFolderId) return;

    setIsDeletingFolderId(folder.id);
    setFolderActionError('');

    try {
      const response = await fetch(`/api/folders?id=${encodeURIComponent(folder.id)}`, { method: 'DELETE' });
      if (!response.ok) {
        if (isUnauthorizedResponse(response)) {
          await signOutToPath('/login');
          return;
        }

        throw new Error(await getResponseErrorTextWithRequestId(response, 'Не удалось удалить папку'));
      }

      setLocalFolders((prev) => prev.filter((item) => item.id !== folder.id));
      setLinks((prev) => prev.map((link) => (
        link.folder?.id === folder.id ? { ...link, folder: null } : link
      )));

      if (folderId === folder.id) {
        updateParams((params) => {
          params.delete('folderId');
        });
      }

      setOpenFolderActionsId(null);
      setEditingFolderId(null);
      setEditingFolderName('');
      if (onDataChanged) {
        void Promise.resolve(onDataChanged());
      }
      toast.success(`Папка «${folder.name}» удалена`);
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Не удалось удалить папку';
      setFolderActionError(message);
      toast.error(message);
    } finally {
      setIsDeletingFolderId(null);
    }
  }, [folderId, isDeletingFolderId, onDataChanged, toast, updateParams]);

  const handleCreateTag = useCallback(async () => {
    const trimmedName = newTagName.trim();
    if (!trimmedName || isCreatingTag) return;

    if (localTags.some((tag) => tag.name.trim().toLowerCase() === trimmedName.toLowerCase())) {
      setTagActionError('Тег с таким названием уже существует');
      return;
    }

    setIsCreatingTag(true);
    setTagActionError('');

    try {
      const response = await fetch('/api/tags', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          name: trimmedName,
          color: '#8b5cf6',
        }),
      });

      if (!response.ok) {
        if (isUnauthorizedResponse(response)) {
          await signOutToPath('/login');
          return;
        }

        throw new Error(await getResponseErrorTextWithRequestId(response, 'Не удалось создать тег'));
      }

      const data = await response.json() as { tag?: TagType };
      const createdTag = data.tag;

      if (createdTag) {
        setLocalTags((prev) => {
          const alreadyExists = prev.some((item) => item.id === createdTag.id);
          if (alreadyExists) return prev;

          const next = [...prev, {
            ...createdTag,
            _count: createdTag._count ?? { links: 0 },
          }];
          next.sort((a, b) => a.name.localeCompare(b.name, 'ru-RU'));
          return next;
        });
      }

      setShowAddTagInput(false);
      setNewTagName('');
      void refreshTagsInBackground();
      if (onDataChanged) {
        void Promise.resolve(onDataChanged());
      }
      toast.success('Тег создан');
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Не удалось создать тег';
      setTagActionError(message);
      toast.error(message);
    } finally {
      setIsCreatingTag(false);
    }
  }, [isCreatingTag, localTags, newTagName, onDataChanged, refreshTagsInBackground, toast]);

  const toggleTagFilter = useCallback((tagId: string) => {
    updateParams((params) => {
      const current = params.getAll('tagIds');
      const next = current.includes(tagId)
        ? current.filter((id) => id !== tagId)
        : [...current, tagId];
      writeTagIdsToParams(params, next);
    });
  }, [updateParams]);

  const startRenameTag = useCallback((tag: TagType) => {
    setEditingTagId(tag.id);
    setEditingTagName(tag.name);
    setOpenTagActionsId(null);
    setTagActionError('');
  }, []);

  const cancelRenameTag = useCallback(() => {
    setEditingTagId(null);
    setEditingTagName('');
    setTagActionError('');
  }, []);

  const handleRenameTag = useCallback(async () => {
    if (!editingTagId) return;

    const trimmedName = editingTagName.trim();
    if (!trimmedName || isRenamingTagId) return;

    if (
      localTags.some(
        (tag) => tag.id !== editingTagId && tag.name.trim().toLowerCase() === trimmedName.toLowerCase()
      )
    ) {
      setTagActionError('Тег с таким названием уже существует');
      return;
    }

    setIsRenamingTagId(editingTagId);
    setTagActionError('');

    try {
      const response = await fetch(`/api/tags?id=${editingTagId}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: trimmedName }),
      });

      if (!response.ok) {
        if (isUnauthorizedResponse(response)) {
          await signOutToPath('/login');
          return;
        }

        throw new Error(await getResponseErrorTextWithRequestId(response, 'Не удалось переименовать тег'));
      }

      const data = await response.json() as { tag?: TagType };
      const renamedTag = data.tag;

      if (renamedTag) {
        setLocalTags((prev) => {
          const next = prev.map((tag) => (
            tag.id === renamedTag.id
              ? {
                ...tag,
                name: renamedTag.name,
                color: renamedTag.color,
              }
              : tag
          ));
          next.sort((a, b) => a.name.localeCompare(b.name, 'ru-RU'));
          return next;
        });

        setLinks((prev) => prev.map((link) => ({
          ...link,
          tags: link.tags.map((entry) => (
            entry.tag.id === renamedTag.id
              ? { ...entry, tag: { ...entry.tag, name: renamedTag.name, color: renamedTag.color } }
              : entry
          )),
        })));
      }

      setEditingTagId(null);
      setEditingTagName('');
      setOpenTagActionsId(null);
      void refreshTagsInBackground();
      if (onDataChanged) {
        void Promise.resolve(onDataChanged());
      }
      toast.success('Тег переименован');
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Не удалось переименовать тег';
      setTagActionError(message);
      toast.error(message);
    } finally {
      setIsRenamingTagId(null);
    }
  }, [editingTagId, editingTagName, isRenamingTagId, localTags, onDataChanged, refreshTagsInBackground, toast]);

  const handleDeleteTag = useCallback(async (tag: TagType) => {
    if (isDeletingTagId) return;

    setIsDeletingTagId(tag.id);
    setTagActionError('');

    try {
      const response = await fetch(`/api/tags?id=${tag.id}`, { method: 'DELETE' });
      if (!response.ok) {
        if (isUnauthorizedResponse(response)) {
          await signOutToPath('/login');
          return;
        }

        throw new Error(await getResponseErrorTextWithRequestId(response, 'Не удалось удалить тег'));
      }

      setLocalTags((prev) => prev.filter((item) => item.id !== tag.id));
      setLinks((prev) => prev.map((link) => ({
        ...link,
        tags: link.tags.filter((entry) => entry.tag.id !== tag.id),
      })));

      if (tagIds.includes(tag.id)) {
        updateParams((params) => {
          const next = params.getAll('tagIds').filter((id) => id !== tag.id);
          writeTagIdsToParams(params, next);
        });
      }

      setOpenTagActionsId(null);
      setEditingTagId(null);
      setEditingTagName('');
      void refreshTagsInBackground();
      if (onDataChanged) {
        void Promise.resolve(onDataChanged());
      }
      toast.success(`Тег «${tag.name}» удалён`);
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Не удалось удалить тег';
      setTagActionError(message);
      toast.error(message);
    } finally {
      setIsDeletingTagId(null);
    }
  }, [isDeletingTagId, onDataChanged, refreshTagsInBackground, tagIds, toast, updateParams]);

  const closeFilterMenus = useCallback(() => {
    setShowFolderMenu(false);
    setShowTagMenu(false);
    setShowPeriodMenu(false);
    setShowSortMenu(false);
    setShowBulkFolderMenu(false);
    setShowBulkTagsMenu(false);
    setOpenFolderActionsId(null);
    setEditingFolderId(null);
    setEditingFolderName('');
    setOpenTagActionsId(null);
    setEditingTagId(null);
    setEditingTagName('');
  }, []);

  const closeOtherMenus = useCallback((keep: MenuKey) => {
    if (keep !== 'folder') setShowFolderMenu(false);
    if (keep !== 'tag') setShowTagMenu(false);
    if (keep !== 'period') setShowPeriodMenu(false);
    if (keep !== 'sort') setShowSortMenu(false);
    setShowBulkFolderMenu(false);
    setShowBulkTagsMenu(false);
    setOpenFolderActionsId(null);
    setOpenSettingsLinkId(null);
  }, []);

  const toggleSelectedLink = useCallback((linkId: string) => {
    setSelectedLinkIds((current) => (
      current.includes(linkId)
        ? current.filter((id) => id !== linkId)
        : [...current, linkId]
    ));
  }, []);

  const toggleSelectAllVisible = useCallback(() => {
    setSelectedLinkIds((current) => {
      if (allVisibleSelected) {
        return current.filter((id) => !visibleLinks.some((link) => link.id === id));
      }
      const merged = new Set(current);
      visibleLinks.forEach((link) => merged.add(link.id));
      return Array.from(merged);
    });
  }, [allVisibleSelected, visibleLinks]);

  const runBulkAction = useCallback(async (
    action: 'move' | 'addTags' | 'removeTags' | 'setFavorite' | 'delete',
    data: Record<string, unknown>,
    successMessage: string,
    options?: { refreshDashboard?: boolean }
  ) => {
    if (selectedLinkIds.length === 0) return;

    if (!planFeatures.bulkActions) {
      toast.info('Массовые действия недоступны на вашем тарифе.');
      return;
    }

    const actionLinkIds = [...selectedLinkIds];

    setIsBulkSubmitting(true);
    try {
      const response = await fetch('/api/links/bulk', {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          linkIds: actionLinkIds,
          action,
          data,
        }),
      });

      if (!response.ok) {
        if (isUnauthorizedResponse(response)) {
          await signOutToPath('/login');
          return;
        }
        throw new Error(await getResponseErrorTextWithRequestId(response, 'Не удалось выполнить массовое действие'));
      }

      closeFilterMenus();
      setSelectedLinkIds([]);

      if (action === 'delete') {
        const targetIds = new Set(actionLinkIds);
        setLinks((prev) => prev.filter((link) => !targetIds.has(link.id)));
      }

      toast.success(successMessage);

      void fetchLinks(1, false, { force: true, showLoader: false });

      if (action === 'addTags' || action === 'removeTags') {
        void refreshTagsInBackground();
      }

      const shouldRefreshDashboard = options?.refreshDashboard ?? (action === 'delete' || action === 'setFavorite' || action === 'move');
      if (shouldRefreshDashboard && onDataChanged) {
        void Promise.resolve(onDataChanged());
      }
    } catch (error) {
      toast.error(error instanceof Error ? error.message : 'Не удалось выполнить массовое действие');
    } finally {
      setIsBulkSubmitting(false);
    }
  }, [closeFilterMenus, fetchLinks, onDataChanged, planFeatures.bulkActions, refreshTagsInBackground, selectedLinkIds, toast]);

  const selectedLinksLabel = useMemo(() => {
    const count = selectedLinkIds.length;
    const mod10 = count % 10;
    const mod100 = count % 100;
    if (mod10 === 1 && mod100 !== 11) return `${count} ссылка`;
    if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) return `${count} ссылки`;
    return `${count} ссылок`;
  }, [selectedLinkIds.length]);

  return (
    <motion.section
      key="links"
      initial={{ opacity: 0, y: 14 }}
      animate={{ opacity: 1, y: 0 }}
      exit={{ opacity: 0, y: -10 }}
      transition={{ duration: 0.24, ease: 'easeOut' }}
      onClick={() => {
        setOpenSettingsLinkId(null);
        closeFilterMenus();
      }}
      className="mx-auto max-w-5xl space-y-6"
    >
      <div>
        <h1 className="mb-2 text-3xl font-bold tracking-tight text-white">Ссылки</h1>
        <p className="text-sm text-gray-400">
          Управление вашими короткими ссылками, тегами и настройками маршрутизации.
        </p>
      </div>

      <button
        type="button"
        onClick={() => setIsCreateDialogOpen(true)}
        className="mobile-create-link-button inline-flex h-11 w-full items-center justify-center gap-2 rounded-xl bg-gradient-to-r from-fuchsia-500 to-violet-600 px-4 text-sm font-semibold text-white shadow-[0_12px_35px_rgba(139,92,246,0.25)] transition-all duration-200 hover:opacity-95 sm:hidden"
      >
        <Plus className="h-4 w-4" />
        Создать ссылку
      </button>

      <FilterToolbar
        folderId={folderId}
        isFavorite={isFavorite}
        tagIds={tagIds}
        sortBy={sortBy}
        selectedPeriod={selectedPeriod}
        totalLinks={totalLinks}
        favoriteCount={favoriteCount}
        folders={localFolders}
        localTags={localTags}
        activeFolderLabel={activeFolderLabel}
        activeTagLabel={activeTagLabel}
        sortLabel={sortLabel}
        showFolderMenu={showFolderMenu}
        showTagMenu={showTagMenu}
        showPeriodMenu={showPeriodMenu}
        showSortMenu={showSortMenu}
        setShowFolderMenu={setShowFolderMenu}
        setShowTagMenu={setShowTagMenu}
        setShowPeriodMenu={setShowPeriodMenu}
        setShowSortMenu={setShowSortMenu}
        closeOtherMenus={closeOtherMenus}
        newFolderName={newFolderName}
        setNewFolderName={setNewFolderName}
        showAddFolderInput={showAddFolderInput}
        setShowAddFolderInput={setShowAddFolderInput}
        isCreatingFolder={isCreatingFolder}
        folderActionError={folderActionError}
        setFolderActionError={setFolderActionError}
        openFolderActionsId={openFolderActionsId}
        setOpenFolderActionsId={setOpenFolderActionsId}
        editingFolderId={editingFolderId}
        editingFolderName={editingFolderName}
        setEditingFolderName={setEditingFolderName}
        isRenamingFolderId={isRenamingFolderId}
        isDeletingFolderId={isDeletingFolderId}
        newTagName={newTagName}
        setNewTagName={setNewTagName}
        showAddTagInput={showAddTagInput}
        setShowAddTagInput={setShowAddTagInput}
        isCreatingTag={isCreatingTag}
        tagActionError={tagActionError}
        setTagActionError={setTagActionError}
        openTagActionsId={openTagActionsId}
        setOpenTagActionsId={setOpenTagActionsId}
        editingTagId={editingTagId}
        editingTagName={editingTagName}
        setEditingTagName={setEditingTagName}
        isRenamingTagId={isRenamingTagId}
        isDeletingTagId={isDeletingTagId}
        handleCreateTag={() => { void handleCreateTag(); }}
        handleCreateFolder={() => { void handleCreateFolder(); }}
        handleRenameFolder={() => { void handleRenameFolder(); }}
        handleDeleteFolder={(folder) => { void handleDeleteFolder(folder); }}
        startRenameFolder={startRenameFolder}
        cancelRenameFolder={cancelRenameFolder}
        handleRenameTag={() => { void handleRenameTag(); }}
        handleDeleteTag={(tag) => { void handleDeleteTag(tag); }}
        startRenameTag={startRenameTag}
        cancelRenameTag={cancelRenameTag}
        toggleTagFilter={toggleTagFilter}
        updateParams={updateParams}
      />

      <ApiErrorAlert message={loadError} />

      {isLoading ? (
        <div className="space-y-4">
          {Array.from({ length: 4 }).map((_, index) => (
            <div key={`link-skeleton-${index}`} className="h-[120px] animate-pulse rounded-2xl border border-white/10 bg-white/[0.02]" />
          ))}
        </div>
      ) : visibleLinks.length === 0 ? (
        <div className="rounded-2xl border border-white/10 bg-black/40 p-8 text-center backdrop-blur-md">
          <p className="mb-1 text-sm text-gray-400">
            {links.length === 0 ? 'Ссылки не найдены' : 'Нет активных ссылок в текущей выборке'}
          </p>
          <p className="text-xs text-gray-500">Попробуйте изменить фильтры или строку поиска</p>
        </div>
      ) : (
        <div className="space-y-4">
          <div className="flex items-center justify-between rounded-xl border border-white/10 bg-black/40 px-4 py-3">
            <div className="flex items-center gap-3">
              <button
                type="button"
                onClick={toggleSelectAllVisible}
                className="text-gray-400 transition-colors hover:text-white"
              >
                {allVisibleSelected ? (
                  <CheckSquare className="h-5 w-5 text-violet-400" />
                ) : selectedVisibleIds.length > 0 ? (
                  <CheckSquare className="h-5 w-5 text-violet-400/60" />
                ) : (
                  <Square className="h-5 w-5" />
                )}
              </button>
              <span className="text-sm text-gray-400">Выбрать все</span>
            </div>
            <span className="text-sm text-gray-500">Показано {visibleLinks.length}</span>
          </div>

          <BulkActionsBar
            selectedCount={selectedLinkIds.length}
            isBulkSubmitting={isBulkSubmitting}
            folders={localFolders}
            localTags={localTags}
            showBulkFolderMenu={showBulkFolderMenu}
            showBulkTagsMenu={showBulkTagsMenu}
            onOpenBulkFolderMenu={() => {
              setShowBulkFolderMenu((prev) => !prev);
              setShowBulkTagsMenu(false);
            }}
            onOpenBulkTagsMenu={() => {
              setShowBulkTagsMenu((prev) => !prev);
              setShowBulkFolderMenu(false);
            }}
            onBulkMoveToFolder={(folderIdValue) => {
              void runBulkAction(
                'move',
                { folderId: folderIdValue },
                folderIdValue ? 'Ссылки перемещены' : 'Папки очищены'
              );
            }}
            onBulkAddTag={(tagId) => { void runBulkAction('addTags', { tagIds: [tagId] }, 'Теги добавлены'); }}
            onBulkClearTags={() => { void runBulkAction('removeTags', { tagIds: ['all'] }, 'Теги очищены'); }}
            onBulkDelete={() => { setIsBulkDeleteConfirmOpen(true); }}
          />

          <motion.div layout className="space-y-4">
            <AnimatePresence initial={false}>
              {visibleLinks.map((link, index) => (
                <LinkRow
                  key={link.id}
                  link={link}
                  index={index}
                  selected={selectedLinkIds.includes(link.id)}
                  settingsOpen={openSettingsLinkId === link.id}
                  onToggleSelect={toggleSelectedLink}
                  onToggleFavorite={(target) => { void handleToggleFavorite(target); }}
                  onCopy={(value) => { void handleCopy(value); }}
                  onOpenStats={handleOpenStats}
                  onOpenQr={(target) => setQrModal({ isOpen: true, url: target.shortUrl, title: target.title })}
                  onToggleSettings={(linkId) => {
                    closeFilterMenus();
                    setOpenSettingsLinkId((current) => (current === linkId ? null : linkId));
                  }}
                  onCloseSettings={() => setOpenSettingsLinkId(null)}
                  onEdit={(target) => setEditLink(target)}
                  onRequestDelete={(target) => setDeleteTarget(target)}
                />
              ))}
            </AnimatePresence>
          </motion.div>
        </div>
      )}

      {hasMore ? (
        <div className="flex justify-center">
          <button
            type="button"
            disabled={isLoadingMore}
            onClick={() => void fetchLinks(currentPage + 1, true)}
            className="px-4 py-2 rounded-lg border border-white/10 bg-white/[0.02] text-sm text-gray-300 hover:text-white hover:bg-white/[0.06] transition-colors disabled:opacity-60"
          >
            {isLoadingMore ? 'Загрузка...' : 'Показать ещё'}
          </button>
        </div>
      ) : null}

      <QRCodeModal
        isOpen={qrModal.isOpen}
        onClose={() => setQrModal({ isOpen: false, url: '', title: null })}
        url={qrModal.url}
        title={qrModal.title}
      />

      <CreateLinkDialog
        isOpen={isCreateDialogOpen}
        onClose={() => setIsCreateDialogOpen(false)}
        onCreated={handleLinksCreated}
        planFeatures={planFeatures}
        initialFolderId={folderId}
      />

      <EditLinkDialog
        isOpen={Boolean(editLink)}
        link={editLink}
        planFeatures={planFeatures}
        onClose={() => setEditLink(null)}
        onSaved={handleLinksUpdated}
      />

      <Modal
        isOpen={Boolean(deleteTarget)}
        onClose={() => setDeleteTarget(null)}
        onConfirm={() => void handleDelete()}
        title="Удалить ссылку?"
        description={deleteTarget ? `Ссылка "${deleteTarget.title || deleteTarget.slug}" будет удалена без возможности восстановления.` : ''}
        confirmText="Удалить"
        cancelText="Отмена"
        variant="danger"
        isLoading={isDeleting}
      />

      <Modal
        isOpen={isBulkDeleteConfirmOpen}
        onClose={() => setIsBulkDeleteConfirmOpen(false)}
        onConfirm={() => {
          setIsBulkDeleteConfirmOpen(false);
          void runBulkAction('delete', {}, 'Ссылки удалены');
        }}
        title="Удалить выбранные ссылки?"
        description={`Будет удалено ${selectedLinksLabel} без возможности восстановления.`}
        confirmText="Удалить"
        cancelText="Отмена"
        variant="danger"
        isLoading={isBulkSubmitting}
      />
    </motion.section>
  );
}
