/**
 * @license
 * Copyright 2025 HopCode Team
 * SPDX-License-Identifier: Apache-2.0
 */
import { useState, useEffect, useMemo } from 'react';
import { AsyncFzf } from 'fzf';
import { createDebugLogger } from '@hoptrendy/hopcode-core';
import { CommandKind, } from '../commands/types.js';
import { getCommandDisplayName, getCommandSourceBadge, } from '../../services/commandMetadata.js';
const debugLogger = createDebugLogger('SLASH_COMPLETION');
// Utility function to safely handle errors without information disclosure
function logErrorSafely(error, context) {
    if (error instanceof Error) {
        // Log full error details securely for debugging
        debugLogger.error(`[${context}]`, error);
    }
    else {
        debugLogger.error(`[${context}] Non-error thrown:`, error);
    }
}
// Shared utility function for command matching logic
function matchesCommand(cmd, query) {
    return (cmd.name.toLowerCase() === query.toLowerCase() ||
        cmd.altNames?.some((alt) => alt.toLowerCase() === query.toLowerCase()) ||
        false);
}
function useCommandParser(query, slashCommands) {
    return useMemo(() => {
        if (!query) {
            return {
                hasTrailingSpace: false,
                commandPathParts: [],
                partial: '',
                currentLevel: slashCommands,
                leafCommand: null,
                exactMatchAsParent: undefined,
                isArgumentCompletion: false,
            };
        }
        const fullPath = query.substring(1) || '';
        const hasTrailingSpace = !!query.endsWith(' ');
        const rawParts = fullPath.split(/\s+/).filter((p) => p);
        let commandPathParts = rawParts;
        let partial = '';
        if (!hasTrailingSpace && rawParts.length > 0) {
            partial = rawParts[rawParts.length - 1];
            commandPathParts = rawParts.slice(0, -1);
        }
        let currentLevel = slashCommands;
        let leafCommand = null;
        for (const part of commandPathParts) {
            if (!currentLevel) {
                leafCommand = null;
                currentLevel = [];
                break;
            }
            const found = currentLevel.find((cmd) => matchesCommand(cmd, part));
            if (found) {
                leafCommand = found;
                currentLevel = found.subCommands;
                if (found.kind === CommandKind.MCP_PROMPT) {
                    break;
                }
            }
            else {
                leafCommand = null;
                currentLevel = [];
                break;
            }
        }
        let exactMatchAsParent;
        if (!hasTrailingSpace && currentLevel) {
            exactMatchAsParent = currentLevel.find((cmd) => matchesCommand(cmd, partial) && cmd.subCommands);
            if (exactMatchAsParent) {
                leafCommand = exactMatchAsParent;
                currentLevel = exactMatchAsParent.subCommands;
                partial = '';
            }
        }
        const depth = commandPathParts.length;
        const isArgumentCompletion = !!(leafCommand?.completion &&
            (hasTrailingSpace ||
                (rawParts.length > depth && depth > 0 && partial !== '')));
        return {
            hasTrailingSpace,
            commandPathParts,
            partial,
            currentLevel,
            leafCommand,
            exactMatchAsParent,
            isArgumentCompletion,
        };
    }, [query, slashCommands]);
}
var CommandMatchStrength;
(function (CommandMatchStrength) {
    CommandMatchStrength[CommandMatchStrength["FUZZY"] = 0] = "FUZZY";
    CommandMatchStrength[CommandMatchStrength["SEGMENT_PREFIX"] = 1] = "SEGMENT_PREFIX";
    CommandMatchStrength[CommandMatchStrength["PREFIX"] = 2] = "PREFIX";
    CommandMatchStrength[CommandMatchStrength["EXACT"] = 3] = "EXACT";
})(CommandMatchStrength || (CommandMatchStrength = {}));
const RECENT_DECAY_MS = 10 * 60 * 1000;
function getCompletionPriority(command) {
    return command.completionPriority ?? 0;
}
function isSegmentBoundary(value, start) {
    if (start <= 0) {
        return false;
    }
    return ['-', '_', '/', ' '].includes(value[start - 1] ?? '');
}
function getCommandMatchStrength(matchedValue, query, start) {
    const normalizedValue = matchedValue.toLowerCase();
    const normalizedQuery = query.toLowerCase();
    if (normalizedValue === normalizedQuery) {
        return CommandMatchStrength.EXACT;
    }
    if (normalizedValue.startsWith(normalizedQuery)) {
        return CommandMatchStrength.PREFIX;
    }
    if (start > 0 &&
        normalizedValue.slice(start).startsWith(normalizedQuery) &&
        isSegmentBoundary(normalizedValue, start)) {
        return CommandMatchStrength.SEGMENT_PREFIX;
    }
    return CommandMatchStrength.FUZZY;
}
function compareRankedCommandMatches(left, right) {
    return (right.matchStrength - left.matchStrength ||
        right.completionPriority - left.completionPriority ||
        right.recentScore - left.recentScore ||
        right.score - left.score ||
        left.start - right.start ||
        left.itemLength - right.itemLength ||
        left.originalIndex - right.originalIndex);
}
function getRecentScore(command, recentCommands, now = Date.now()) {
    const recent = recentCommands?.get(command.name);
    if (!recent) {
        return 0;
    }
    const ageMs = Math.max(0, now - recent.usedAt);
    return recent.count * 10 + 10 * Math.max(0, 1 - ageMs / RECENT_DECAY_MS);
}
function getMatchedAlias(command, matchedValue) {
    return command.altNames?.find((altName) => altName.toLowerCase() === matchedValue.toLowerCase());
}
function createRankedCommandMatch(command, matchedValue, query, result, originalIndex, recentCommands) {
    return {
        command,
        matchStrength: getCommandMatchStrength(matchedValue, query, result.start),
        completionPriority: getCompletionPriority(command),
        recentScore: getRecentScore(command, recentCommands),
        score: result.score,
        start: result.start,
        itemLength: matchedValue.length,
        originalIndex,
        matchedAlias: getMatchedAlias(command, matchedValue),
    };
}
function toCommandSuggestion(command, matchedAlias, includeAliases = false) {
    return {
        label: getCommandDisplayName(command, { matchedAlias, includeAliases }),
        value: command.name,
        description: command.description,
        commandKind: command.kind,
        source: command.source,
        sourceLabel: command.sourceLabel,
        sourceBadge: getCommandSourceBadge(command) ?? undefined,
        argumentHint: command.argumentHint,
        matchedAlias,
        supportedModes: command.supportedModes,
        modelInvocable: command.modelInvocable,
    };
}
function useCommandSuggestions(parserResult, commandContext, getFzfForCommands, getPrefixSuggestions, recentCommands) {
    const [suggestions, setSuggestions] = useState([]);
    const [isLoading, setIsLoading] = useState(false);
    useEffect(() => {
        const abortController = new AbortController();
        const { signal } = abortController;
        const { isArgumentCompletion, leafCommand, commandPathParts, partial, currentLevel, } = parserResult;
        if (isArgumentCompletion) {
            const fetchAndSetSuggestions = async () => {
                if (signal.aborted)
                    return;
                // Safety check: ensure leafCommand and completion exist
                if (!leafCommand?.completion) {
                    debugLogger.warn('Attempted argument completion without completion function');
                    return;
                }
                setIsLoading(true);
                try {
                    const rawParts = [...commandPathParts];
                    if (partial)
                        rawParts.push(partial);
                    const depth = commandPathParts.length;
                    const argString = rawParts.slice(depth).join(' ');
                    const results = (await leafCommand.completion({
                        ...commandContext,
                        invocation: {
                            raw: `/${rawParts.join(' ')}`,
                            name: leafCommand.name,
                            args: argString,
                        },
                    }, argString)) || [];
                    if (!signal.aborted) {
                        const finalSuggestions = results
                            .map((item) => toSuggestion(item))
                            .filter((suggestion) => !!suggestion);
                        setSuggestions(finalSuggestions);
                        setIsLoading(false);
                    }
                }
                catch (error) {
                    if (!signal.aborted) {
                        logErrorSafely(error, 'Argument completion');
                        setSuggestions([]);
                        setIsLoading(false);
                    }
                }
            };
            fetchAndSetSuggestions();
            return () => abortController.abort();
        }
        const commandsToSearch = currentLevel || [];
        if (commandsToSearch.length > 0) {
            const performFuzzySearch = async () => {
                if (signal.aborted)
                    return;
                let rankedSuggestions = [];
                if (partial === '') {
                    // If no partial query, recently used commands should be the most prominent.
                    rankedSuggestions = commandsToSearch
                        .flatMap((cmd, index) => {
                        if (!cmd.description || cmd.hidden) {
                            return [];
                        }
                        return [
                            createRankedCommandMatch(cmd, cmd.name, partial, { score: 0, start: 0 }, index, recentCommands),
                        ];
                    })
                        .sort((left, right) => {
                        const recentDifference = right.recentScore - left.recentScore;
                        if (recentDifference !== 0) {
                            return recentDifference;
                        }
                        return compareRankedCommandMatches(left, right);
                    });
                }
                else {
                    // Use fuzzy search for non-empty partial queries with fallback
                    const fzfInstance = getFzfForCommands(commandsToSearch);
                    if (fzfInstance) {
                        try {
                            const fzfResults = await fzfInstance.fzf.find(partial);
                            if (signal.aborted)
                                return;
                            const commandOrder = new Map();
                            commandsToSearch.forEach((cmd, index) => {
                                commandOrder.set(cmd, index);
                            });
                            const rankedMatches = new Map();
                            fzfResults.forEach((result) => {
                                const cmd = fzfInstance.commandMap.get(result.item);
                                const originalIndex = cmd ? commandOrder.get(cmd) : undefined;
                                if (cmd && cmd.description && originalIndex !== undefined) {
                                    const rankedMatch = createRankedCommandMatch(cmd, result.item, partial, result, originalIndex, recentCommands);
                                    const existingRank = rankedMatches.get(cmd);
                                    if (!existingRank ||
                                        compareRankedCommandMatches(rankedMatch, existingRank) < 0) {
                                        rankedMatches.set(cmd, rankedMatch);
                                    }
                                }
                            });
                            rankedSuggestions = Array.from(rankedMatches.values()).sort(compareRankedCommandMatches);
                        }
                        catch (error) {
                            logErrorSafely(error, 'Fuzzy search - falling back to prefix matching');
                            // Fallback to prefix-based filtering
                            rankedSuggestions = getPrefixSuggestions(commandsToSearch, partial);
                        }
                    }
                    else {
                        // Fallback to prefix-based filtering when fzf instance creation fails
                        rankedSuggestions = getPrefixSuggestions(commandsToSearch, partial);
                    }
                }
                if (!signal.aborted) {
                    const finalSuggestions = rankedSuggestions.map((match) => toCommandSuggestion(match.command, match.matchedAlias, partial === ''));
                    setSuggestions(finalSuggestions);
                }
            };
            performFuzzySearch().catch((error) => {
                logErrorSafely(error, 'Unexpected fuzzy search error');
                if (!signal.aborted) {
                    // Ultimate fallback: show no suggestions rather than confusing the user
                    // with all available commands when their query clearly doesn't match anything
                    setSuggestions([]);
                }
            });
            return () => abortController.abort();
        }
        setSuggestions([]);
        return () => abortController.abort();
    }, [
        parserResult,
        commandContext,
        getFzfForCommands,
        getPrefixSuggestions,
        recentCommands,
    ]);
    return { suggestions, isLoading };
}
function toSuggestion(item) {
    if (typeof item === 'string') {
        return { label: item, value: item };
    }
    if (!item.value) {
        return null;
    }
    return {
        label: item.label ?? item.value,
        value: item.value,
        description: item.description,
    };
}
function useCompletionPositions(query, parserResult) {
    return useMemo(() => {
        if (!query) {
            return { start: -1, end: -1 };
        }
        const { hasTrailingSpace, partial, exactMatchAsParent } = parserResult;
        // Set completion start/end positions
        if (hasTrailingSpace || exactMatchAsParent) {
            return { start: query.length, end: query.length };
        }
        else if (partial) {
            if (parserResult.isArgumentCompletion) {
                const commandSoFar = `/${parserResult.commandPathParts.join(' ')}`;
                const argStartIndex = commandSoFar.length +
                    (parserResult.commandPathParts.length > 0 ? 1 : 0);
                return { start: argStartIndex, end: query.length };
            }
            else {
                return { start: query.length - partial.length, end: query.length };
            }
        }
        else {
            return { start: 1, end: query.length };
        }
    }, [query, parserResult]);
}
function usePerfectMatch(parserResult) {
    return useMemo(() => {
        const { hasTrailingSpace, partial, leafCommand, currentLevel } = parserResult;
        if (hasTrailingSpace) {
            return { isPerfectMatch: false };
        }
        if (leafCommand && partial === '' && leafCommand.action) {
            return { isPerfectMatch: true };
        }
        if (currentLevel) {
            const perfectMatch = currentLevel.find((cmd) => matchesCommand(cmd, partial) && cmd.action);
            if (perfectMatch) {
                return { isPerfectMatch: true };
            }
        }
        return { isPerfectMatch: false };
    }, [parserResult]);
}
export function useSlashCompletion(props) {
    const { enabled, query, slashCommands, commandContext, recentCommands, setSuggestions, setIsLoadingSuggestions, setIsPerfectMatch, } = props;
    const [completionStart, setCompletionStart] = useState(-1);
    const [completionEnd, setCompletionEnd] = useState(-1);
    // Simplified cache for AsyncFzf instances - WeakMap handles automatic cleanup
    const fzfInstanceCache = useMemo(() => new WeakMap(), []);
    // Helper function to create or retrieve cached AsyncFzf instance for a command level
    const getFzfForCommands = useMemo(() => (commands) => {
        if (!commands || commands.length === 0) {
            return null;
        }
        // Check if we already have a cached instance
        const cached = fzfInstanceCache.get(commands);
        if (cached) {
            return cached;
        }
        // Create new fzf instance
        const commandItems = [];
        const commandMap = new Map();
        commands.forEach((cmd) => {
            if (cmd.description && !cmd.hidden) {
                commandItems.push(cmd.name);
                commandMap.set(cmd.name, cmd);
                if (cmd.altNames) {
                    cmd.altNames.forEach((alt) => {
                        commandItems.push(alt);
                        commandMap.set(alt, cmd);
                    });
                }
            }
        });
        if (commandItems.length === 0) {
            return null;
        }
        try {
            const instance = {
                fzf: new AsyncFzf(commandItems, {
                    fuzzy: 'v2',
                    casing: 'case-insensitive', // Explicitly enforce case-insensitivity
                }),
                commandMap,
            };
            // Cache the instance - WeakMap will handle automatic cleanup
            fzfInstanceCache.set(commands, instance);
            return instance;
        }
        catch (error) {
            logErrorSafely(error, 'FZF instance creation');
            return null;
        }
    }, [fzfInstanceCache]);
    // Memoized helper function for prefix-based filtering to improve performance
    const getPrefixSuggestions = useMemo(() => (commands, partial) => {
        const rankedMatches = commands.flatMap((cmd, index) => {
            if (!cmd.description || cmd.hidden) {
                return [];
            }
            const matchedValues = [cmd.name, ...(cmd.altNames ?? [])].filter((value) => value.toLowerCase().startsWith(partial.toLowerCase()));
            if (matchedValues.length === 0) {
                return [];
            }
            const bestMatch = matchedValues
                .map((matchedValue) => createRankedCommandMatch(cmd, matchedValue, partial, {
                score: matchedValue.toLowerCase() === partial.toLowerCase()
                    ? 100
                    : 80,
                start: 0,
            }, index, recentCommands))
                .sort(compareRankedCommandMatches)[0];
            return bestMatch ? [bestMatch] : [];
        });
        return rankedMatches.sort(compareRankedCommandMatches);
    }, [recentCommands]);
    // Use extracted hooks for better separation of concerns
    const parserResult = useCommandParser(query, slashCommands);
    const { suggestions: hookSuggestions, isLoading } = useCommandSuggestions(parserResult, commandContext, getFzfForCommands, getPrefixSuggestions, recentCommands);
    const { start: calculatedStart, end: calculatedEnd } = useCompletionPositions(query, parserResult);
    const { isPerfectMatch } = usePerfectMatch(parserResult);
    // Clear internal state when disabled
    useEffect(() => {
        if (!enabled) {
            setSuggestions([]);
            setIsLoadingSuggestions(false);
            setIsPerfectMatch(false);
            setCompletionStart(-1);
            setCompletionEnd(-1);
        }
    }, [enabled, setSuggestions, setIsLoadingSuggestions, setIsPerfectMatch]);
    // Update external state only when enabled
    useEffect(() => {
        if (!enabled || query === null) {
            return;
        }
        setSuggestions(hookSuggestions);
        setIsLoadingSuggestions(isLoading);
        setIsPerfectMatch(isPerfectMatch);
        setCompletionStart(calculatedStart);
        setCompletionEnd(calculatedEnd);
    }, [
        enabled,
        query,
        hookSuggestions,
        isLoading,
        isPerfectMatch,
        calculatedStart,
        calculatedEnd,
        setSuggestions,
        setIsLoadingSuggestions,
        setIsPerfectMatch,
    ]);
    return {
        completionStart,
        completionEnd,
    };
}
//# sourceMappingURL=useSlashCompletion.js.map