import message from '@/components/ui/message';
import fileManagerService from '@/services/file-manager-service';
import skillSpaceService, {
  SkillSearchConfig,
} from '@/services/skill-space-service';
import { getAuthorization } from '@/utils/authorization-util';
import { useQuery } from '@tanstack/react-query';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { Skill, SkillFileEntry, SkillMetadata, SkillSpace } from './types';
import {
  filterUploadFiles,
  isTextFile,
  parseFrontmatter,
  validateSkillFormat as validateSkillFormatImpl,
} from './validation';

const SKILLS_FOLDER = 'skills';

// Helper to get file extension
const getFileExt = (filename: string): string => {
  const parts = filename.split('.');
  return parts.length > 1 ? parts.pop()!.toLowerCase() : '';
};

// Helper to check if file is markdown
export const isMarkdownFile = (filename: string): boolean => {
  const mdExts = ['md', 'markdown', 'mdown', 'mkd'];
  return mdExts.includes(getFileExt(filename));
};

// Helper to parse YAML-like metadata from markdown frontmatter
export const parseMetadata = (
  content: string,
): { metadata: SkillMetadata; body: string } => {
  const { metadata, body } = parseFrontmatter(content);
  return { metadata, body };
};

// Normalize timestamp-like values from backend to milliseconds.
// Supports epoch seconds, epoch milliseconds and ISO datetime strings.
const toTimestampMs = (value: unknown): number | null => {
  if (value === null || value === undefined || value === '') return null;

  const normalizeEpoch = (raw: number): number | null => {
    if (!Number.isFinite(raw)) return null;

    let n = raw;
    // Convert unit by magnitude: ns -> us -> ms -> s.
    // Current epoch in ms is around 1e12.
    if (n > 1e17)
      n = n / 1e6; // nanoseconds
    else if (n > 1e14)
      n = n / 1e3; // microseconds
    else if (n < 1e11) n = n * 1e3; // seconds

    return Math.round(n);
  };

  if (typeof value === 'number' && Number.isFinite(value)) {
    return normalizeEpoch(value);
  }

  if (typeof value === 'string') {
    const trimmed = value.trim();
    if (!trimmed) return null;

    const numeric = Number(trimmed);
    if (!Number.isNaN(numeric)) {
      return normalizeEpoch(numeric);
    }

    const parsed = Date.parse(trimmed);
    return Number.isNaN(parsed) ? null : parsed;
  }

  return null;
};

const pickSkillTimestamp = (result: any): number => {
  const candidates = [
    result?.updated_at,
    result?.updatedAt,
    result?.update_time,
    result?.updateTime,
    result?.update_date,
    result?.modified_at,
    result?.modifiedAt,
    result?.metadata?.updated_at,
    result?.metadata?.updatedAt,
    result?.metadata?.update_time,
    result?.metadata?.updateTime,
    result?.metadata?.update_date,
    result?.skill?.updated_at,
    result?.skill?.updatedAt,
    result?.skill?.update_time,
    result?.skill?.updateTime,
    result?.skill?.update_date,
    result?.created_at,
    result?.createdAt,
    result?.create_time,
    result?.createTime,
    result?.create_date,
    result?.metadata?.created_at,
    result?.metadata?.createdAt,
    result?.metadata?.create_time,
    result?.metadata?.createTime,
    result?.metadata?.create_date,
    result?.skill?.created_at,
    result?.skill?.createdAt,
    result?.skill?.create_time,
    result?.skill?.createTime,
    result?.skill?.create_date,
  ];

  for (const candidate of candidates) {
    const ts = toTimestampMs(candidate);
    if (ts !== null) return ts;
  }

  return Date.now();
};

// Export validation function from validation module
export { validateSkillFormatImpl as validateSkillFormat };

// Re-export validation utilities for use in components
export {
  isMacJunkPath,
  isTextFile,
  parseFrontmatter,
  sanitizeRelPath,
} from './validation';

// Query key for file content
const fileContentQueryKey = (fileId: string) => ['skillFileContent', fileId];

// Hook to fetch file content using TanStack Query
export const useFileContent = (fileId: string | null) => {
  return useQuery({
    queryKey: fileContentQueryKey(fileId || ''),
    queryFn: async (): Promise<string | null> => {
      if (!fileId) return null;
      const response = await fileManagerService.getFile({}, fileId);
      const blob = response.data as Blob;
      return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => resolve(reader.result as string);
        reader.onerror = () => reject(reader.error);
        reader.readAsText(blob);
      });
    },
    enabled: !!fileId,
    staleTime: 5 * 60 * 1000, // 5 minutes
  });
};

// Hook to manage skills
export const useSkills = () => {
  const { t } = useTranslation();
  const [skills, setSkills] = useState<Skill[]>([]);
  const [loading, setLoading] = useState(false);
  const [searchQuery, setSearchQuery] = useState('');

  // Fetch file content (kept for backward compatibility)
  const fetchFileContent = async (fileId: string): Promise<string | null> => {
    try {
      const response = await fileManagerService.getFile({}, fileId);
      const blob = response.data as Blob;
      return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => resolve(reader.result as string);
        reader.onerror = () => reject(reader.error);
        reader.readAsText(blob);
      });
    } catch (error) {
      console.error('Error fetching file content:', error);
      return null;
    }
  };

  // Fetch details of a specific skill (with version support)
  const fetchSkillDetails = async (
    folderId: string,
    folderName: string,
  ): Promise<Skill | null> => {
    try {
      // First, list the skill folder to find version folders
      const { data: skillFolderData } = await fileManagerService.listFile({
        parent_id: folderId,
      });

      if (skillFolderData.code !== 0) return null;

      const skillItems = skillFolderData.data?.files || [];

      // Find version folders (folders that match semver pattern like x.y.z)
      const versionFolders = skillItems.filter(
        (f: any) => f.type === 'folder' && /^\d+\.\d+\.\d+/.test(f.name),
      );

      if (versionFolders.length === 0) {
        // No version folders found - fallback to legacy structure
        // oxlint-disable-next-line typescript/no-use-before-define
        return fetchSkillDetailsLegacy(folderId, folderName, skillItems);
      }

      // Sort versions by version number (descending)
      const sortedVersions = versionFolders.sort((a: any, b: any) => {
        const va = a.name.split('.').map(Number);
        const vb = b.name.split('.').map(Number);
        for (let i = 0; i < Math.max(va.length, vb.length); i++) {
          const na = va[i] || 0;
          const nb = vb[i] || 0;
          if (na !== nb) return nb - na; // Descending order
        }
        return 0;
      });

      const allVersions = sortedVersions.map((v: any) => v.name);
      const latestVersionFolder = sortedVersions[0];
      const versionFolderId = latestVersionFolder.id;
      const versionName = latestVersionFolder.name;

      // Get all files recursively in the latest version folder
      const fileEntries: SkillFileEntry[] = [];
      let readmeContent: string | null = null;
      let firstFileDate: string | null = null;

      // Recursively fetch all files
      const fetchFilesRecursive = async (
        parentId: string,
        basePath: string = '',
      ) => {
        const { data } = await fileManagerService.listFile({
          parent_id: parentId,
        });
        if (data.code !== 0) return;

        const files = data.data?.files || [];

        // Track date from first encountered file
        if (!firstFileDate && files.length > 0) {
          firstFileDate = files[0]?.create_date || files[0]?.update_date;
        }

        for (const f of files) {
          const path = basePath ? `${basePath}/${f.name}` : f.name;

          fileEntries.push({
            name: f.name,
            path: path,
            is_dir: f.type === 'folder',
            size: f.size || 0,
          });

          // Check for SKILL.md first, then README.md for metadata
          const lowerName = f.name.toLowerCase();
          if (
            lowerName === 'skill.md' ||
            lowerName === 'readme.md' ||
            lowerName === 'index.md'
          ) {
            if (!readmeContent) {
              readmeContent = await fetchFileContent(f.id);
            }
          }

          // Recursively fetch subfolder contents
          if (f.type === 'folder') {
            await fetchFilesRecursive(f.id, path);
          }
        }
      };

      await fetchFilesRecursive(versionFolderId);

      // Parse metadata from README
      let metadata: SkillMetadata = {};
      let description = '';

      if (readmeContent) {
        const parsed = parseMetadata(readmeContent);
        metadata = parsed.metadata;
        description = metadata.description || parsed.body.slice(0, 200);
      }

      // Get dates
      const createDate = firstFileDate || new Date().toISOString();
      const updateDate = createDate;

      // Priority: folderName (user-specified) > metadata.name (from SKILL.md)
      // This allows users to override the skill name from SKILL.md
      const skillName = folderName || metadata.name || 'unnamed-skill';

      return {
        id: skillName, // Use skill name as ID (consistent with search results)
        name: skillName,
        description,
        source_type: 'local',
        created_at: new Date(createDate).getTime(),
        updated_at: new Date(updateDate).getTime(),
        files: fileEntries,
        metadata: { ...metadata, version: versionName },
        versions: allVersions,
        _folderId: folderId, // Internal use for file operations
      };
    } catch (error) {
      console.error('Error fetching skill details:', error);
      return null;
    }
  };

  // Legacy fetch for skills without version structure
  const fetchSkillDetailsLegacy = async (
    folderId: string,
    folderName: string,
    skillItems: any[],
  ): Promise<Skill | null> => {
    try {
      const fileEntries: SkillFileEntry[] = [];
      let readmeContent: string | null = null;
      let firstFileDate: string | null = null;

      // Recursively fetch all files
      const fetchFilesRecursive = async (
        parentId: string,
        basePath: string = '',
      ) => {
        const { data } = await fileManagerService.listFile({
          parent_id: parentId,
        });
        if (data.code !== 0) return;

        const files = data.data?.files || [];

        if (!firstFileDate && files.length > 0) {
          firstFileDate = files[0]?.create_date || files[0]?.update_date;
        }

        for (const f of files) {
          const path = basePath ? `${basePath}/${f.name}` : f.name;

          fileEntries.push({
            name: f.name,
            path: path,
            is_dir: f.type === 'folder',
            size: f.size || 0,
          });

          const lowerName = f.name.toLowerCase();
          if (
            lowerName === 'skill.md' ||
            lowerName === 'readme.md' ||
            lowerName === 'index.md'
          ) {
            if (!readmeContent) {
              readmeContent = await fetchFileContent(f.id);
            }
          }

          if (f.type === 'folder') {
            await fetchFilesRecursive(f.id, path);
          }
        }
      };

      // Process items from the skill folder
      for (const f of skillItems) {
        if (f.type === 'folder') {
          await fetchFilesRecursive(f.id, f.name);
        } else {
          fileEntries.push({
            name: f.name,
            path: f.name,
            is_dir: false,
            size: f.size || 0,
          });

          const lowerName = f.name.toLowerCase();
          if (
            lowerName === 'skill.md' ||
            lowerName === 'readme.md' ||
            lowerName === 'index.md'
          ) {
            if (!readmeContent) {
              readmeContent = await fetchFileContent(f.id);
            }
          }
        }
      }

      let metadata: SkillMetadata = {};
      let description = '';

      if (readmeContent) {
        const parsed = parseMetadata(readmeContent);
        metadata = parsed.metadata;
        description = metadata.description || parsed.body.slice(0, 200);
      }

      const createDate = firstFileDate || new Date().toISOString();

      // Priority: folderName (user-specified) > metadata.name (from SKILL.md)
      // This allows users to override the skill name from SKILL.md
      const skillName = folderName || metadata.name || 'unnamed-skill';

      return {
        id: skillName, // Use skill name as ID (consistent with search results)
        name: skillName,
        description,
        source_type: 'local',
        created_at: new Date(createDate).getTime(),
        updated_at: new Date(createDate).getTime(),
        files: fileEntries,
        metadata,
        _folderId: folderId, // Internal use for file operations
      };
    } catch (error) {
      console.error('Error fetching legacy skill details:', error);
      return null;
    }
  };

  // Ensure skills folder exists, returns folder ID
  const ensureSkillsFolder = useCallback(async (): Promise<string | null> => {
    try {
      // List root files to find skills folder
      const { data } = await fileManagerService.listFile({});

      if (data.code !== 0) return null;

      const rootId = data.data?.parent_folder?.id;
      const files = data.data?.files || [];

      // Check if skills folder exists
      const skillsFolder = files.find(
        (f: any) => f.name === SKILLS_FOLDER && f.type === 'folder',
      );

      if (skillsFolder) {
        return skillsFolder.id;
      }

      // Create skills folder
      const createRes = await fileManagerService.createFolder({
        name: SKILLS_FOLDER,
        type: 'folder',
        parent_id: rootId,
      });

      if (createRes.data.code === 0) {
        return createRes.data.data?.id || null;
      }

      return null;
    } catch (error) {
      console.error('Error ensuring skills folder:', error);
      return null;
    }
  }, []);

  const fetchSpaces = useCallback(async (): Promise<SkillSpace[]> => {
    try {
      const result = await skillSpaceService.listSpaces();
      return result.spaces.map((space) => ({
        id: space.id,
        name: space.name,
        create_time: space.create_time,
        folder_id: space.folder_id,
      }));
    } catch (error) {
      console.error('Error fetching skill hubs:', error);
      return [];
    }
  }, []);

  const ensureSkillSpaceFolder = useCallback(
    async (
      spaceName: string,
      createIfMissing = false,
    ): Promise<string | null> => {
      const skillsFolderId = await ensureSkillsFolder();
      if (!skillsFolderId) return null;

      const { data } = await fileManagerService.listFile({
        parent_id: skillsFolderId,
      });

      if (data.code !== 0) return null;

      const hubFolder = (data.data?.files || []).find(
        (f: any) => f.name === spaceName && f.type === 'folder',
      );
      if (hubFolder) return hubFolder.id;

      if (!createIfMissing) return null;

      const createRes = await fileManagerService.createFolder({
        name: spaceName,
        type: 'folder',
        parent_id: skillsFolderId,
      });

      if (createRes.data.code !== 0) return null;
      return createRes.data.data?.id || null;
    },
    [ensureSkillsFolder],
  );

  const createSpace = useCallback(
    async (spaceName: string): Promise<{ id: string; name: string } | null> => {
      try {
        const space = await skillSpaceService.createSpace({ name: spaceName });
        message.success(
          t('skills.spaceCreated') || 'Skill Space created successfully',
        );
        return space;
      } catch (error: any) {
        console.error('Error creating skill space:', error);
        message.error(error.message || t('skills.fetchError'));
        return null;
      }
    },
    [t],
  );

  // Delete a skill space
  const deleteSpace = useCallback(
    async (spaceId: string): Promise<boolean> => {
      try {
        await skillSpaceService.deleteSpace(spaceId);
        message.success(
          t('skills.spaceDeleted') || 'Skill Space deleted successfully',
        );
        return true;
      } catch (error: any) {
        console.error('Error deleting skill space:', error);
        message.error(error.message || t('skills.fetchError'));
        return false;
      }
    },
    [t],
  );

  // Update a skill space (rename)
  const updateSpace = useCallback(
    async (spaceId: string, spaceName: string): Promise<boolean> => {
      try {
        await skillSpaceService.updateSpace(spaceId, { name: spaceName });
        message.success(
          t('skills.spaceUpdated') || 'Skill Space renamed successfully',
        );
        return true;
      } catch (error: any) {
        console.error('Error updating skill space:', error);
        message.error(error.message || t('skills.fetchError'));
        return false;
      }
    },
    [t],
  );

  // Fetch skills from file system (fallback when search returns empty)
  const fetchSkillsFromFileSystem = useCallback(
    async (spaceName?: string): Promise<{ skills: Skill[]; total: number }> => {
      if (!spaceName) {
        return { skills: [], total: 0 };
      }
      try {
        const spaceFolderId = await ensureSkillSpaceFolder(spaceName, false);
        if (!spaceFolderId) {
          return { skills: [], total: 0 };
        }

        const { data } = await fileManagerService.listFile({
          parent_id: spaceFolderId,
        });

        const skillFolders =
          data.code === 0
            ? data.data?.files?.filter((f: any) => f.type === 'folder') || []
            : [];

        // Fetch details for each skill
        const skillsData: Skill[] = (
          await Promise.all(
            skillFolders.map(async (folder: any) => {
              const skill = await fetchSkillDetails(folder.id, folder.name);
              return skill;
            }),
          )
        ).filter(Boolean);

        return { skills: skillsData, total: skillsData.length };
      } catch (error) {
        console.error('Error fetching skills from file system:', error);
        return { skills: [], total: 0 };
      }
    },
    // oxlint-disable-next-line react/exhaustive-deps
    [ensureSkillSpaceFolder],
  );

  // Fetch skills using search API (supports pagination and sorting)
  // Falls back to file system if search returns empty (skills not indexed yet)
  const fetchSkills = useCallback(
    async (
      spaceName?: string,
      spaceId?: string,
      page = 1,
      pageSize = 50,
      sortBy = 'update_time',
      sortOrder: 'asc' | 'desc' = 'desc',
    ) => {
      if (!spaceName || !spaceId) {
        setSkills([]);
        return { skills: [], total: 0 };
      }
      setLoading(true);
      try {
        // Use search API with empty query to list all skills
        const response = await fetch('/api/v1/skills/search', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            Authorization: getAuthorization(),
          },
          body: JSON.stringify({
            space_id: spaceId,
            query: '', // Empty query = list all
            page,
            page_size: pageSize,
            sort_by: sortBy,
            sort_order: sortOrder,
          }),
        });

        if (!response.ok) {
          throw new Error('Failed to fetch skills');
        }

        const result = await response.json();
        if (result.code !== 0) {
          throw new Error(result.message || 'Failed to fetch skills');
        }

        const searchSkills = result.data?.skills || [];
        const total = result.data?.total || 0;

        // If search returned results, use them
        if (searchSkills.length > 0) {
          const skillsData: Skill[] = searchSkills.map((result: any) => {
            const timestamp = pickSkillTimestamp(result);
            const skillId = result.skill_id || result.name;

            return {
              id: skillId,
              name: result.name,
              description: result.description || '',
              source_type: 'search',
              created_at: timestamp,
              updated_at: timestamp,
              metadata: {
                tags: result.tags || [],
                version: result.version,
              },
              files: [],
              _folderId: result.folder_id,
            };
          });

          setSkills(skillsData);

          // Asynchronously load file counts for search results
          Promise.all(
            skillsData.map(async (skill) => {
              if (!skill._folderId) return skill;
              try {
                const detail = await fetchSkillDetails(
                  skill._folderId,
                  skill.name,
                );
                if (detail) {
                  return { ...skill, files: detail.files };
                }
              } catch {
                // Ignore errors for individual skill file loading
              }
              return skill;
            }),
          ).then((enrichedSkills) => {
            setSkills(enrichedSkills);
          });

          return { skills: skillsData, total };
        }

        // Search returned empty, fall back to file system
        // oxlint-disable-next-line no-console
        console.log(
          '[Skills] Search returned empty, falling back to file system',
        );
        const fsResult = await fetchSkillsFromFileSystem(spaceName);
        setSkills(fsResult.skills);
        return fsResult;
      } catch (error) {
        console.error('Error fetching skills:', error);
        // Fall back to file system on error
        const fsResult = await fetchSkillsFromFileSystem(spaceName);
        setSkills(fsResult.skills);
        return fsResult;
      } finally {
        setLoading(false);
      }
    },
    // oxlint-disable-next-line react/exhaustive-deps
    [t, fetchSkillsFromFileSystem],
  );

  // Upload a new skill with proper directory structure (with version support)
  const uploadSkill = useCallback(
    async (
      name: string,
      version: string,
      files: File[],
      spaceName?: string,
      spaceId?: string,
      embdId?: string,
    ): Promise<boolean> => {
      try {
        setLoading(true);
        if (!spaceName) throw new Error('Space name is required');

        // Use spaceName for file system operations, spaceId for indexing
        const normalizedSpaceName = spaceName.trim();
        const normalizedSpaceId = spaceId?.trim() || normalizedSpaceName;

        // Filter out ignored/junk files first
        const filteredFiles = filterUploadFiles(files);

        // Validate skill format
        const validation = await validateSkillFormatImpl(filteredFiles);
        if (!validation.valid) {
          const errorKey = `skills.validation.${validation.error}`;
          const errorMsg = t(errorKey) || t('skills.validation.invalid');
          message.error(errorMsg);
          return false;
        }

        // Get space folder ID (using space name for file system)
        const spaceFolderId = await ensureSkillSpaceFolder(
          normalizedSpaceName,
          true,
        );

        if (!spaceFolderId) throw new Error('Skills space not found');

        const skillNameNormalized = name.replace(/\s+/g, '-').toLowerCase();

        // Check if skill folder exists
        const { data: existingData } = await fileManagerService.listFile({
          parent_id: spaceFolderId,
        });

        let skillFolderId: string;

        if (existingData.code === 0) {
          const existingSkill = existingData.data?.files?.find(
            (f: any) => f.name === skillNameNormalized && f.type === 'folder',
          );

          if (existingSkill) {
            // Skill exists, check if version already exists
            const { data: versionData } = await fileManagerService.listFile({
              parent_id: existingSkill.id,
            });

            if (versionData.code === 0) {
              const existingVersion = versionData.data?.files?.find(
                (f: any) => f.name === version && f.type === 'folder',
              );

              if (existingVersion) {
                message.error(
                  t('skills.versionExists') || 'This version already exists',
                );
                return false;
              }
            }

            skillFolderId = existingSkill.id;
          } else {
            // Create skill folder
            const folderRes = await fileManagerService.createFolder({
              name: skillNameNormalized,
              type: 'folder',
              parent_id: spaceFolderId,
            });

            if (folderRes.data.code !== 0) {
              throw new Error('Failed to create skill folder');
            }

            skillFolderId = folderRes.data.data?.id;
          }
        } else {
          throw new Error('Failed to list skills folder');
        }

        if (!skillFolderId) throw new Error('Failed to get skill folder ID');

        // Create version folder
        const versionRes = await fileManagerService.createFolder({
          name: version,
          type: 'folder',
          parent_id: skillFolderId,
        });

        if (versionRes.data.code !== 0) {
          throw new Error('Failed to create version folder');
        }

        const versionFolderId = versionRes.data.data?.id;

        if (!versionFolderId)
          throw new Error('Failed to get version folder ID');

        // Upload files recursively to preserve directory structure
        const uploadFileWithStructure = async (
          file: File,
          parentId: string,
        ) => {
          const relativePath = (file as any).webkitRelativePath || file.name;
          const pathParts = relativePath.split('/');

          // If file is in root directory (no subdirectories)
          if (pathParts.length === 1) {
            const formData = new FormData();
            formData.append('parent_id', parentId);
            formData.append('file', file);
            await fileManagerService.uploadFile(formData);
            return;
          }

          // Navigate/create directory structure
          let currentParentId = parentId;
          for (let i = 0; i < pathParts.length - 1; i++) {
            const dirName = pathParts[i];

            // List current directory to check if subdirectory exists
            const { data: listData } = await fileManagerService.listFile({
              parent_id: currentParentId,
            });

            if (listData.code !== 0) {
              throw new Error(`Failed to list directory: ${dirName}`);
            }

            const existingDir = listData.data?.files?.find(
              (f: any) => f.name === dirName && f.type === 'folder',
            );

            if (existingDir) {
              currentParentId = existingDir.id;
            } else {
              // Create subdirectory
              const createRes = await fileManagerService.createFolder({
                name: dirName,
                type: 'folder',
                parent_id: currentParentId,
              });

              if (createRes.data.code !== 0) {
                throw new Error(`Failed to create directory: ${dirName}`);
              }

              currentParentId = createRes.data.data?.id;
            }
          }

          // Upload file to the final directory
          const formData = new FormData();
          formData.append('parent_id', currentParentId);
          formData.append('file', file);
          await fileManagerService.uploadFile(formData);
        };

        // Upload all files sequentially to avoid race conditions
        for (const file of filteredFiles) {
          await uploadFileWithStructure(file, versionFolderId);
        }

        // Build search index for the uploaded skill
        try {
          // Read all text files and build content
          let skillMetadata: SkillMetadata = {};
          let skillDescription = '';
          const fileContents: { path: string; content: string }[] = [];

          for (const file of filteredFiles) {
            const relativePath = (file as any).webkitRelativePath || file.name;
            if (!isTextFile(relativePath, file.type)) {
              continue;
            }

            const content = await file.text();
            fileContents.push({ path: relativePath, content });

            // Parse metadata from skill.md/readme.md/index.md
            const lowerName = file.name.toLowerCase();
            if (
              lowerName === 'skill.md' ||
              lowerName === 'readme.md' ||
              lowerName === 'index.md'
            ) {
              const parsed = parseMetadata(content);
              skillMetadata = parsed.metadata;
              skillDescription =
                skillMetadata.description || parsed.body.slice(0, 200);
            }
          }

          // Build concatenated content for indexing
          const concatenatedContent = fileContents
            .map((f) => `${f.path}\n===\n${f.content}`)
            .join('\n\n');

          // Index the skill with embd_id from config (if available)
          // Use user-specified name (skillNameNormalized) as skill ID and name
          // This ensures consistency between folder name, skill ID, and display name
          const indexResponse = await fetch('/api/v1/skills/index', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              Authorization: getAuthorization(),
            },
            body: JSON.stringify({
              space_id: normalizedSpaceId,
              embd_id: embdId,
              skills: [
                {
                  id: skillNameNormalized,
                  folder_id: skillFolderId,
                  name: skillNameNormalized,
                  description: skillDescription,
                  tags: skillMetadata.tags || [],
                  content: concatenatedContent,
                },
              ],
            }),
          });

          if (!indexResponse.ok) {
            console.warn(
              '[Skill Index] Failed to index skill:',
              await indexResponse.text(),
            );
          }
        } catch (indexError) {
          // Indexing failure should not block upload success
          console.warn('[Skill Index] Error indexing skill:', indexError);
        }

        message.success(t('skills.uploadSuccess'));
        await fetchSkills(normalizedSpaceName, normalizedSpaceId);
        return true;
      } catch (error) {
        console.error('Error uploading skill:', error);
        message.error(t('skills.uploadError'));
        return false;
      } finally {
        setLoading(false);
      }
    },
    [t, fetchSkills, ensureSkillSpaceFolder],
  );

  // Delete a skill
  const deleteSkill = useCallback(
    async (
      skillId: string,
      _skillName?: string,
      spaceId?: string,
      spaceName?: string,
      folderId?: string,
    ): Promise<boolean> => {
      try {
        if (!spaceId) throw new Error('Space ID is required');
        if (!spaceName) throw new Error('Space name is required');
        const normalizedSpaceId = spaceId.trim();
        const normalizedSpaceName = spaceName.trim();

        let targetFolderId: string | null = folderId || null;

        // If folderId not provided, try to find the skill in current skills state
        if (!targetFolderId) {
          const skillInState = skills.find((s) => s.id === skillId);
          if (skillInState && (skillInState as any)._folderId) {
            targetFolderId = (skillInState as any)._folderId;
          }
        }

        // Fallback: search in file system if not found
        if (!targetFolderId) {
          const spaceFolderId = await ensureSkillSpaceFolder(
            normalizedSpaceName,
            false,
          );
          if (spaceFolderId) {
            const { data: listData } = await fileManagerService.listFile({
              parent_id: spaceFolderId,
            });

            if (listData.code === 0) {
              const skillFolder = (listData.data?.files || []).find(
                (f: any) => f.type === 'folder' && f.name === skillId,
              );
              if (skillFolder) {
                targetFolderId = skillFolder.id;
              }
            }
          }
        }

        if (!targetFolderId) {
          throw new Error('Skill not found');
        }

        // Get versions by listing the skill folder
        const { data: versionData } = await fileManagerService.listFile({
          parent_id: targetFolderId,
        });

        let versionsToDelete: string[] = ['latest'];
        if (versionData.code === 0) {
          const versionFolders = (versionData.data?.files || []).filter(
            (f: any) => f.type === 'folder' && /^\d+\.\d+\.\d+/.test(f.name),
          );
          if (versionFolders.length > 0) {
            versionsToDelete = versionFolders.map((f: any) => f.name);
          }
        }

        // Delete search index for all versions
        // Backend uses skillName_version as doc_id (replacing '/' with '_')
        // We need to delete each version's index separately
        // oxlint-disable-next-line no-console
        console.log(
          `[deleteSkill] Starting index deletion for skillId: ${skillId}, spaceId: ${normalizedSpaceId}`,
        );
        // oxlint-disable-next-line no-console
        console.log(`[deleteSkill] versionsToDelete:`, versionsToDelete);

        for (const version of versionsToDelete) {
          const indexId =
            version === 'latest' ? skillId : `${skillId}/${version}`;
          try {
            // oxlint-disable-next-line no-console
            console.log(
              `[deleteSkill] Deleting index: ${indexId} for space: ${normalizedSpaceId}`,
            );
            await skillSpaceService.deleteSkillIndex(
              indexId,
              normalizedSpaceId,
            );
            // oxlint-disable-next-line no-console
            console.log(`[deleteSkill] Successfully deleted index: ${indexId}`);
          } catch (indexError: any) {
            // oxlint-disable-next-line no-console
            console.warn(
              `[deleteSkill] Error deleting skill index for ${indexId}:`,
              indexError?.message || indexError,
            );
          }
        }

        // If we couldn't determine versions from filesystem, try common version formats
        if (versionsToDelete.length === 1 && versionsToDelete[0] === 'latest') {
          // Try to delete the skill with version suffixes
          const commonVersions = ['1.0.0', '0.1.0', '0.0.1', 'latest'];
          for (const version of commonVersions) {
            const indexId = `${skillId}/${version}`;
            try {
              // oxlint-disable-next-line no-console
              console.log(
                `[deleteSkill] Trying to delete index with version: ${indexId}`,
              );
              await skillSpaceService.deleteSkillIndex(
                indexId,
                normalizedSpaceId,
              );
              // oxlint-disable-next-line no-console
              console.log(
                `[deleteSkill] Successfully deleted index: ${indexId}`,
              );
            } catch {
              // Ignore errors for versions that don't exist
            }
          }
        }

        const { data } = await fileManagerService.removeFile({
          ids: [targetFolderId],
        });

        if (data.code !== 0) throw new Error('Failed to delete skill');

        message.success(t('skills.deleteSuccess'));
        // Refresh skills list using space name and space id
        await fetchSkills(normalizedSpaceName, normalizedSpaceId);
        return true;
      } catch (error) {
        console.error('Error deleting skill:', error);
        message.error(t('skills.deleteError'));
        return false;
      }
    },
    // oxlint-disable-next-line react/exhaustive-deps
    [t, fetchSkills, ensureSkillSpaceFolder, skills],
  );

  // Recursively find file by path in folder structure
  // For versioned skills, automatically finds the version folder first
  const findFileByPath = async (
    folderId: string,
    targetPath: string,
    version?: string,
  ): Promise<any | null> => {
    let currentFolderId = folderId;

    // If version is provided, first find the version folder
    if (version) {
      const { data } = await fileManagerService.listFile({
        parent_id: currentFolderId,
      });
      if (data.code !== 0) return null;

      const files = data.data?.files || [];
      const versionFolder = files.find(
        (f: any) => f.name === version && f.type === 'folder',
      );

      if (!versionFolder) return null;
      currentFolderId = versionFolder.id;
    } else {
      // No version specified, try to find the latest version folder
      const { data } = await fileManagerService.listFile({
        parent_id: currentFolderId,
      });
      if (data.code !== 0) return null;

      const files = data.data?.files || [];
      const versionFolders = files.filter(
        (f: any) => f.type === 'folder' && /^\d+\.\d+\.\d+/.test(f.name),
      );

      if (versionFolders.length > 0) {
        // Sort by version number (descending) to get the latest
        const sortedVersions = versionFolders.sort((a: any, b: any) => {
          const va = a.name.split('.').map(Number);
          const vb = b.name.split('.').map(Number);
          for (let i = 0; i < Math.max(va.length, vb.length); i++) {
            const na = va[i] || 0;
            const nb = vb[i] || 0;
            if (na !== nb) return nb - na; // Descending order
          }
          return 0;
        });
        currentFolderId = sortedVersions[0].id;
      }
      // If no version folders found, stay at current level (legacy structure)
    }

    // Now find the file in the version folder (or original folder if no version)
    const parts = targetPath.split('/');

    for (let i = 0; i < parts.length; i++) {
      const { data } = await fileManagerService.listFile({
        parent_id: currentFolderId,
      });
      if (data.code !== 0) return null;

      const files = data.data?.files || [];
      const part = parts[i];

      // Check if this is the last part (the file)
      if (i === parts.length - 1) {
        const file = files.find((f: any) => f.name === part);
        return file || null;
      }

      // This is a folder, find it and continue
      const subFolder = files.find(
        (f: any) => f.name === part && f.type === 'folder',
      );
      if (!subFolder) return null;
      currentFolderId = subFolder.id;
    }

    return null;
  };

  // Get file content for a skill
  // Automatically handles versioned skills by checking skill.metadata.version
  // Can be called with an optional skill object (for search results not in skills state)
  const getSkillFileContent = useCallback(
    async (
      skillId: string,
      filePath: string,
      version?: string,
      skillObj?: Skill,
    ): Promise<string | null> => {
      try {
        // Find the skill to get its folder ID
        // Use provided skill object if available (for search results), otherwise look up in skills state
        const skill = skillObj || skills.find((s) => s.id === skillId);
        if (!skill) return null;

        // Use internal _folderId for file operations
        const folderId = (skill as any)._folderId;
        if (!folderId) return null;

        // If version is not provided, try to find it from the skill or auto-discover
        let targetVersion = version;
        if (!targetVersion) {
          targetVersion = skill?.metadata?.version;
        }

        // Handle both file name and file path
        const file = await findFileByPath(folderId, filePath, targetVersion);
        if (!file) return null;
        return await fetchFileContent(file.id);
      } catch (error) {
        console.error('Error getting skill file content:', error);
        return null;
      }
    },
    [skills],
  );

  // Fetch files for a specific version of a skill
  // Can be called with an optional skill object (for search results not in skills state)
  const getSkillVersionFiles = useCallback(
    async (
      skillId: string,
      version: string,
      skillObj?: Skill,
    ): Promise<SkillFileEntry[]> => {
      try {
        // Find the skill to get its folder ID
        // Use provided skill object if available (for search results), otherwise look up in skills state
        const skill = skillObj || skills.find((s) => s.id === skillId);
        if (!skill) return [];

        // Use internal _folderId for file operations
        const folderId = (skill as any)._folderId;
        if (!folderId) return [];

        // First, list the skill folder to find the version folder
        const { data: skillFolderData } = await fileManagerService.listFile({
          parent_id: folderId,
        });

        if (skillFolderData.code !== 0) return [];

        const skillItems = skillFolderData.data?.files || [];

        // If version is not provided, find the latest version folder
        let targetVersion = version;
        if (!targetVersion) {
          // Find all version folders (matching semver pattern x.y.z)
          const versionFolders = skillItems.filter(
            (f: any) => f.type === 'folder' && /^\d+\.\d+\.\d+/.test(f.name),
          );
          if (versionFolders.length === 0) return [];

          // Sort by version number (descending) to get the latest
          const sortedVersions = versionFolders.sort((a: any, b: any) => {
            const va = a.name.split('.').map(Number);
            const vb = b.name.split('.').map(Number);
            for (let i = 0; i < Math.max(va.length, vb.length); i++) {
              const na = va[i] || 0;
              const nb = vb[i] || 0;
              if (na !== nb) return nb - na; // Descending order
            }
            return 0;
          });
          targetVersion = sortedVersions[0].name;
        }

        const versionFolder = skillItems.find(
          (f: any) => f.name === targetVersion && f.type === 'folder',
        );

        if (!versionFolder) return [];

        const fileEntries: SkillFileEntry[] = [];

        // Recursively fetch all files in the version folder
        const fetchFilesRecursive = async (
          parentId: string,
          basePath: string = '',
        ) => {
          const { data } = await fileManagerService.listFile({
            parent_id: parentId,
          });
          if (data.code !== 0) return;

          const files = data.data?.files || [];

          for (const f of files) {
            const path = basePath ? `${basePath}/${f.name}` : f.name;

            fileEntries.push({
              name: f.name,
              path: path,
              is_dir: f.type === 'folder',
              size: f.size || 0,
            });

            if (f.type === 'folder') {
              await fetchFilesRecursive(f.id, path);
            }
          }
        };

        await fetchFilesRecursive(versionFolder.id);
        return fileEntries;
      } catch (error) {
        console.error('Error fetching skill version files:', error);
        return [];
      }
    },
    [skills],
  );

  // Filter skills by search query
  const filteredSkills = useMemo(
    () =>
      skills.filter(
        (skill) =>
          skill.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
          skill.description
            ?.toLowerCase()
            .includes(searchQuery.toLowerCase()) ||
          skill.metadata?.tags?.some((tag) =>
            tag.toLowerCase().includes(searchQuery.toLowerCase()),
          ),
      ),
    [skills, searchQuery],
  );

  // Fetch skills on mount
  useEffect(() => {
    fetchSkills();
  }, [fetchSkills]);

  // Get skill details by folder ID and name (for loading versions)
  const getSkillDetails = useCallback(
    async (folderId: string, folderName: string): Promise<Skill | null> => {
      return await fetchSkillDetails(folderId, folderName);
    },
    // oxlint-disable-next-line react/exhaustive-deps
    [],
  );

  return {
    skills,
    filteredSkills,
    loading,
    searchQuery,
    setSearchQuery,
    fetchSpaces,
    createSpace,
    deleteSpace,
    updateSpace,
    fetchSkills,
    uploadSkill,
    deleteSkill,
    getSkillFileContent,
    getSkillVersionFiles,
    getSkillDetails,
  };
};

// Query key for skill search config
const skillSearchConfigQueryKey = (spaceId: string, embdId?: string) =>
  ['skillSearchConfig', spaceId, embdId].filter(Boolean);

// Skill Search Config Hook
export const useSkillSearchConfig = (spaceId?: string) => {
  const { t } = useTranslation();
  const [saving, setSaving] = useState(false);

  // Use TanStack Query to fetch and cache config
  const {
    data: config,
    refetch: refetchConfig,
    isLoading: configLoading,
  } = useQuery({
    queryKey: skillSearchConfigQueryKey(spaceId || '', undefined),
    queryFn: async () => {
      if (!spaceId) return null;
      const data = await skillSpaceService.getConfig(spaceId);
      return data as SkillSearchConfig | null;
    },
    enabled: !!spaceId,
    staleTime: 5 * 60 * 1000, // 5 minutes
  });

  // Fetch config with optional embdId (refreshes the query)
  const fetchConfig = useCallback(
    async (_embdId?: string, currentSpaceId?: string) => {
      const targetSpaceId = currentSpaceId || spaceId;
      if (!targetSpaceId) return null;
      const { data } = await refetchConfig();
      return data as SkillSearchConfig | null;
    },
    [spaceId, refetchConfig],
  );

  // Save config
  const saveConfig = useCallback(
    async (configData: SkillSearchConfig): Promise<boolean> => {
      try {
        setSaving(true);
        if (!spaceId) throw new Error('Space ID is required');
        await skillSpaceService.updateConfig({
          ...configData,
          space_id: spaceId,
        });
        // Refetch config after save
        await refetchConfig();
        message.success(t('skillSearch.saveSuccess'));
        return true;
      } catch (error: any) {
        console.error('Error saving skill search config:', error);
        message.error(error.message || t('skillSearch.saveError'));
        return false;
      } finally {
        setSaving(false);
      }
    },
    [t, spaceId, refetchConfig],
  );

  // Reindex all skills
  const reindex = useCallback(
    async (embdId?: string): Promise<boolean> => {
      try {
        setSaving(true);
        if (!spaceId) throw new Error('Space ID is required');
        await skillSpaceService.reindex({
          skills: [],
          space_id: spaceId,
          embd_id: embdId,
        });
        message.success(t('skillSearch.reindexSuccess'));
        return true;
      } catch (error: any) {
        console.error('Error reindexing skills:', error);
        message.error(error.message || t('skillSearch.reindexError'));
        return false;
      } finally {
        setSaving(false);
      }
    },
    [t, spaceId],
  );

  // Initialize index
  const initializeIndex = useCallback(async (): Promise<boolean> => {
    try {
      if (!spaceId) throw new Error('Space ID is required');
      // Initialize index is now handled automatically when creating index
      // Call index API directly to ensure index exists
      // embd_id will be fetched from skill search config by backend
      await skillSpaceService.indexSkills({ skills: [], space_id: spaceId });
      return true;
    } catch (error) {
      console.error('Error initializing skill search index:', error);
      return false;
    }
  }, [spaceId]);

  // Search skills
  const searchSkills = useCallback(
    async (query: string, page = 1, pageSize = 10) => {
      try {
        if (!spaceId) return { skills: [], total: 0 };
        const data = await skillSpaceService.search({
          space_id: spaceId,
          query,
          page,
          page_size: pageSize,
        });
        // Transform backend results to Skill[] format
        // Use folder_id if available (for file operations), otherwise skill_id
        const skills: Skill[] = (data.skills || []).map((result: any) => {
          // Prefer backend timestamp to avoid all cards showing "just now".
          // Fallback to now only when backend doesn't provide time fields.
          const timestamp = pickSkillTimestamp(result);

          // skill_id from backend is now the skill name (without version suffix)
          const skillId = result.skill_id || result.name;

          return {
            id: skillId, // Use skill name as ID (consistent with list view)
            name: result.name,
            description: result.description,
            source_type: 'search',
            created_at: timestamp,
            updated_at: timestamp,
            metadata: {
              tags: result.tags || [],
              score: result.score,
              bm25_score: result.bm25_score,
              vector_score: result.vector_score,
            },
            files: [],
            _folderId: result.folder_id, // Store folder_id for file operations if needed
          };
        });
        return {
          skills,
          total: data.total || 0,
        };
      } catch (error) {
        console.error('Error searching skills:', error);
        return { skills: [], total: 0 };
      }
    },
    [spaceId],
  );

  // Get index status
  const getIndexStatus = useCallback(async () => {
    try {
      const response = await fetch('/api/v1/skills/status', {
        headers: {
          Authorization: getAuthorization(),
        },
      });
      const data = await response.json();
      if (data.code === 0) {
        return data.data;
      }
      return null;
    } catch (error) {
      console.error('Error getting skill index status:', error);
      return null;
    }
  }, []);

  return {
    config,
    configLoading,
    saving,
    fetchConfig,
    saveConfig,
    reindex,
    initializeIndex,
    searchSkills,
    getIndexStatus,
  };
};
