import { useCallback, useEffect, useRef, useState } from "react";
import {
  canUseHostedProvider,
  canUseProviderWithoutApiKey,
} from "@/features/ai/lib/provider-access";
import { useAIChatStore } from "@/features/ai/store/store";
import { getProviderById } from "@/features/ai/types/providers";
import { useSettingsStore } from "@/features/settings/store";
import { useAuthStore } from "@/features/window/stores/auth-store";
import { useInlineEditToolbarStore } from "@/features/editor/stores/inline-edit-toolbar-store";
import { toast } from "@/ui/toast";
import {
  InlineEditError,
  requestInlineEdit,
} from "@/features/editor/services/editor-inline-edit-service";
import { EDITOR_CONSTANTS } from "@/features/editor/config/constants";
import type { Position, Range } from "@/features/editor/types/editor";
import type { EditorModelPositionResolver } from "@/features/athas-editor/view-model/view-layout";
import {
  calculateCursorPositionFromContent,
  calculateCursorPositionFromLineOffsets,
  calculateOffsetFromPosition,
  getAccurateCursorX,
} from "@/features/athas-editor/utils/position";

const DEFAULT_INLINE_EDIT_INSTRUCTION = "Improve this code while preserving behavior.";
const INLINE_EDIT_POPOVER_WIDTH = 380;
const INLINE_EDIT_POPOVER_ESTIMATED_HEIGHT = 58;
const INLINE_EDIT_POPOVER_MARGIN = 8;
const INLINE_EDIT_POPOVER_X_OFFSET = 0;
const INLINE_EDIT_POPOVER_Y_OFFSET = 6;
const INLINE_EDIT_TOP_THRESHOLD = 64;

interface UseInlineEditOptions {
  enabled?: boolean;
  viewKey?: string | null;
  inputRef?: React.RefObject<HTMLTextAreaElement | null>;
  buffer: { id: string; content: string; path: string; language: string } | undefined;
  selection: Range | undefined;
  lines: string[];
  lineOffsets: number[];
  fontSize: number;
  fontFamily: string;
  lineHeight: number;
  tabSize: number;
  lastScrollRef: React.RefObject<{ top: number; left: number }>;
  resolveModelPosition?: EditorModelPositionResolver;
  getCursorOffset?: () => number | null;
  getSelectionAnchor?: () => { line: number; column: number } | null;
  getViewportMetrics?: () => {
    scrollTop: number;
    scrollLeft: number;
    viewportWidth: number;
    viewportHeight: number;
  } | null;
  applyInlineEdit?: (edit: {
    range: Range;
    editedText: string;
    newContent: string;
    newCursorOffset: number;
    newPosition: Position;
  }) => void;
  setCursorPosition: (position: Position) => void;
  setSelection: (selection?: Range) => void;
  updateBufferContent?: (bufferId: string, content: string, snapshot?: boolean) => void;
}

export function useInlineEdit({
  enabled = true,
  viewKey,
  inputRef,
  buffer,
  selection,
  lines,
  lineOffsets,
  fontSize,
  fontFamily,
  lineHeight,
  tabSize,
  lastScrollRef,
  resolveModelPosition,
  getCursorOffset,
  getSelectionAnchor,
  getViewportMetrics,
  applyInlineEdit,
  setCursorPosition,
  setSelection,
  updateBufferContent,
}: UseInlineEditOptions) {
  const inlineEditRequested = useInlineEditToolbarStore.use.isVisible();
  const inlineEditTargetViewKey = useInlineEditToolbarStore.use.targetViewKey();
  const inlineEditVisible =
    enabled &&
    inlineEditRequested &&
    (!inlineEditTargetViewKey || !viewKey || inlineEditTargetViewKey === viewKey);
  const inlineEditToolbarActions = useInlineEditToolbarStore.use.actions();
  const inlineEditPopoverRef = useRef<HTMLDivElement>(null);
  const inlineEditInstructionRef = useRef<HTMLInputElement>(null);
  const focusRestoreRef = useRef<HTMLElement | null>(null);

  const [inlineEditInstruction, setInlineEditInstruction] = useState("");
  const [isInlineEditRunning, setIsInlineEditRunning] = useState(false);
  const [inlineEditError, setInlineEditError] = useState<string | null>(null);
  const [inlineEditSelectionAnchor, setInlineEditSelectionAnchor] = useState<{
    line: number;
    column: number;
  } | null>(null);

  const aiProviderId = useSettingsStore((state) => state.settings.aiProviderId);
  const aiModelId = useSettingsStore((state) => state.settings.aiModelId);
  const updateSetting = useSettingsStore((state) => state.updateSetting);
  const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
  const subscription = useAuthStore((state) => state.subscription);
  const checkAllProviderApiKeys = useAIChatStore((state) => state.checkAllProviderApiKeys);

  const getSelectionAnchorPosition = useCallback((): { line: number; column: number } | null => {
    if (!selection || selection.start.offset === selection.end.offset) return null;

    const start = selection.start.offset <= selection.end.offset ? selection.start : selection.end;
    const end = selection.start.offset <= selection.end.offset ? selection.end : selection.start;

    if (start.line === end.line) {
      return {
        line: start.line,
        column: Math.round((start.column + end.column) / 2),
      };
    }

    return {
      line: end.line,
      column: end.column,
    };
  }, [selection]);

  useEffect(() => {
    if (!enabled) {
      setInlineEditError(null);
      setInlineEditSelectionAnchor(null);
      return;
    }

    if (!inlineEditVisible) {
      setInlineEditError(null);
      const restoreTarget = focusRestoreRef.current;
      focusRestoreRef.current = null;
      if (restoreTarget && document.contains(restoreTarget)) {
        requestAnimationFrame(() => restoreTarget.focus());
      }
      return;
    }

    focusRestoreRef.current = document.activeElement as HTMLElement | null;
    setInlineEditInstruction("");
    setInlineEditError(null);

    let cancelled = false;
    let attempt = 0;

    const focusInstructionInput = () => {
      if (cancelled) return;

      const input = inlineEditInstructionRef.current;
      if (!input) {
        if (attempt < 4) {
          attempt += 1;
          requestAnimationFrame(focusInstructionInput);
        }
        return;
      }

      input.focus({ preventScroll: true });
      input.select();

      if (document.activeElement !== input && attempt < 4) {
        attempt += 1;
        requestAnimationFrame(focusInstructionInput);
      }
    };

    requestAnimationFrame(focusInstructionInput);

    return () => {
      cancelled = true;
    };
  }, [enabled, inlineEditVisible]);

  useEffect(() => {
    if (!enabled) return;
    if (!inlineEditVisible) return;
    void checkAllProviderApiKeys();
  }, [enabled, inlineEditVisible, checkAllProviderApiKeys]);

  useEffect(() => {
    if (!enabled) {
      setInlineEditSelectionAnchor(null);
      return;
    }
    if (!inlineEditVisible) {
      setInlineEditSelectionAnchor(null);
      return;
    }
    if (inlineEditSelectionAnchor) return;
    const providedAnchor = getSelectionAnchorPosition() ?? getSelectionAnchor?.();
    if (providedAnchor) {
      setInlineEditSelectionAnchor(providedAnchor);
      return;
    }

    const cursorOffset =
      getCursorOffset?.() ?? (inputRef?.current ? inputRef.current.selectionStart : null);
    if (cursorOffset === null || cursorOffset === undefined) return;

    const anchorPos = calculateCursorPositionFromLineOffsets(cursorOffset, lines, lineOffsets);
    setInlineEditSelectionAnchor({ line: anchorPos.line, column: anchorPos.column });
  }, [
    enabled,
    inlineEditVisible,
    inlineEditSelectionAnchor,
    lineOffsets,
    lines,
    inputRef,
    getCursorOffset,
    getSelectionAnchor,
    getSelectionAnchorPosition,
  ]);

  const resolveInlineEditRange = useCallback((): Range | null => {
    if (!enabled) return null;

    if (selection && selection.start.offset !== selection.end.offset) {
      const start =
        selection.start.offset <= selection.end.offset ? selection.start : selection.end;
      const end = selection.start.offset <= selection.end.offset ? selection.end : selection.start;
      return { start, end };
    }

    if (lines.length === 0) {
      return null;
    }

    const cursorOffset =
      getCursorOffset?.() ?? (inputRef?.current ? inputRef.current.selectionStart : null);
    if (cursorOffset === null || cursorOffset === undefined) return null;

    const cursorPosition = calculateCursorPositionFromLineOffsets(cursorOffset, lines, lineOffsets);
    const lineText = lines[cursorPosition.line] ?? "";
    const lineStartOffset =
      lineOffsets[cursorPosition.line] ??
      calculateOffsetFromPosition(cursorPosition.line, 0, lines);
    const lineEndOffset = lineStartOffset + lineText.length;

    return {
      start: {
        line: cursorPosition.line,
        column: 0,
        offset: lineStartOffset,
      },
      end: {
        line: cursorPosition.line,
        column: lineText.length,
        offset: lineEndOffset,
      },
    };
  }, [enabled, getCursorOffset, inputRef, lineOffsets, lines, selection]);

  const handleApplyInlineEdit = useCallback(async () => {
    if (!enabled) {
      inlineEditToolbarActions.hide();
      return;
    }

    if (!buffer) {
      toast.warning("Inline edit requires an open buffer.");
      inlineEditToolbarActions.hide();
      return;
    }

    const targetRange = resolveInlineEditRange();
    if (!targetRange) {
      toast.warning("Could not determine an inline edit target.");
      inlineEditToolbarActions.hide();
      return;
    }

    const startOffset = targetRange.start.offset;
    const endOffset = targetRange.end.offset;
    const selectedText = buffer.content.slice(startOffset, endOffset);

    const provider = getProviderById(aiProviderId);

    if (!aiModelId.trim()) {
      toast.error("Please select an inline edit model.");
      return;
    }

    const enterprisePolicy = subscription?.enterprise?.policy;
    const managedPolicy = enterprisePolicy?.managedMode ? enterprisePolicy : null;

    const hasStoredProviderKey =
      useAIChatStore.getState().providerApiKeys.get(aiProviderId) || false;
    const canUseProvider = canUseProviderWithoutApiKey({
      providerId: aiProviderId,
      subscription,
      hasStoredKey: hasStoredProviderKey,
      requiresApiKey: provider?.requiresApiKey ?? true,
    });

    if (!canUseProvider) {
      await checkAllProviderApiKeys();
      const hasProviderKeyAfterRefresh =
        useAIChatStore.getState().providerApiKeys.get(aiProviderId) || false;
      if (!hasProviderKeyAfterRefresh) {
        toast.error(`${provider?.name ?? aiProviderId} API key is required for inline edit.`);
        return;
      }
    }

    const hasProviderKey = useAIChatStore.getState().providerApiKeys.get(aiProviderId) || false;
    const useHosted = !hasProviderKey && canUseHostedProvider(aiProviderId, subscription);

    if (useHosted && !isAuthenticated) {
      toast.error("Please sign in to use hosted inline edit.");
      return;
    }

    if (useHosted && managedPolicy && !managedPolicy.aiCompletionEnabled) {
      toast.error("Inline edit is disabled by your organization policy.");
      return;
    }

    if (!useHosted && managedPolicy && !managedPolicy.allowByok) {
      toast.error("BYOK is disabled by your organization policy.");
      return;
    }

    const beforeSelection = buffer.content.slice(Math.max(0, startOffset - 12000), startOffset);
    const afterSelection = buffer.content.slice(endOffset, endOffset + 12000);

    setInlineEditError(null);
    setIsInlineEditRunning(true);

    try {
      const { editedText } = await requestInlineEdit(
        {
          provider: aiProviderId,
          model: aiModelId,
          beforeSelection,
          selectedText,
          afterSelection,
          instruction: inlineEditInstruction.trim() || DEFAULT_INLINE_EDIT_INSTRUCTION,
          filePath: buffer.path,
          languageId: buffer.language,
        },
        { useHosted },
      );

      if (!editedText.trim()) {
        toast.warning("Inline edit returned an empty result.");
        return;
      }

      const newContent = `${buffer.content.slice(0, startOffset)}${editedText}${buffer.content.slice(
        endOffset,
      )}`;
      const newCursorOffset = startOffset + editedText.length;
      const newPosition = calculateCursorPositionFromContent(newCursorOffset, newContent);

      if (applyInlineEdit) {
        applyInlineEdit({
          range: targetRange,
          editedText,
          newContent,
          newCursorOffset,
          newPosition,
        });
      } else {
        updateBufferContent?.(buffer.id, newContent, true);
      }

      setCursorPosition(newPosition);
      setSelection(undefined);
      setInlineEditSelectionAnchor(null);
      inlineEditToolbarActions.hide();
      if (inputRef?.current) {
        inputRef.current.selectionStart = newCursorOffset;
        inputRef.current.selectionEnd = newCursorOffset;
      }

      toast.success("Inline edit applied.");
    } catch (error) {
      const errorMessage =
        error instanceof InlineEditError ? error.message : "Inline edit failed. Please try again.";
      setInlineEditError(errorMessage);
      if (error instanceof InlineEditError) {
        toast.error(error.message);
      } else {
        toast.error("Inline edit failed. Please try again.");
      }
    } finally {
      setIsInlineEditRunning(false);
    }
  }, [
    buffer,
    resolveInlineEditRange,
    isAuthenticated,
    subscription,
    checkAllProviderApiKeys,
    aiProviderId,
    aiModelId,
    inlineEditInstruction,
    inlineEditError,
    applyInlineEdit,
    updateBufferContent,
    setCursorPosition,
    setSelection,
    inlineEditToolbarActions,
    inputRef,
    enabled,
  ]);

  const popoverPosition = (() => {
    if (!enabled) return null;
    if (!inlineEditVisible || !inlineEditSelectionAnchor) return null;
    if (inlineEditSelectionAnchor.line < 0 || inlineEditSelectionAnchor.line >= lines.length) {
      return null;
    }

    const lineText = lines[inlineEditSelectionAnchor.line] || "";
    const anchorColumn = Math.min(inlineEditSelectionAnchor.column, lineText.length);
    const resolvedAnchor = resolveModelPosition?.(inlineEditSelectionAnchor.line, anchorColumn);
    const anchorX =
      resolvedAnchor?.left !== undefined
        ? resolvedAnchor.left - EDITOR_CONSTANTS.EDITOR_PADDING_LEFT
        : getAccurateCursorX(lineText, anchorColumn, fontSize, fontFamily, tabSize);
    const anchorTop =
      resolvedAnchor?.top ??
      inlineEditSelectionAnchor.line * lineHeight + EDITOR_CONSTANTS.EDITOR_PADDING_TOP;
    const viewportMetrics = getViewportMetrics?.();
    const textarea = inputRef?.current;
    const scrollLeft =
      viewportMetrics?.scrollLeft ?? textarea?.scrollLeft ?? lastScrollRef.current.left;
    const scrollTop =
      viewportMetrics?.scrollTop ?? textarea?.scrollTop ?? lastScrollRef.current.top;
    const viewportWidth =
      viewportMetrics?.viewportWidth ??
      textarea?.clientWidth ??
      INLINE_EDIT_POPOVER_WIDTH + INLINE_EDIT_POPOVER_MARGIN * 2;
    const viewportHeight =
      viewportMetrics?.viewportHeight ??
      textarea?.clientHeight ??
      INLINE_EDIT_POPOVER_ESTIMATED_HEIGHT + INLINE_EDIT_POPOVER_MARGIN * 2;

    const minLeft = scrollLeft + INLINE_EDIT_POPOVER_MARGIN;
    const maxLeft = Math.max(
      minLeft,
      scrollLeft + viewportWidth - INLINE_EDIT_POPOVER_WIDTH - INLINE_EDIT_POPOVER_MARGIN,
    );
    const rawLeft = anchorX + EDITOR_CONSTANTS.EDITOR_PADDING_LEFT + INLINE_EDIT_POPOVER_X_OFFSET;
    const clampedLeft = Math.min(Math.max(rawLeft, minLeft), maxLeft);

    const minTop = scrollTop + INLINE_EDIT_POPOVER_MARGIN;
    const maxTop = Math.max(
      minTop,
      scrollTop +
        viewportHeight -
        INLINE_EDIT_POPOVER_ESTIMATED_HEIGHT -
        INLINE_EDIT_POPOVER_MARGIN,
    );
    const preferBelow = anchorTop - scrollTop < INLINE_EDIT_TOP_THRESHOLD;
    const belowTop = anchorTop + lineHeight + INLINE_EDIT_POPOVER_Y_OFFSET;
    const aboveTop =
      anchorTop - INLINE_EDIT_POPOVER_ESTIMATED_HEIGHT - INLINE_EDIT_POPOVER_Y_OFFSET;
    let top = preferBelow ? belowTop : aboveTop;
    if (top < minTop) {
      top = belowTop;
    }
    const clampedTop = Math.min(Math.max(top, minTop), maxTop);

    return {
      top: clampedTop,
      left: clampedLeft,
    };
  })();

  return {
    inlineEditVisible,
    inlineEditInstruction,
    setInlineEditInstruction,
    inlineEditError,
    setInlineEditError,
    isInlineEditRunning,
    isInlineEditModelLoading: false,
    inlineEditModels: [],
    inlineEditSelectionAnchor,
    setInlineEditSelectionAnchor,
    inlineEditPopoverRef,
    inlineEditInstructionRef,
    inlineEditToolbarActions,
    aiProviderId,
    aiModelId,
    updateSetting,
    handleApplyInlineEdit,
    popoverPosition,
  };
}
