import { useState, useEffect } from "react";
import { Box, Text, useInput } from "ink";
import { existsSync, statSync, readdirSync } from "fs";
import { homedir } from "os";
import { join, dirname, basename } from "path";
import { useAddDirectoryFlow } from "../../hooks/useAddDirectoryFlow.ts";
import type { ExtendedKey } from "../../types/ink-keys.ts";

export interface DirectoriesStepProps {
  directories: Array<{ path: string; maxDepth: number; label?: string }>;
  onComplete: (directories: Array<{ path: string; maxDepth: number; label?: string }>) => void;
  onBack: () => void;
  onCancel: () => void;
}

interface DirectoryItem {
  path: string;
  maxDepth: number;
  label?: string;
  valid: boolean;
  error?: string;
}

interface SimplifiedBrowserState {
  // Current working directory
  currentPath: string;

  // Contents of currentPath
  entries: Array<{ name: string; fullPath: string }>;

  // What user is typing
  inputBuffer: string;

  // Completions for current input
  completions: string[];

  // Index for Tab cycling
  completionIndex: number;

  // Scroll offset for folder list
  scrollOffset: number;

  // Selected folder index in the filtered list
  selectedFolderIndex: number;

  error: string | null;
}

export function validateDirectoryPath(path: string): { valid: boolean; error?: string } {
  if (!path) {
    return { valid: false, error: "Path is required" };
  }

  const expanded = path.replace(/^~/, homedir());

  if (!existsSync(expanded)) {
    return { valid: false, error: "Path does not exist" };
  }

  try {
    const stat = statSync(expanded);
    if (!stat.isDirectory()) {
      return { valid: false, error: "Not a directory" };
    }
  } catch {
    return { valid: false, error: "Cannot access path" };
  }

  return { valid: true };
}

/**
 * Read directory contents, filtering to directories only
 */
export function readDirectory(path: string): Array<{ name: string; fullPath: string }> {
  try {
    const entries = readdirSync(path, { withFileTypes: true });
    return entries
      .filter((e) => {
        if (e.name.startsWith(".")) return false;

        // Regular directory check
        if (e.isDirectory()) return true;

        // Check if symlink points to a directory
        if (e.isSymbolicLink()) {
          try {
            const fullPath = join(path, e.name);
            return statSync(fullPath).isDirectory();
          } catch {
            return false;
          }
        }

        return false;
      })
      .map((e) => ({
        name: e.name,
        fullPath: join(path, e.name),
      }))
      .sort((a, b) => a.name.localeCompare(b.name));
  } catch {
    return [];
  }
}

/**
 * Get path completions based on input
 */
export function getCompletions(input: string, currentPath: string): string[] {
  // Strip trailing slash for proper dirname/basename handling
  const normalizedInput = input.endsWith("/") ? input.slice(0, -1) : input;
  const expanded = normalizedInput.replace(/^~/, homedir());

  // If input starts with /, it's an absolute path
  if (input.startsWith("/")) {
    const dir = dirname(expanded || "/");
    const prefix = basename(expanded);
    const entries = readDirectory(dir);
    return entries
      .filter((e) => e.name.toLowerCase().startsWith(prefix.toLowerCase()))
      .map((e) => join(dir, e.name));
  }

  // Relative or home-based path
  const basePath = expanded.startsWith("/") ? "/" : currentPath;
  const dir = join(basePath, dirname(expanded || "."));
  const prefix = basename(expanded);

  try {
    const entries = readDirectory(dir);
    return entries
      .filter((e) => e.name.toLowerCase().startsWith(prefix.toLowerCase()))
      .map((e) => join(dir, e.name));
  } catch {
    return [];
  }
}

/**
 * Format path for display (replace home with ~)
 */
export function formatDisplayPath(path: string): string {
  const home = homedir();
  if (path.startsWith(home)) {
    return "~" + path.slice(home.length);
  }
  return path;
}

/**
 * Hybrid directory browser with typing + navigation
 */
interface HybridDirectoryBrowserProps {
  startingPath: string;
  onSelect: (path: string) => void;
  onCancel: () => void;
}

export function HybridDirectoryBrowser({ startingPath, onSelect, onCancel }: HybridDirectoryBrowserProps) {
  const MAX_DISPLAY_FOLDERS = 10;

  const [browser, setBrowser] = useState<SimplifiedBrowserState>({
    currentPath: startingPath,
    entries: readDirectory(startingPath),
    inputBuffer: "~",
    completions: [],
    completionIndex: 0,
    scrollOffset: 0,
    selectedFolderIndex: 0,
    error: null,
  });

  // Update completions when input changes
  useEffect(() => {
    if (browser.inputBuffer.length > 0) {
      const matches = getCompletions(browser.inputBuffer, browser.currentPath);
      setBrowser((prev) => ({
        ...prev,
        completions: matches,
        completionIndex: 0,
      }));
    } else {
      setBrowser((prev) => ({
        ...prev,
        completions: [],
        completionIndex: 0,
      }));
    }
  }, [browser.inputBuffer, browser.currentPath]);

  // Update currentPath when input is a valid directory
  useEffect(() => {
    if (browser.inputBuffer.length > 0) {
      // Expand ~ for validation
      const expanded = browser.inputBuffer.replace(/^~/, homedir());
      // Check if it's a valid directory
      if (existsSync(expanded) && statSync(expanded).isDirectory()) {
        setBrowser((prev) => ({
          ...prev,
          currentPath: expanded,
          entries: readDirectory(expanded),
        }));
      }
    } else {
      // When input is empty, default to home directory
      setBrowser((prev) => ({
        ...prev,
        currentPath: startingPath,
        entries: readDirectory(startingPath),
      }));
    }
  }, [browser.inputBuffer, startingPath]);

  // Reset scroll offset and selection when currentPath changes
  useEffect(() => {
    setBrowser((prev) => ({
      ...prev,
      scrollOffset: 0,
      selectedFolderIndex: 0,
    }));
  }, [browser.currentPath]);

  useInput((input, key) => {
    // Esc: go back to parent directory or cancel
    if (key.escape) {
      // If input ends with / (navigated into a directory), go back to parent
      if (browser.inputBuffer.endsWith("/")) {
        const parentPath = dirname(browser.currentPath);
        setBrowser((prev) => ({
          ...prev,
          inputBuffer: "",
          currentPath: parentPath,
          entries: readDirectory(parentPath),
          completions: [],
          scrollOffset: 0,
          selectedFolderIndex: 0,
          error: null,
        }));
        return;
      }

      // If there's other input beyond ~, clear it (back to ~)
      if (browser.inputBuffer.length > 1) {
        setBrowser((prev) => ({
          ...prev,
          inputBuffer: "~",
          completions: [],
          selectedFolderIndex: 0,
          scrollOffset: 0,
          error: null,
        }));
        return;
      }

      // Otherwise cancel
      onCancel();
      return;
    }

    // Tab: cycle completions and update current path
    if (key.tab && browser.completions.length > 0) {
      const nextIndex = (browser.completionIndex + 1) % browser.completions.length;
      const completedPath = browser.completions[nextIndex]!;

      // Check if it's a directory and add trailing slash
      const expanded = completedPath.replace(/^~/, homedir());
      const isDirectory = existsSync(expanded) && statSync(expanded).isDirectory();

      // Use ~ format for home directory paths in input (for backspace-ability)
      const home = homedir();
      const inputPath = completedPath.startsWith(home)
        ? "~" + completedPath.slice(home.length)
        : completedPath;
      const inputWithSlash = isDirectory ? inputPath + "/" : inputPath;

      setBrowser((prev) => ({
        ...prev,
        completionIndex: nextIndex,
        inputBuffer: inputWithSlash,
        currentPath: completedPath,
        entries: readDirectory(completedPath),
        scrollOffset: 0,
        selectedFolderIndex: 0,
        error: null,
      }));
      return;
    }

    // Compute filtered entries for navigation (only filter if not a path)
    const isPathInput = browser.inputBuffer.includes("/");
    const filteredEntries = (browser.inputBuffer.length > 0 && !isPathInput)
      ? browser.entries.filter(e =>
          e.name.toLowerCase().includes(browser.inputBuffer.toLowerCase())
        )
      : browser.entries;

    // j/k or arrow keys for navigating filtered folder list
    if (input === "j" || key.downArrow) {
      const maxIndex = Math.max(filteredEntries.length - 1, 0);
      setBrowser((prev) => ({
        ...prev,
        selectedFolderIndex: Math.min(prev.selectedFolderIndex + 1, maxIndex),
        // Auto-scroll if selection goes below visible area
        scrollOffset: Math.max(prev.scrollOffset,
          Math.min(prev.selectedFolderIndex + 1 - MAX_DISPLAY_FOLDERS + 1,
            Math.max(filteredEntries.length - MAX_DISPLAY_FOLDERS, 0))),
      }));
      return;
    }

    if (input === "k" || key.upArrow) {
      setBrowser((prev) => ({
        ...prev,
        selectedFolderIndex: Math.max(prev.selectedFolderIndex - 1, 0),
        // Auto-scroll if selection goes above visible area
        scrollOffset: Math.min(prev.scrollOffset, prev.selectedFolderIndex - 1),
      }));
      return;
    }

    // Space: select folder from filtered list and navigate
    if (input === " ") {
      // If there's a selected folder in filtered list, navigate into it
      if (filteredEntries.length > 0 && browser.selectedFolderIndex < filteredEntries.length) {
        const selectedEntry = filteredEntries[browser.selectedFolderIndex]!;
        // Use ~ format for home directory paths in input
        const home = homedir();
        const inputPath = selectedEntry.fullPath.startsWith(home)
          ? "~" + selectedEntry.fullPath.slice(home.length)
          : selectedEntry.fullPath;
        setBrowser((prev) => ({
          ...prev,
          inputBuffer: inputPath + "/",
          currentPath: selectedEntry.fullPath,
          entries: readDirectory(selectedEntry.fullPath),
          completions: [],
          scrollOffset: 0,
          selectedFolderIndex: 0,
          error: null,
        }));
        return;
      }

      // Space on current path with no selection
      onSelect(browser.currentPath);
      return;
    }

    // Enter: accept current path and continue
    if (key.return) {
      const targetPath = browser.inputBuffer.replace(/\/$/, "") || browser.currentPath;
      const validation = validateDirectoryPath(targetPath);
      if (validation.valid) {
        onSelect(targetPath);
      } else {
        setBrowser((prev) => ({
          ...prev,
          error: validation.error || "Invalid path",
        }));
      }
      return;
    }

    // Ctrl+U: clear input back to ~
    if (key.ctrl && input === 'u') {
      setBrowser((prev) => ({
        ...prev,
        inputBuffer: "~",
        completions: [],
        selectedFolderIndex: 0,
        scrollOffset: 0,
        error: null,
      }));
      return;
    }

    // Backspace/delete
    if ((key as unknown as ExtendedKey).backspace || (key as unknown as ExtendedKey).delete) {
      if (browser.inputBuffer.length > 0) {
        setBrowser((prev) => ({
          ...prev,
          inputBuffer: prev.inputBuffer.slice(0, -1),
          selectedFolderIndex: 0,
          scrollOffset: 0,
          error: null,
        }));
      }
      return;
    }

    // Regular character input
    if (input.length === 1 && /[a-zA-Z0-9_\/\.~-]/.test(input)) {
      setBrowser((prev) => ({
        ...prev,
        inputBuffer: prev.inputBuffer + input,
        selectedFolderIndex: 0,
        scrollOffset: 0,
        error: null,
      }));
      return;
    }
  });

  return (
    <Box flexDirection="column" flexGrow={1}>
      {/* Text input */}
      <Box marginBottom={1}>
        <Box gap={1}>
          <Text color="yellow">{"> "}</Text>
          <Text color="yellow">
            {browser.inputBuffer}
          </Text>
          <Text color="yellow" inverse>_</Text>
        </Box>
      </Box>

      {/* Error message */}
      {browser.error && (
        <Box marginBottom={1}>
          <Text color="red">{browser.error}</Text>
        </Box>
      )}

      {/* Directory list - filtered and scrollable */}
      <Box flexDirection="column" marginBottom={1}>
        <Text dimColor>Folders in {formatDisplayPath(browser.currentPath)}:</Text>
        {(() => {
          // Only filter by name if input doesn't contain slashes (not a path)
          const isPathInput = browser.inputBuffer.includes("/");
          const filteredEntries = (browser.inputBuffer.length > 0 && !isPathInput)
            ? browser.entries.filter(e =>
                e.name.toLowerCase().includes(browser.inputBuffer.toLowerCase())
              )
            : browser.entries;

          if (filteredEntries.length === 0) {
            return <Text dimColor>No matching folders</Text>;
          }

          const visibleEntries = filteredEntries.slice(
            browser.scrollOffset,
            browser.scrollOffset + MAX_DISPLAY_FOLDERS
          );

          return (
            <>
              {browser.scrollOffset > 0 && (
                <Text dimColor>▲ {browser.scrollOffset} more above</Text>
              )}
              {visibleEntries.map((item, idx) => {
                const globalIndex = browser.scrollOffset + idx;
                const isSelected = globalIndex === browser.selectedFolderIndex;
                return (
                  <Text key={item.fullPath}>
                    {isSelected ? <Text color="cyan">●</Text> : <Text dimColor>○</Text>}
                    {" "}{item.name}/
                  </Text>
                );
              })}
              {browser.scrollOffset + MAX_DISPLAY_FOLDERS < filteredEntries.length && (
                <Text dimColor>▼ {filteredEntries.length - browser.scrollOffset - MAX_DISPLAY_FOLDERS} more below</Text>
              )}
            </>
          );
        })()}
      </Box>

      {/* Help text */}
      <Box flexDirection="column" marginTop={1}>
        <Box gap={2}>
          <Text dimColor>Tab:</Text>
          <Text dimColor>autocomplete</Text>
          <Text dimColor>j/k:</Text>
          <Text dimColor>navigate</Text>
          <Text dimColor>Space:</Text>
          <Text dimColor>navigate folder</Text>
        </Box>
        <Box gap={2}>
          <Text dimColor>Enter:</Text>
          <Text dimColor>select & continue</Text>
          <Text dimColor>Esc:</Text>
          <Text dimColor>go back</Text>
        </Box>
      </Box>
    </Box>
  );
}

interface FlowSeed {
  initialPath?: string;
  initialMaxDepth?: string;
  initialLabel?: string;
}

/**
 * Sub-component that owns one mount of useAddDirectoryFlow. Wrapping the
 * hook in a child lets us remount (and re-seed) it cleanly when the user
 * picks "a" (add new) vs "e" (edit existing).
 */
function FlowMode({
  seed,
  onComplete,
  onCancel,
}: {
  seed: FlowSeed;
  onComplete: (path: string, maxDepth: number, label: string) => void;
  onCancel: () => void;
}) {
  const flow = useAddDirectoryFlow({
    initialPath: seed.initialPath,
    initialMaxDepth: seed.initialMaxDepth,
    initialLabel: seed.initialLabel,
    onComplete,
    onCancel,
  });

  useInput((inputStr, key) => {
    if (flow.step === "browse") return;
    const extKey = key as unknown as ExtendedKey;

    if (key.escape) {
      flow.back();
      return;
    }
    if (key.return) {
      flow.advance();
      return;
    }
    if (flow.step === "maxDepth") {
      if (extKey.backspace || extKey.delete) {
        flow.setMaxDepthInput(flow.maxDepthInput.slice(0, -1));
      } else if (/^[0-9]$/.test(inputStr)) {
        flow.setMaxDepthInput(flow.maxDepthInput + inputStr);
      }
    } else if (flow.step === "label") {
      if (extKey.backspace || extKey.delete) {
        flow.setLabelInput(flow.labelInput.slice(0, -1));
      } else if (inputStr.length === 1) {
        flow.setLabelInput(flow.labelInput + inputStr);
      }
    }
  });

  return (
    <>
      {flow.step === "browse" && (
        <HybridDirectoryBrowser
          startingPath={homedir()}
          onSelect={flow.setSelectedPath}
          onCancel={onCancel}
        />
      )}
      {flow.step === "maxDepth" && (
        <Box flexDirection="column" marginBottom={1}>
          <Text>Max scan depth (0-10, default 2):</Text>
          <Box gap={1}>
            <Text color="yellow">{"> "}</Text>
            <Text color="yellow">{flow.maxDepthInput}</Text>
            <Text color="yellow" inverse>_</Text>
          </Box>
          <Text dimColor>Path: {formatDisplayPath(flow.selectedPath || "")}</Text>
          {flow.error && <Text color="red">{flow.error}</Text>}
          <Text dimColor>Press Enter to confirm, Esc to go back</Text>
        </Box>
      )}
      {flow.step === "label" && (
        <Box flexDirection="column" marginBottom={1}>
          <Text>Label (optional, press Enter to skip):</Text>
          <Box gap={1}>
            <Text color="yellow">{"> "}</Text>
            <Text color="yellow">{flow.labelInput}</Text>
            <Text color="yellow" inverse>_</Text>
          </Box>
          <Text dimColor>Path: {formatDisplayPath(flow.selectedPath || "")}</Text>
          <Text dimColor>Depth: {flow.maxDepthInput}</Text>
          {flow.error && <Text color="red">{flow.error}</Text>}
          <Text dimColor>Press Enter to confirm, Esc to go back</Text>
        </Box>
      )}
    </>
  );
}

export function DirectoriesStep({
  directories,
  onComplete,
  onBack,
  onCancel,
}: DirectoriesStepProps) {
  const [items, setItems] = useState<DirectoryItem[]>(
    directories.map((d) => ({
      ...d,
      valid: true,
    }))
  );
  const [selectedIndex, setSelectedIndex] = useState(0);
  // Outer mode: in "list" we render the items table; in "flow" the
  // useAddDirectoryFlow sub-component owns the screen.
  const [outerMode, setOuterMode] = useState<"list" | "flow">("list");
  const [flowSeed, setFlowSeed] = useState<FlowSeed>({});
  const [error, setError] = useState<string | null>(null);

  const handleFlowComplete = (path: string, maxDepth: number, label: string) => {
    const newItem: DirectoryItem = {
      path,
      maxDepth,
      label: label || undefined,
      valid: true,
    };
    setItems((prev) => {
      const existingIndex = prev.findIndex((i) => i.path === path);
      if (existingIndex >= 0) {
        const updated = [...prev];
        updated[existingIndex] = newItem;
        return updated;
      }
      return [...prev, newItem];
    });
    setOuterMode("list");
    setError(null);
  };

  const handleFlowCancel = () => {
    setOuterMode("list");
    setError(null);
  };

  const handleSkip = () => {
    const defaultDir: DirectoryItem = {
      path: `${homedir()}/projects`,
      maxDepth: 2,
      label: "Projects",
      valid: true,
    };
    onComplete([{ path: defaultDir.path, maxDepth: defaultDir.maxDepth, label: defaultDir.label }]);
  };

  const handleComplete = () => {
    const validItems = items.filter((i) => i.valid);
    if (validItems.length === 0) {
      setError("Add at least one directory");
      return;
    }
    onComplete(
      validItems.map((i) => ({
        path: i.path,
        maxDepth: i.maxDepth,
        label: i.label,
      }))
    );
  };

  useInput((inputStr, key) => {
    if (outerMode === "flow") return; // FlowMode owns all keys

    if (key.escape || inputStr === "q" || inputStr === "Q") {
      onCancel();
      return;
    }

    if ((key as unknown as ExtendedKey).backspace || (key as unknown as ExtendedKey).delete) {
      onBack();
      return;
    }

    if (key.return) {
      if (items.length > 0) {
        handleComplete();
      }
      return;
    }

    if (inputStr === "j" || key.downArrow) {
      setSelectedIndex((i) => Math.min(i + 1, Math.max(items.length - 1, 0)));
      return;
    }

    if (inputStr === "k" || key.upArrow) {
      setSelectedIndex((i) => Math.max(i - 1, 0));
      return;
    }

    if (inputStr === "a" || inputStr === "A") {
      setFlowSeed({});
      setOuterMode("flow");
      setError(null);
      return;
    }

    if ((inputStr === "d" || inputStr === "D") && items.length > 0) {
      setItems((prev) => prev.filter((_, i) => i !== selectedIndex));
      if (selectedIndex >= items.length - 1) {
        setSelectedIndex(Math.max(items.length - 2, 0));
      }
      return;
    }

    if (inputStr === "s" || inputStr === "S") {
      handleSkip();
      return;
    }

    if ((inputStr === "e" || inputStr === "E") && items.length > 0) {
      const item = items[selectedIndex];
      if (item) {
        setFlowSeed({
          initialPath: item.path,
          initialMaxDepth: String(item.maxDepth),
          initialLabel: item.label || "",
        });
        setOuterMode("flow");
        setError(null);
      }
      return;
    }
  });

  return (
    <Box
      flexDirection="column"
      borderStyle="round"
      borderColor="cyan"
      paddingX={2}
      paddingY={1}
      width={80}
    >
      {/* Title */}
      <Box marginBottom={1}>
        <Text bold color="cyan">
          Configure Project Directories
        </Text>
      </Box>

      {/* Instructions */}
      {outerMode === "list" && (
        <Box marginBottom={1}>
          <Text>Add the directories containing your git projects.</Text>
        </Box>
      )}

      {/* Add/edit flow */}
      {outerMode === "flow" && (
        <FlowMode
          seed={flowSeed}
          onComplete={handleFlowComplete}
          onCancel={handleFlowCancel}
        />
      )}

      {/* List mode */}
      {outerMode === "list" && (
        <>
          {/* Current directories */}
          {items.length > 0 && (
            <Box flexDirection="column" marginBottom={1}>
              <Text dimColor>Current directories:</Text>
              {items.map((item, index) => (
                <Box key={item.path} gap={2}>
                  <Text color={index === selectedIndex ? "cyan" : "gray"}>
                    {index === selectedIndex ? "●" : "○"}
                  </Text>
                  <Text color={index === selectedIndex ? "white" : "gray"}>
                    {item.label || item.path}
                  </Text>
                  <Text dimColor>
                    (depth: {item.maxDepth})
                  </Text>
                </Box>
              ))}
            </Box>
          )}

          {/* Empty state */}
          {items.length === 0 && (
            <Box flexDirection="column" marginBottom={1}>
              <Text dimColor>No directories added yet.</Text>
              <Text dimColor>Press 'a' to browse your filesystem.</Text>
            </Box>
          )}

          {/* Error message */}
          {error && (
            <Box marginBottom={1}>
              <Text color="red">{error}</Text>
            </Box>
          )}

          {/* Actions */}
          <Box gap={2} marginTop={1}>
            <Text dimColor>Shortcuts:</Text>
            <Text color="green" bold>
              a
            </Text>
            <Text dimColor>Add</Text>
            {items.length > 0 && (
              <>
                <Text color="green" bold>
                  d
                </Text>
                <Text dimColor>Delete</Text>
                <Text color="green" bold>
                  e
                </Text>
                <Text dimColor>Edit</Text>
              </>
            )}
            <Text color="green" bold>
              s
            </Text>
            <Text dimColor>Skip (defaults)</Text>
          </Box>
          <Box gap={2}>
            <Text dimColor>j/k</Text>
            <Text dimColor>Navigate</Text>
            <Text color="green" bold>
              Enter
            </Text>
            <Text dimColor>Continue</Text>
            <Text color="red" bold>
              Backspace
            </Text>
            <Text dimColor>Back</Text>
            <Text color="red" bold>
              q
            </Text>
            <Text dimColor>Quit</Text>
          </Box>
        </>
      )}
    </Box>
  );
}
