/**
 * Terminal file picker for browsing directories and selecting files.
 *
 * This component does not include a dialog wrapper — consumers wrap it in
 * {@link DialogWrapper} when needed. When used inside a dialog overlay, pass
 * `layerId` so keyboard input is routed through the input-layer stack instead
 * of conflicting with background {@link ScrollableList} handlers.
 */

import React, { useCallback, useEffect, useMemo, useState } from "react";
import { Box, Text } from "ink";

import { ScrollableList, type ListItemData } from "./List.js";
import { useLayeredInput } from "../hooks/useInputLayer.js";
import { colors } from "../theme.js";
import {
  listDirectoryEntries,
  type DirectoryEntry,
} from "../utils/list-directory-entries.js";

/**
 * Props for {@link FilePicker}.
 */
export interface FilePickerProps {
  /** Starting directory. Defaults to `process.cwd()`. */
  initialDirectory?: string;
  /**
   * Allowed file extensions without a leading dot (e.g. `['json']`).
   * Omit to show all files. Directories are always shown.
   */
  extensions?: string[];
  /**
   * Input-layer id for dialog use. When set, this component handles ↑↓/Enter
   * via {@link useLayeredInput} and disables {@link ScrollableList} focus.
   */
  layerId?: string;
  /** Whether the list receives keyboard focus when `layerId` is not set. */
  focus?: boolean;
  /** Maximum visible rows in the scroll window. */
  maxVisible?: number;
  /** Called when the user confirms a file with Enter. */
  onSelectFile: (absolutePath: string) => void;
  /** Optional callback whenever the browsed directory changes. */
  onDirectoryChange?: (absolutePath: string) => void;
}

/**
 * Truncates a long path for display, keeping the end visible.
 */
function formatDirectoryPath(directoryPath: string, maxLength = 56): string {
  if (directoryPath.length <= maxLength) {
    return directoryPath;
  }

  return `...${directoryPath.slice(-(maxLength - 3))}`;
}

/**
 * Builds list row metadata for a directory entry.
 */
function toListItem(entry: DirectoryEntry): ListItemData<DirectoryEntry> {
  if (entry.kind === "parent") {
    return {
      key: "__parent__",
      label: "..",
      description: "Parent directory",
      value: entry,
    };
  }

  if (entry.kind === "directory") {
    return {
      key: `dir:${entry.absolutePath}`,
      label: entry.name,
      description: "Directory",
      value: entry,
    };
  }

  return {
    key: `file:${entry.absolutePath}`,
    label: entry.name,
    value: entry,
  };
}

/**
 * Generic terminal file picker with optional extension filtering.
 */
export function FilePicker({
  initialDirectory = process.cwd(),
  extensions,
  layerId,
  focus = true,
  maxVisible = 10,
  onSelectFile,
  onDirectoryChange,
}: FilePickerProps): React.ReactElement {
  const [currentDirectory, setCurrentDirectory] = useState(() =>
    initialDirectory,
  );
  const [selectedIndex, setSelectedIndex] = useState(0);
  const [loadError, setLoadError] = useState<string | undefined>();

  const { entries, error } = useMemo(
    () => listDirectoryEntries(currentDirectory, { extensions }),
    [currentDirectory, extensions],
  );

  useEffect(() => {
    setLoadError(error);
  }, [error]);

  useEffect(() => {
    setSelectedIndex(0);
  }, [currentDirectory, extensions]);

  useEffect(() => {
    if (entries.length === 0) {
      setSelectedIndex(0);
      return;
    }

    if (selectedIndex >= entries.length) {
      setSelectedIndex(entries.length - 1);
    }
  }, [entries, selectedIndex]);

  const listItems = useMemo(
    (): ListItemData<DirectoryEntry>[] => entries.map(toListItem),
    [entries],
  );

  /**
   * Moves selection to the previous visible row, wrapping at the top.
   */
  const selectPrevious = useCallback((): void => {
    setSelectedIndex((previous) =>
      previous <= 0 ? Math.max(entries.length - 1, 0) : previous - 1,
    );
  }, [entries.length]);

  /**
   * Moves selection to the next visible row, wrapping at the bottom.
   */
  const selectNext = useCallback((): void => {
    setSelectedIndex((previous) =>
      entries.length === 0
        ? 0
        : previous >= entries.length - 1
          ? 0
          : previous + 1,
    );
  }, [entries.length]);

  /**
   * Applies the current row: navigate for parent/directory, select for files.
   */
  const activateSelectedEntry = useCallback((): void => {
    const entry = entries[selectedIndex];
    if (!entry) {
      return;
    }

    if (entry.kind === "parent" || entry.kind === "directory") {
      setCurrentDirectory(entry.absolutePath);
      onDirectoryChange?.(entry.absolutePath);
      return;
    }

    onSelectFile(entry.absolutePath);
  }, [entries, onDirectoryChange, onSelectFile, selectedIndex]);

  /**
   * Dialog overlays must pass `layerId` because ScrollableList uses raw ink
   * `useInput`, which does not respect the input capture stack.
   */
  useLayeredInput(
    layerId ?? "file-picker-standalone",
    (_input, key) => {
      if (!layerId) {
        return;
      }

      if (key.upArrow) {
        selectPrevious();
        return;
      }

      if (key.downArrow) {
        selectNext();
        return;
      }

      if (key.return) {
        activateSelectedEntry();
      }
    },
    { isActive: Boolean(layerId) },
  );

  const renderItem = useCallback(
    (
      item: ListItemData<DirectoryEntry>,
      isSelected: boolean,
      isFocused: boolean,
    ): React.ReactNode => {
      const entry = item.value;
      /**
       * Inside dialogs, ScrollableList focus is disabled (input comes from layerId).
       * Treat the selected row as highlighted so it matches other focused lists.
       */
      const isHighlighted = layerId ? isSelected : isFocused;
      const textColor = isHighlighted ? colors.focus : colors.text;
      const indicator = isHighlighted ? "▸ " : "  ";

      if (entry?.kind === "parent") {
        return (
          <Text color={textColor} bold={isSelected}>
            {indicator}
            ⬆ ..
          </Text>
        );
      }

      if (entry?.kind === "directory") {
        return (
          <Text color={textColor} bold={isSelected}>
            {indicator}
            📁 {item.label}/
          </Text>
        );
      }

      return (
        <Text color={textColor} bold={isSelected}>
          {indicator}
          {item.label}
        </Text>
      );
    },
    [layerId],
  );

  const listFocus = layerId ? false : focus;

  return (
    <Box flexDirection="column">
      <Text color={colors.textMuted}>
        Directory: {formatDirectoryPath(currentDirectory)}
      </Text>

      {loadError ? (
        <Box marginTop={1}>
          <Text color={colors.error}>{loadError}</Text>
        </Box>
      ) : null}

      <Box marginTop={1} flexDirection="column">
        <ScrollableList
          items={listItems}
          selectedIndex={selectedIndex}
          onSelect={setSelectedIndex}
          onActivate={() => activateSelectedEntry()}
          focus={listFocus}
          maxVisible={maxVisible}
          emptyMessage="No matching files or folders"
          renderItem={renderItem}
        />
      </Box>
    </Box>
  );
}
