#!/usr/bin/env python3
"""Generate .pyi stub files for @configurable decorated functions and classes.

This script scans Python files for @configurable decorators on functions,
methods, classes (with __init__), and dataclasses. It generates corresponding
type stub files with TypedDict configs and ConfigurableIndicator wrappers.
"""

from __future__ import annotations

import argparse
from pathlib import Path
import sys

from loguru import logger

from hipr.stubs import process_file


def _process_files(files: list[Path]) -> int:
  """Process a list of Python files and count generated stubs.

  Args:
    files: List of Python files to process

  Returns:
    Number of stub files generated
  """
  generated_count = 0

  for py_file in files:
    if "__pycache__" in str(py_file):
      logger.debug("Skipping cache directory: {}", py_file)
      continue

    if py_file.name.startswith("test_"):
      logger.debug("Skipping test file: {}", py_file)
      continue

    try:
      if process_file(py_file):
        generated_count += 1
    except SyntaxError as e:
      logger.error("Syntax error in {}: {}", py_file, e)
    except OSError as e:
      logger.error("I/O error reading {}: {}", py_file, e)
    except ValueError as e:
      logger.error("Invalid constraint in {}: {}", py_file, e)
    except (ImportError, AttributeError, RuntimeError) as e:
      # Specific errors that can occur during stub generation
      logger.error("Error processing {}: {}", py_file, e)

  return generated_count


def _resolve_paths(pattern: str) -> list[Path]:
  """Resolve a path pattern to a list of Python files.

  Args:
    pattern: A file path, directory, or glob pattern

  Returns:
    List of Python files to process
  """
  path = Path(pattern)

  # Case 1: It's an existing directory - recursively find all .py files
  if path.is_dir():
    return list(path.rglob("*.py"))

  # Case 2: It's an existing .py file
  if path.is_file() and path.suffix == ".py":
    return [path]

  # Case 3: It's a glob pattern - expand it
  # For absolute paths or patterns with directory components, we need to
  # split the base directory from the pattern
  pattern_path = Path(pattern)

  # Find the first component with glob characters
  parts = pattern_path.parts
  base_parts: list[str] = []
  glob_parts: list[str] = []
  found_glob = False

  for part in parts:
    if found_glob or "*" in part or "?" in part or "[" in part:
      found_glob = True
      glob_parts.append(part)
    else:
      base_parts.append(part)

  if glob_parts:
    # Reconstruct base path and glob pattern
    base_path = Path(*base_parts) if base_parts else Path()
    glob_pattern = str(Path(*glob_parts))

    if base_path.is_dir():
      return [
        p for p in base_path.glob(glob_pattern) if p.suffix == ".py" and p.is_file()
      ]

  # Fallback: try as-is from current directory (only for relative patterns)
  # For absolute paths that don't exist, Path().glob() raises NotImplementedError
  if pattern_path.is_absolute():
    return []  # Absolute path that doesn't exist as file/dir

  return [p for p in Path().glob(pattern) if p.suffix == ".py" and p.is_file()]


def main(argv: list[str] | None = None) -> int:
  """Scan files/directories and generate stubs for all @configurable functions.

  Args:
    argv: Command line arguments (uses sys.argv if None)

  Returns:
    Exit code (0 for success, 1 for error)
  """
  parser = argparse.ArgumentParser(
    description="Generate .pyi stub files for @configurable decorators",
    formatter_class=argparse.RawDescriptionHelpFormatter,
    epilog="""
Examples:
  # Generate stubs for src/ directory (default)
  hipr-generate-stubs

  # Generate stubs for a specific directory
  hipr-generate-stubs my_package/

  # Generate stubs for a single file
  hipr-generate-stubs my_module.py

  # Generate stubs for multiple files (shell glob expansion)
  hipr-generate-stubs src/*.py

  # Generate stubs for multiple specific files
  hipr-generate-stubs file1.py file2.py file3.py

  # Generate stubs using quoted glob pattern
  hipr-generate-stubs "src/**/*.py"
    """,
  )
  parser.add_argument(
    "patterns",
    nargs="*",
    default=["src"],
    help="Files, directories, or glob patterns to process (default: src)",
  )
  parser.add_argument(
    "--verbose",
    "-v",
    action="store_true",
    help="Enable verbose logging",
  )

  args = parser.parse_args(argv)

  # Extract typed values from argparse Namespace
  verbose: bool = bool(args.verbose)  # pyright: ignore[reportAny]
  patterns: list[str] = list(args.patterns)  # pyright: ignore[reportAny]

  if not verbose:
    logger.remove()  # Remove default handler
    logger.add(sys.stderr, level="INFO", format="<level>{message}</level>")

  # Resolve all patterns to a list of files
  all_files: list[Path] = []
  for pattern in patterns:
    all_files.extend(_resolve_paths(pattern))

  # Remove duplicates while preserving order
  seen: set[Path] = set()
  files: list[Path] = []
  for f in all_files:
    if f not in seen:
      seen.add(f)
      files.append(f)

  if not files:
    logger.error("No Python files found matching: {}", ", ".join(patterns))
    return 1

  logger.debug("Found {} Python file(s) to process", len(files))

  generated_count = _process_files(files)
  logger.info("Generated {} stub file(s)", generated_count)
  return 0


if __name__ == "__main__":
  sys.exit(main())
