content: import json
  import os
  import shutil
  import tarfile
  import uuid
  from pathlib import Path
  from typing import Any, Optional, Tuple
  from urllib.parse import urlparse, urlunparse
  
  import requests
  import requests.auth
  from fastapi import HTTPException
  from git import GitCommandError, InvalidGitRepositoryError, Repo
  from sqlalchemy.orm import Session
  
  from app.modules.code_provider.code_provider_service import CodeProviderService
  from app.modules.parsing.graph_construction.parsing_schema import RepoDetails
  from app.modules.parsing.utils.repo_name_normalizer import normalize_repo_name
  from app.modules.projects.projects_schema import ProjectStatusEnum
  from app.modules.projects.projects_service import ProjectService
  from app.modules.utils.logger import setup_logger
  
  logger = setup_logger(__name__)
  
  
  class ParsingServiceError(Exception):
      """Base exception class for ParsingService errors."""
  
  
  class ParsingFailedError(ParsingServiceError):
      """Raised when a parsing fails."""
  
  
  class ParseHelper:
      def __init__(self, db_session: Session):
          self.project_manager = ProjectService(db_session)
          self.db = db_session
          self.github_service = CodeProviderService(db_session)
  
          # Initialize repo manager if enabled
          self.repo_manager = None
          try:
              repo_manager_enabled = (
                  os.getenv("REPO_MANAGER_ENABLED", "false").lower() == "true"
              )
              if repo_manager_enabled:
                  from app.modules.repo_manager import RepoManager
  
                  self.repo_manager = RepoManager()
                  logger.info("RepoManager initialized in ParseHelper")
          except Exception as e:
              logger.warning(f"Failed to initialize RepoManager: {e}")
  
      @staticmethod
      def get_directory_size(path):
          total_size = 0
          for dirpath, dirnames, filenames in os.walk(path):
              for f in filenames:
                  fp = os.path.join(dirpath, f)
                  total_size += os.path.getsize(fp)
          return total_size
  
      async def clone_or_copy_repository(
          self, repo_details: RepoDetails, user_id: str
      ) -> Tuple[Any, str, Any]:
          owner = None
          auth = None
          repo = None
  
          if repo_details.repo_path:
              if not os.path.exists(repo_details.repo_path):
                  raise HTTPException(
                      status_code=400,
                      detail="Local repository does not exist on the given path",
                  )
              repo = Repo(repo_details.repo_path)
              logger.info(
                  f"ParsingHelper: clone_or_copy_repository created local Repo object for path: {repo_details.repo_path}"
              )
          else:
              try:
                  github, repo = self.github_service.get_repo(repo_details.repo_name)
                  owner = repo.owner.login
  
                  # Extract auth from the Github client
                  # The auth is stored in the _Github__requester.auth attribute
                  if hasattr(github, "_Github__requester") and hasattr(
                      github._Github__requester, "auth"
                  ):
                      auth = github._Github__requester.auth
                  elif hasattr(github, "get_app_auth"):
                      # Fallback for older method
                      auth = github.get_app_auth()
                  else:
                      logger.warning(
                          f"Could not extract auth from GitHub client for {repo_details.repo_name}"
                      )
              except HTTPException as he:
                  raise he
              except Exception:
                  logger.exception("Failed to fetch repository")
                  raise HTTPException(
                      status_code=404,
                      detail="Repository not found or inaccessible on GitHub",
                  )
  
          return repo, owner, auth
  
      def is_text_file(self, file_path):
          def open_text_file(file_path):
              """
              Try multiple encodings to detect if file is text.
  
              Order of encodings to try:
              1. utf-8 (most common)
              2. utf-8-sig (UTF-8 with BOM)
              3. utf-16 (common in Windows C# files)
              4. latin-1/iso-8859-1 (fallback, accepts all byte sequences)
              """
              encodings = ["utf-8", "utf-8-sig", "utf-16", "latin-1"]
  
              for encoding in encodings:
                  try:
                      with open(file_path, "r", encoding=encoding) as f:
                          # Read first 8KB to detect encoding
                          f.read(8192)
                      return True
                  except (UnicodeDecodeError, UnicodeError):
                      continue
                  except Exception:
                      # Handle other errors (permissions, file not found, etc.)
                      return False
  
              # If all encodings fail, likely a binary file
              return False
  
          ext = file_path.split(".")[-1]
          exclude_extensions = [
              "png",
              "jpg",
              "jpeg",
              "gif",
              "bmp",
              "tiff",
              "webp",
              "ico",
              "svg",
              "mp4",
              "avi",
              "mov",
              "wmv",
              "flv",
              "ipynb",
          ]
          include_extensions = [
              "py",
              "js",
              "ts",
              "c",
              "cs",
              "cpp",
              "h",
              "hpp",
              "el",
              "ex",
              "exs",
              "elm",
              "go",
              "java",
              "ml",
              "mli",
              "php",
              "ql",
              "rb",
              "rs",
              "md",
              "txt",
              "json",
              "yaml",
              "yml",
              "toml",
              "ini",
              "cfg",
              "conf",
              "xml",
              "html",
              "css",
              "sh",
              "ps1",
              "psm1",
              "md",
              "mdx",
              "xsq",
              "proto",
          ]
          if ext in exclude_extensions:
              return False
          elif ext in include_extensions or open_text_file(file_path):
              return True
          else:
              return False
  
      async def download_and_extract_tarball(
          self, repo, branch, target_dir, auth, repo_details, user_id
      ):
          # Get repo name for logging - handle both Repo objects and repo objects with full_name
          repo_name = (
              repo.working_tree_dir
              if isinstance(repo, Repo)
              else getattr(repo, "full_name", "unknown")
          )
  
          logger.info(
              f"ParsingHelper: Starting tarball download for repo '{repo_name}', branch '{branch}'"
          )
  
          try:
              logger.info(
                  f"ParsingHelper: Getting archive link for repo '{repo_name}', branch '{branch}'"
              )
              tarball_url = repo.get_archive_link("tarball", branch)
              logger.info(f"ParsingHelper: Retrieved tarball URL: {tarball_url}")
  
              # Validate that tarball_url is a string, not an exception object
              if not isinstance(tarball_url, str):
                  logger.error(
                      f"ParsingHelper: Invalid tarball URL type: {type(tarball_url)}, value: {tarball_url}"
                  )
                  raise ValueError(
                      f"Expected string URL, got {type(tarball_url)}: {tarball_url}"
                  )
  
              # For GitBucket private repos, use PyGithub client's requester for authenticated requests
              # According to GitBucket API docs: https://github.com/gitbucket/gitbucket/wiki/API-WebHook
              # Authentication: "Authorization: token YOUR_TOKEN" in header
              provider_type = os.getenv("CODE_PROVIDER", "github").lower()
  
              if (
                  provider_type == "gitbucket"
                  and hasattr(repo, "_provider")
                  and repo._provider
              ):
                  # For GitBucket, use the provider's authentication
                  # According to GitBucket API docs: https://github.com/gitbucket/gitbucket/wiki/API-WebHook
                  # Authentication format: "Authorization: token YOUR_TOKEN" in header
                  try:
                      github_client = repo._provider.client
                      if hasattr(github_client, "_Github__requester"):
                          requester = github_client._Github__requester
  
                          # Use the requester's session which has authentication already configured
                          if hasattr(requester, "_Requester__session"):
                              session = requester._Requester__session
                              response = session.get(tarball_url, stream=True, timeout=30)
  
                              # If we get 401, the session auth might not be working, fall back to manual token
                              if response.status_code == 401:
                                  raise requests.exceptions.HTTPError(
                                      "401 Unauthorized from session"
                                  )
                          else:
                              raise AttributeError("Requester session not available")
                      else:
                          raise AttributeError("Requester not found")
                  except Exception:
                      # Fallback to manual token extraction
                      token = None
                      headers = {}
  
                      # Priority 1: Try to get token from auth parameter
                      if auth and hasattr(auth, "token"):
                          token = auth.token
  
                      # Priority 2: Try to extract from PyGithub client's requester
                      if not token and hasattr(repo, "_provider") and repo._provider:
                          try:
                              github_client = repo._provider.client
                              if hasattr(github_client, "_Github__requester"):
                                  requester = github_client._Github__requester
  
                                  if hasattr(requester, "auth") and hasattr(
                                      requester.auth, "token"
                                  ):
                                      token = requester.auth.token
                                  elif hasattr(
                                      requester, "_Requester__authorizationHeader"
                                  ):
                                      auth_header = (
                                          requester._Requester__authorizationHeader
                                      )
                                      if auth_header:
                                          if auth_header.startswith("token "):
                                              token = auth_header[6:]
                                          elif auth_header.startswith("Bearer "):
                                              token = auth_header[7:]
                          except Exception:
                              pass  # Token extraction failed, will try next method
  
                      # Priority 3: Fallback to environment variable
                      if not token:
                          token = os.getenv("CODE_PROVIDER_TOKEN")
  
                      if not token:
                          error_msg = "No authentication token available for GitBucket archive download"
                          logger.error(f"ParsingHelper: {error_msg}")
                          raise ValueError(error_msg)
  
                      # GitBucket web endpoints (archive downloads) may require Basic Auth
                      # Try token header format first (API standard per GitBucket docs)
                      headers = {"Authorization": f"token {token}"}
                      logger.debug(
                          "ParsingHelper: Attempting archive download with token header"
                      )
  
                      response = requests.get(
                          tarball_url, stream=True, headers=headers, timeout=30
                      )
  
                      # If token header fails with 401, try Basic Auth with repo owner username
                      # GitBucket web endpoints sometimes require Basic Auth (supported since v4.3)
                      if response.status_code == 401:
                          logger.debug(
                              "ParsingHelper: Token header auth failed, trying Basic Auth"
                          )
                          response.close()
  
                          # Try Basic Auth with repo owner username and token as password
                          if hasattr(repo, "owner") and hasattr(repo.owner, "login"):
                              username = repo.owner.login
                              basic_auth = requests.auth.HTTPBasicAuth(username, token)
                              response = requests.get(
                                  tarball_url, stream=True, auth=basic_auth, timeout=30
                              )
                              logger.debug(
                                  f"ParsingHelper: Basic Auth response status: {response.status_code}"
                              )
              else:
                  # For GitHub and other providers, use standard token auth
                  headers = {}
                  if auth:
                      headers = {"Authorization": f"token {auth.token}"}
                  response = requests.get(
                      tarball_url, stream=True, headers=headers, timeout=30
                  )
  
              response.raise_for_status()
  
          except requests.exceptions.HTTPError as e:
              # If we get 401, archive download might not be supported for private repos
              # Fall back to git clone for GitBucket
              status_code = None
              if hasattr(e, "response") and e.response is not None:
                  status_code = e.response.status_code
              elif "401" in str(e):
                  status_code = 401
  
              if status_code == 401:
                  provider_type = os.getenv("CODE_PROVIDER", "github").lower()
                  if provider_type == "gitbucket":
                      logger.info(
                          "ParsingHelper: Archive download failed with 401 for GitBucket private repo, "
                          "falling back to git clone"
                      )
                      return await self._clone_repository_with_auth(
                          repo, branch, target_dir, user_id
                      )
  
              logger.exception("ParsingHelper: Failed to download repository archive")
              raise ParsingFailedError("Failed to download repository archive") from e
          except requests.exceptions.RequestException as e:
              logger.exception("ParsingHelper: Error fetching tarball")
              raise ParsingFailedError("Failed to download repository archive") from e
          except Exception as e:
              logger.exception("ParsingHelper: Unexpected error in tarball download")
              raise ParsingFailedError(
                  "Unexpected error during repository download"
              ) from e
          tarball_path = os.path.join(
              target_dir,
              f"{repo.full_name.replace('/', '-').replace('.', '-')}-{branch.replace('/', '-').replace('.', '-')}.tar.gz",
          )
  
          final_dir = os.path.join(
              target_dir,
              f"{repo.full_name.replace('/', '-').replace('.', '-')}-{branch.replace('/', '-').replace('.', '-')}-{user_id}",
          )
  
          logger.info(f"ParsingHelper: Tarball path: {tarball_path}")
          logger.info(f"ParsingHelper: Final directory: {final_dir}")
  
          try:
              logger.info(f"ParsingHelper: Writing tarball to {tarball_path}")
              with open(tarball_path, "wb") as f:
                  for chunk in response.iter_content(chunk_size=8192):
                      f.write(chunk)
              tarball_size = os.path.getsize(tarball_path)
              logger.info(
                  f"ParsingHelper: Successfully downloaded tarball, size: {tarball_size} bytes"
              )
  
              # Validate tarball size - very small files are likely error responses
              if tarball_size < 100:
                  error_msg = (
                      f"Tarball is suspiciously small ({tarball_size} bytes). "
                      "This may indicate an error response from the server or an empty repository."
                  )
                  logger.error(f"ParsingHelper: {error_msg}")
                  raise ParsingFailedError(error_msg)
  
              logger.info(f"ParsingHelper: Extracting tarball to {final_dir}")
              try:
                  with tarfile.open(tarball_path, "r:gz") as tar:
                      # Validate that the tarball is not empty
                      if not tar.getmembers():
                          error_msg = "Tarball contains no files"
                          logger.error(f"ParsingHelper: {error_msg}")
                          raise ParsingFailedError(error_msg)
  
                      temp_dir = os.path.join(final_dir, "temp_extract")
                      os.makedirs(temp_dir, exist_ok=True)
                      tar.extractall(path=temp_dir)
                      logger.info(
                          f"ParsingHelper: Extracted tarball contents to {temp_dir}"
                      )
  
                      # Check if extraction directory has contents
                      extracted_contents = os.listdir(temp_dir)
                      if not extracted_contents:
                          error_msg = (
                              "Tarball extraction resulted in empty directory. "
                              "The archive may be corrupted or the repository may be empty."
                          )
                          logger.error(f"ParsingHelper: {error_msg}")
                          raise ParsingFailedError(error_msg)
  
                      extracted_dir = os.path.join(temp_dir, extracted_contents[0])
                      logger.info(
                          f"ParsingHelper: Main extracted directory: {extracted_dir}"
                      )
  
                      text_files_count = 0
                      for root, dirs, files in os.walk(extracted_dir):
                          for file in files:
                              if file.startswith("."):
                                  continue
                              file_path = os.path.join(root, file)
                              if self.is_text_file(file_path):
                                  try:
                                      relative_path = os.path.relpath(
                                          file_path, extracted_dir
                                      )
                                      dest_path = os.path.join(final_dir, relative_path)
                                      os.makedirs(
                                          os.path.dirname(dest_path), exist_ok=True
                                      )
                                      shutil.copy2(file_path, dest_path)
                                      text_files_count += 1
                                  except (shutil.Error, OSError):
                                      logger.exception(
                                          "ParsingHelper: Error copying file",
                                          file_path=file_path,
                                      )
  
                      logger.info(
                          f"ParsingHelper: Copied {text_files_count} text files to final directory"
                      )
                      # Remove the temporary directory
                      try:
                          shutil.rmtree(temp_dir)
                      except OSError:
                          logger.exception("Error removing temporary directory")
                          pass
              except tarfile.TarError as e:
                  error_msg = f"Failed to extract tarball: {e}. The archive may be corrupted or invalid."
                  logger.error(f"ParsingHelper: {error_msg}")
                  raise ParsingFailedError(error_msg) from e
  
          except (IOError, tarfile.TarError, shutil.Error) as e:
              logger.exception("Error handling tarball")
              raise ParsingFailedError("Failed to process repository archive") from e
          finally:
              if os.path.exists(tarball_path):
                  os.remove(tarball_path)
  
          return final_dir
  
      async def _clone_repository_with_auth(self, repo, branch, target_dir, user_id):
          """
          Clone repository using git with authentication.
          Fallback method when archive download fails for private repos.
  
          Requires GITBUCKET_USERNAME and GITBUCKET_PASSWORD environment variables.
  
          This method clones to a temporary directory, filters text files using is_text_file(),
          and copies only text files to the final directory to prevent binary file parsing errors.
          """
          repo_name = (
              repo.working_tree_dir
              if isinstance(repo, Repo)
              else getattr(repo, "full_name", "unknown")
          )
  
          logger.info(
              f"ParsingHelper: Cloning repository '{repo_name}' branch '{branch}' using git"
          )
  
          final_dir = os.path.join(
              target_dir,
              f"{repo.full_name.replace('/', '-').replace('.', '-')}-{branch.replace('/', '-').replace('.', '-')}-{user_id}",
          )
  
          # Create temporary clone directory
          temp_clone_dir = os.path.join(target_dir, f"{uuid.uuid4()}_temp_clone")
  
          # Get credentials from environment variables
          username = os.getenv("GITBUCKET_USERNAME")
          password = os.getenv("GITBUCKET_PASSWORD")
  
          if not username or not password:
              error_msg = (
                  "GITBUCKET_USERNAME and GITBUCKET_PASSWORD environment variables "
                  "are required for cloning private GitBucket repositories"
              )
              logger.error(f"ParsingHelper: {error_msg}")
              raise ParsingFailedError(error_msg)
  
          # Construct GitBucket clone URL with embedded credentials
          # Format: http://username:password@hostname/path/owner/repo.git
          base_url = os.getenv("CODE_PROVIDER_BASE_URL", "http://localhost:8080")
          if base_url.endswith("/api/v3"):
              base_url = base_url[:-7]  # Remove '/api/v3'
  
          parsed = urlparse(base_url)
          # Preserve the path component from base URL (e.g., /gitbucket)
          base_path = parsed.path.rstrip("/")  # Remove trailing slash if present
          repo_path = (
              f"{base_path}/{repo.full_name}.git"
              if base_path
              else f"/{repo.full_name}.git"
          )
  
          clone_url_with_auth = urlunparse(
              (
                  parsed.scheme,
                  f"{username}:{password}@{parsed.netloc}",
                  repo_path,
                  "",
                  "",
                  "",
              )
          )
  
          # Log URL without credentials for security
          safe_url = urlunparse((parsed.scheme, parsed.netloc, repo_path, "", "", ""))
          logger.info(f"ParsingHelper: Cloning from {safe_url}")
  
          try:
              # Clone the repository to temporary directory with shallow clone for faster download
              _ = Repo.clone_from(
                  clone_url_with_auth, temp_clone_dir, branch=branch, depth=1
              )
              logger.info(
                  f"ParsingHelper: Successfully cloned repository to temporary directory: {temp_clone_dir}"
              )
  
              # Filter and copy only text files to final directory
              logger.info(
                  f"ParsingHelper: Filtering text files from clone to {final_dir}"
              )
              os.makedirs(final_dir, exist_ok=True)
  
              text_files_count = 0
              for root, dirs, files in os.walk(temp_clone_dir):
                  # Skip .git directory
                  if ".git" in root.split(os.sep):
                      continue
  
                  # Skip hidden directories
                  if any(part.startswith(".") for part in root.split(os.sep)):
                      continue
  
                  for file in files:
                      # Skip hidden files
                      if file.startswith("."):
                          continue
  
                      file_path = os.path.join(root, file)
  
                      # Filter using is_text_file check
                      if self.is_text_file(file_path):
                          try:
                              # Calculate relative path from clone root
                              relative_path = os.path.relpath(file_path, temp_clone_dir)
                              dest_path = os.path.join(final_dir, relative_path)
  
                              # Create destination directory structure
                              os.makedirs(os.path.dirname(dest_path), exist_ok=True)
  
                              # Copy text file to final directory
                              shutil.copy2(file_path, dest_path)
                              text_files_count += 1
                          except (shutil.Error, OSError) as e:
                              logger.error(
                                  f"ParsingHelper: Error copying file {file_path}: {e}"
                              )
  
              logger.info(
                  f"ParsingHelper: Copied {text_files_count} text files from git clone to final directory"
              )
  
              # Clean up temporary clone directory
              try:
                  shutil.rmtree(temp_clone_dir)
                  logger.info(
                      f"ParsingHelper: Cleaned up temporary clone directory: {temp_clone_dir}"
                  )
              except Exception as e:
                  logger.warning(
                      f"ParsingHelper: Failed to clean up temp clone directory: {e}"
                  )
  
              return final_dir
  
          except GitCommandError as e:
              logger.exception("ParsingHelper: Git clone failed")
              # Clean up temp directory on error
              if os.path.exists(temp_clone_dir):
                  try:
                      shutil.rmtree(temp_clone_dir)
                  except Exception:
                      pass
              raise ParsingFailedError(f"Failed to clone repository: {e}") from e
          except Exception as e:
              logger.exception("ParsingHelper: Unexpected error during git clone")
              # Clean up temp directory on error
              if os.path.exists(temp_clone_dir):
                  try:
                      shutil.rmtree(temp_clone_dir)
                  except Exception:
                      pass
              raise ParsingFailedError(
                  f"Unexpected error during repository clone: {e}"
              ) from e
  
      @staticmethod
      def detect_repo_language(repo_dir):
          lang_count = {
              "c_sharp": 0,
              "c": 0,
              "cpp": 0,
              "elisp": 0,
              "elixir": 0,
              "elm": 0,
              "go": 0,
              "java": 0,
              "javascript": 0,
              "ocaml": 0,
              "php": 0,
              "python": 0,
              "ql": 0,
              "ruby": 0,
              "rust": 0,
              "typescript": 0,
              "markdown": 0,
              "xml": 0,
              "other": 0,
          }
          total_chars = 0
  
          try:
              for root, _, files in os.walk(repo_dir):
                  if any(part.startswith(".") for part in root.split(os.sep)):
                      continue
  
                  for file in files:
                      file_path = os.path.join(root, file)
                      ext = os.path.splitext(file)[1].lower()
  
                      # Try multiple encodings for robustness
                      content = None
                      encodings = ["utf-8", "utf-8-sig", "utf-16", "latin-1"]
  
                      for encoding in encodings:
                          try:
                              with open(file_path, "r", encoding=encoding) as f:
                                  content = f.read()
                                  break
                          except (UnicodeDecodeError, UnicodeError):
                              continue
                          except Exception:
                              break
  
                      if content is not None:
                          try:
                              total_chars += len(content)
                              if ext == ".cs":
                                  lang_count["c_sharp"] += 1
                              elif ext == ".c":
                                  lang_count["c"] += 1
                              elif ext in [".cpp", ".cxx", ".cc"]:
                                  lang_count["cpp"] += 1
                              elif ext == ".el":
                                  lang_count["elisp"] += 1
                              elif ext == ".ex" or ext == ".exs":
                                  lang_count["elixir"] += 1
                              elif ext == ".elm":
                                  lang_count["elm"] += 1
                              elif ext == ".go":
                                  lang_count["go"] += 1
                              elif ext == ".java":
                                  lang_count["java"] += 1
                              elif ext in [".js", ".jsx"]:
                                  lang_count["javascript"] += 1
                              elif ext == ".ml" or ext == ".mli":
                                  lang_count["ocaml"] += 1
                              elif ext == ".php":
                                  lang_count["php"] += 1
                              elif ext == ".py":
                                  lang_count["python"] += 1
                              elif ext == ".ql":
                                  lang_count["ql"] += 1
                              elif ext == ".rb":
                                  lang_count["ruby"] += 1
                              elif ext == ".rs":
                                  lang_count["rust"] += 1
                              elif ext in [".ts", ".tsx"]:
                                  lang_count["typescript"] += 1
                              elif ext in [".md", ".mdx"]:
                                  lang_count["markdown"] += 1
                              elif ext in [".xml", ".xsq"]:
                                  lang_count["xml"] += 1
                              # else:
                              #     lang_count["other"] += 1
                          except Exception as e:
                              logger.warning(f"Error processing file {file_path}: {e}")
                              continue
                      else:
                          logger.warning(
                              f"Could not read file with any encoding: {file_path}"
                          )
                          continue
          except (TypeError, FileNotFoundError, PermissionError):
              logger.exception("Error accessing directory", repo_dir=repo_dir)
  
          # Determine the predominant language based on counts
          predominant_language = max(lang_count, key=lang_count.get)
          return predominant_language if lang_count[predominant_language] > 0 else "other"
  
      async def setup_project_directory(
          self,
          repo,
          branch,
          auth,
          repo_details,
          user_id,
          project_id=None,  # Change type to str
          commit_id=None,
      ):
          # Check if this is a local repository by examining the repo object
          # In development mode: repo is Repo object, repo_details is ParsingRequest
          # In non-development mode: both repo and repo_details can be Repo objects
          logger.info(
              f"ParsingHelper: setup_project_directory called with repo type: {type(repo).__name__}, "
              f"repo_details type: {type(repo_details).__name__}"
          )
  
          if isinstance(repo, Repo):
              # Local repository - use full path from Repo object
              repo_path = repo.working_tree_dir
              full_name = repo_path.split("/")[
                  -1
              ]  # Extract just the directory name for display
              logger.info(
                  f"ParsingHelper: Detected local repository at {repo_path} with name {full_name}"
              )
          elif isinstance(repo_details, Repo):
              # Alternative: repo_details is the Repo object (non-dev mode)
              repo_path = repo_details.working_tree_dir
              full_name = repo_path.split("/")[-1]
              logger.info(
                  f"ParsingHelper: Detected local repository at {repo_path} with name {full_name}"
              )
          else:
              # Remote repository - get name from repo_details (ParsingRequest)
              repo_path = None
              if hasattr(repo_details, "repo_name"):
                  full_name = repo_details.repo_name
              else:
  