/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import type React from 'react';
import { useCallback, useMemo, useEffect, useState } from 'react';
import type { Suggestion } from '../components/SuggestionsDisplay.js';
import type { CommandContext, SlashCommand } from '../commands/types.js';
import type { TextBuffer } from '../components/shared/text-buffer.js';
import { logicalPosToOffset } from '../components/shared/text-buffer.js';
import { toCodePoints } from '../utils/textUtils.js';
import { isSlashCommand } from '../utils/commandUtils.js';
import { useAtCompletion } from './useAtCompletion.js';
import { useSlashCompletion } from './useSlashCompletion.js';
import { useShellCompletion } from './useShellCompletion.js';
import {
  usePromptCompletion,
  PROMPT_COMPLETION_MIN_LENGTH,
  type PromptCompletion,
} from './usePromptCompletion.js';
import type { Config } from '@google/gemini-cli-core';
import { useCompletion } from './useCompletion.js';

export enum CompletionMode {
  IDLE = 'IDLE',
  AT = 'AT',
  SLASH = 'SLASH',
  PROMPT = 'PROMPT',
  SHELL = 'SHELL',
}

export interface UseCommandCompletionReturn {
  suggestions: Suggestion[];
  activeSuggestionIndex: number;
  visibleStartIndex: number;
  showSuggestions: boolean;
  isLoadingSuggestions: boolean;
  isPerfectMatch: boolean;
  forceShowShellSuggestions: boolean;
  setForceShowShellSuggestions: (value: boolean) => void;
  isShellSuggestionsVisible: boolean;
  setActiveSuggestionIndex: React.Dispatch<React.SetStateAction<number>>;
  resetCompletionState: () => void;
  navigateUp: () => void;
  navigateDown: () => void;
  handleAutocomplete: (indexToUse: number) => void;
  promptCompletion: PromptCompletion;
  getCommandFromSuggestion: (
    suggestion: Suggestion,
  ) => SlashCommand | undefined;
  slashCompletionRange: {
    completionStart: number;
    completionEnd: number;
    getCommandFromSuggestion: (
      suggestion: Suggestion,
    ) => SlashCommand | undefined;
    isArgumentCompletion: boolean;
    leafCommand: SlashCommand | null;
  };
  getCompletedText: (suggestion: Suggestion) => string | null;
  completionMode: CompletionMode;
}

export interface UseCommandCompletionOptions {
  buffer: TextBuffer;
  cwd: string;
  slashCommands: readonly SlashCommand[];
  commandContext: CommandContext;
  reverseSearchActive?: boolean;
  shellModeActive: boolean;
  config?: Config;
  active: boolean;
}

export function useCommandCompletion({
  buffer,
  cwd,
  slashCommands,
  commandContext,
  reverseSearchActive = false,
  shellModeActive,
  config,
  active,
}: UseCommandCompletionOptions): UseCommandCompletionReturn {
  const [forceShowShellSuggestions, setForceShowShellSuggestions] =
    useState(false);

  const {
    suggestions,
    activeSuggestionIndex,
    visibleStartIndex,
    isLoadingSuggestions,
    isPerfectMatch,

    setSuggestions,
    setActiveSuggestionIndex,
    setIsLoadingSuggestions,
    setIsPerfectMatch,
    setVisibleStartIndex,

    resetCompletionState: baseResetCompletionState,
    navigateUp,
    navigateDown,
  } = useCompletion();

  const resetCompletionState = useCallback(() => {
    baseResetCompletionState();
    setForceShowShellSuggestions(false);
  }, [baseResetCompletionState]);

  const cursorRow = buffer.cursor[0];
  const cursorCol = buffer.cursor[1];

  const {
    completionMode,
    query: memoQuery,
    completionStart,
    completionEnd,
  } = useMemo(() => {
    const currentLine = buffer.lines[cursorRow] || '';
    const codePoints = toCodePoints(currentLine);

    if (shellModeActive) {
      return {
        completionMode:
          currentLine.trim().length === 0
            ? CompletionMode.IDLE
            : CompletionMode.SHELL,
        query: '',
        completionStart: -1,
        completionEnd: -1,
      };
    }

    // FIRST: Check for @ completion (scan backwards from cursor)
    // This must happen before slash command check so that `/cmd @file`
    // triggers file completion, not just slash command completion.
    for (let i = cursorCol - 1; i >= 0; i--) {
      const char = codePoints[i];

      if (char === ' ') {
        let backslashCount = 0;
        for (let j = i - 1; j >= 0 && codePoints[j] === '\\'; j--) {
          backslashCount++;
        }
        if (backslashCount % 2 === 0) {
          break;
        }
      } else if (char === '@') {
        let end = codePoints.length;
        for (let i = cursorCol; i < codePoints.length; i++) {
          if (codePoints[i] === ' ') {
            let backslashCount = 0;
            for (let j = i - 1; j >= 0 && codePoints[j] === '\\'; j--) {
              backslashCount++;
            }

            if (backslashCount % 2 === 0) {
              end = i;
              break;
            }
          }
        }
        const pathStart = i + 1;
        const partialPath = currentLine.substring(pathStart, end);
        return {
          completionMode: CompletionMode.AT,
          query: partialPath,
          completionStart: pathStart,
          completionEnd: end,
        };
      }
    }

    // THEN: Check for slash command (only if no @ completion is active)
    if (cursorRow === 0 && isSlashCommand(currentLine.trim())) {
      return {
        completionMode: CompletionMode.SLASH,
        query: currentLine,
        completionStart: 0,
        completionEnd: currentLine.length,
      };
    }

    // Check for prompt completion - only if enabled
    const trimmedText = buffer.text.trim();
    const isPromptCompletionEnabled = false;
    if (
      isPromptCompletionEnabled &&
      trimmedText.length >= PROMPT_COMPLETION_MIN_LENGTH &&
      !isSlashCommand(trimmedText) &&
      !trimmedText.includes('@')
    ) {
      return {
        completionMode: CompletionMode.PROMPT,
        query: trimmedText,
        completionStart: 0,
        completionEnd: trimmedText.length,
      };
    }

    return {
      completionMode: CompletionMode.IDLE,
      query: null,
      completionStart: -1,
      completionEnd: -1,
    };
  }, [cursorRow, cursorCol, buffer.lines, buffer.text, shellModeActive]);

  useAtCompletion({
    enabled: active && completionMode === CompletionMode.AT,
    pattern: memoQuery || '',
    config,
    cwd,
    setSuggestions,
    setIsLoadingSuggestions,
  });

  const slashCompletionRange = useSlashCompletion({
    enabled:
      active && completionMode === CompletionMode.SLASH && !shellModeActive,
    query: memoQuery,
    slashCommands,
    commandContext,
    setSuggestions,
    setIsLoadingSuggestions,
    setIsPerfectMatch,
  });

  const shellCompletionRange = useShellCompletion({
    enabled: active && completionMode === CompletionMode.SHELL,
    line: buffer.lines[cursorRow] || '',
    cursorCol,
    cwd,
    setSuggestions,
    setIsLoadingSuggestions,
  });

  const query =
    completionMode === CompletionMode.SHELL
      ? shellCompletionRange.query
      : memoQuery;

  const basePromptCompletion = usePromptCompletion({
    buffer,
  });

  const isShellSuggestionsVisible =
    completionMode !== CompletionMode.SHELL || forceShowShellSuggestions;

  const promptCompletion = useMemo(() => {
    if (
      completionMode === CompletionMode.SHELL &&
      suggestions.length === 1 &&
      query != null &&
      shellCompletionRange.completionStart === shellCompletionRange.activeStart
    ) {
      const suggestion = suggestions[0];
      const textToInsertBase = suggestion.value;

      if (
        textToInsertBase.startsWith(query) &&
        textToInsertBase.length > query.length
      ) {
        const currentLine = buffer.lines[cursorRow] || '';
        const start = shellCompletionRange.completionStart;
        const end = shellCompletionRange.completionEnd;

        let textToInsert = textToInsertBase;
        const charAfterCompletion = currentLine[end];
        if (
          charAfterCompletion !== ' ' &&
          !textToInsert.endsWith('/') &&
          !textToInsert.endsWith('\\')
        ) {
          textToInsert += ' ';
        }

        const newText =
          currentLine.substring(0, start) +
          textToInsert +
          currentLine.substring(end);

        return {
          text: newText,
          isActive: true,
          isLoading: false,
          accept: () => {
            buffer.replaceRangeByOffset(
              logicalPosToOffset(buffer.lines, cursorRow, start),
              logicalPosToOffset(buffer.lines, cursorRow, end),
              textToInsert,
            );
          },
          clear: () => {},
          markSelected: () => {},
        };
      }
    }
    return basePromptCompletion;
  }, [
    completionMode,
    suggestions,
    query,
    basePromptCompletion,
    buffer,
    cursorRow,
    shellCompletionRange,
  ]);

  useEffect(() => {
    setActiveSuggestionIndex(suggestions.length > 0 ? 0 : -1);
    setVisibleStartIndex(0);

    // Generic perfect match detection for non-slash modes or as a fallback
    if (completionMode !== CompletionMode.SLASH) {
      if (suggestions.length > 0) {
        const firstSuggestion = suggestions[0];
        setIsPerfectMatch(firstSuggestion.value === query);
      } else {
        setIsPerfectMatch(false);
      }
    }
  }, [
    suggestions,
    setActiveSuggestionIndex,
    setVisibleStartIndex,
    completionMode,
    query,
    setIsPerfectMatch,
  ]);

  useEffect(() => {
    if (
      !active ||
      completionMode === CompletionMode.IDLE ||
      reverseSearchActive
    ) {
      resetCompletionState();
    }
  }, [active, completionMode, reverseSearchActive, resetCompletionState]);

  const showSuggestions =
    active &&
    completionMode !== CompletionMode.IDLE &&
    !reverseSearchActive &&
    isShellSuggestionsVisible &&
    (isLoadingSuggestions || suggestions.length > 0);

  /**
   * Gets the completed text by replacing the completion range with the suggestion value.
   * This is the core string replacement logic used by both autocomplete and auto-execute.
   *
   * @param suggestion The suggestion to apply
   * @returns The completed text with the suggestion applied, or null if invalid
   */
  const getCompletedText = useCallback(
    (suggestion: Suggestion): string | null => {
      const currentLine = buffer.lines[cursorRow] || '';

      let start = completionStart;
      let end = completionEnd;
      if (completionMode === CompletionMode.SLASH) {
        start = slashCompletionRange.completionStart;
        end = slashCompletionRange.completionEnd;
      } else if (completionMode === CompletionMode.SHELL) {
        start = shellCompletionRange.completionStart;
        end = shellCompletionRange.completionEnd;
      }

      if (start === -1 || end === -1) {
        return null;
      }

      // Apply space padding for slash commands (needed for subcommands like "/chat list")
      let suggestionText = suggestion.insertValue ?? suggestion.value;
      if (completionMode === CompletionMode.SLASH) {
        // Add leading space if completing a subcommand (cursor is after parent command with no space)
        if (start === end && start > 1 && currentLine[start - 1] !== ' ') {
          suggestionText = ' ' + suggestionText;
        }
      }

      // Build the completed text with proper spacing
      return (
        currentLine.substring(0, start) +
        suggestionText +
        currentLine.substring(end)
      );
    },
    [
      cursorRow,
      buffer.lines,
      completionMode,
      completionStart,
      completionEnd,
      slashCompletionRange,
      shellCompletionRange,
    ],
  );

  const handleAutocomplete = useCallback(
    (indexToUse: number) => {
      if (indexToUse < 0 || indexToUse >= suggestions.length) {
        return;
      }
      const suggestion = suggestions[indexToUse];
      const completedText = getCompletedText(suggestion);

      if (completedText === null) {
        return;
      }

      let start = completionStart;
      let end = completionEnd;
      if (completionMode === CompletionMode.SLASH) {
        start = slashCompletionRange.completionStart;
        end = slashCompletionRange.completionEnd;
      } else if (completionMode === CompletionMode.SHELL) {
        start = shellCompletionRange.completionStart;
        end = shellCompletionRange.completionEnd;
      }

      // Add space padding for Tab completion (auto-execute gets padding from getCompletedText)
      let suggestionText = suggestion.insertValue ?? suggestion.value;
      if (completionMode === CompletionMode.SLASH) {
        if (
          start === end &&
          start > 1 &&
          (buffer.lines[cursorRow] || '')[start - 1] !== ' '
        ) {
          suggestionText = ' ' + suggestionText;
        }
      }

      const lineCodePoints = toCodePoints(buffer.lines[cursorRow] || '');
      const charAfterCompletion = lineCodePoints[end];

      let shouldAddSpace = true;
      if (completionMode === CompletionMode.SLASH) {
        const command =
          slashCompletionRange.getCommandFromSuggestion(suggestion);
        // Don't add a space if the command has an action (can be executed)
        // and doesn't have a completion function (doesn't REQUIRE more arguments)
        const isExecutableCommand = !!(command && command.action);
        const requiresArguments = !!(command && command.completion);
        shouldAddSpace = !isExecutableCommand || requiresArguments;
      }

      if (
        charAfterCompletion !== ' ' &&
        !suggestionText.endsWith('/') &&
        !suggestionText.endsWith('\\') &&
        shouldAddSpace
      ) {
        suggestionText += ' ';
      }

      buffer.replaceRangeByOffset(
        logicalPosToOffset(buffer.lines, cursorRow, start),
        logicalPosToOffset(buffer.lines, cursorRow, end),
        suggestionText,
      );
    },
    [
      cursorRow,
      buffer,
      suggestions,
      completionMode,
      completionStart,
      completionEnd,
      slashCompletionRange,
      shellCompletionRange,
      getCompletedText,
    ],
  );

  return {
    suggestions,
    activeSuggestionIndex,
    visibleStartIndex,
    showSuggestions,
    isLoadingSuggestions,
    isPerfectMatch,
    forceShowShellSuggestions,
    setForceShowShellSuggestions,
    isShellSuggestionsVisible,
    setActiveSuggestionIndex,
    resetCompletionState,
    navigateUp,
    navigateDown,
    handleAutocomplete,
    promptCompletion,
    getCommandFromSuggestion: slashCompletionRange.getCommandFromSuggestion,
    slashCompletionRange,
    getCompletedText,
    completionMode,
  };
}
