content: """
  Code Changes Manager Tool for Agent State Management
  
  This tool allows agents to manage code changes in memory, reducing token usage
  by storing code modifications separately from response text. Changes are tracked
  per-file and can be searched, retrieved, and serialized for persistence.
  """
  
  import uuid
  import re
  import json
  import os
  import inspect
  import functools
  import difflib
  from datetime import datetime
  from typing import Dict, List, Optional, Any, Union
  from enum import Enum
  from dataclasses import dataclass, asdict
  from pydantic import BaseModel, Field
  from sqlalchemy.orm import Session
  from app.modules.utils.logger import setup_logger
  
  logger = setup_logger(__name__)
  
  
  class ChangeType(str, Enum):
      """Type of code change"""
  
      ADD = "add"  # New file
      UPDATE = "update"  # Modified file
      DELETE = "delete"  # Deleted file
  
  
  @dataclass
  class FileChange:
      """Represents a change to a single file"""
  
      file_path: str
      change_type: ChangeType
      content: Optional[str] = None  # None for DELETE
      previous_content: Optional[str] = None  # For UPDATE/DELETE
      created_at: str = ""
      updated_at: str = ""
      description: Optional[str] = None  # Optional change description
  
      def __post_init__(self):
          now = datetime.now().isoformat()
          if not self.created_at:
              self.created_at = now
          if not self.updated_at:
              self.updated_at = now
  
  
  class CodeChangesManager:
      """Manages code changes in memory for a session"""
  
      def __init__(self):
          self.changes: Dict[str, FileChange] = {}  # file_path -> FileChange
          self.session_id = str(uuid.uuid4())[:8]
  
      def add_file(
          self,
          file_path: str,
          content: str,
          description: Optional[str] = None,
      ) -> bool:
          """Add a new file"""
          logger.info(
              f"CodeChangesManager.add_file: Adding file '{file_path}' (content length: {len(content)} chars)"
          )
          if (
              file_path in self.changes
              and self.changes[file_path].change_type != ChangeType.DELETE
          ):
              logger.warning(
                  f"CodeChangesManager.add_file: File '{file_path}' already exists (not deleted)"
              )
              return False  # File already exists (not deleted)
  
          change = FileChange(
              file_path=file_path,
              change_type=ChangeType.ADD,
              content=content,
              description=description,
          )
          self.changes[file_path] = change
          logger.info(
              f"CodeChangesManager.add_file: Successfully added file '{file_path}' (session: {self.session_id})"
          )
          return True
  
      def _get_current_content(
          self,
          file_path: str,
          project_id: Optional[str] = None,
          db: Optional[Session] = None,
      ) -> str:
          """Get current content of a file (from changes, repository via code provider, filesystem, or empty if new)"""
          logger.info(
              f"CodeChangesManager._get_current_content: Getting content for '{file_path}' "
              f"(project_id={project_id}, db={'provided' if db else 'None'})"
          )
  
          if file_path in self.changes:
              existing = self.changes[file_path]
              if existing.change_type == ChangeType.DELETE:
                  logger.info(
                      f"CodeChangesManager._get_current_content: File '{file_path}' is marked as deleted"
                  )
                  return ""  # Deleted file has no content
              content = existing.content or ""
              logger.info(
                  f"CodeChangesManager._get_current_content: Retrieved '{file_path}' from changes "
                  f"({len(content)} chars, {len(content.split(chr(10)))} lines)"
              )
              return content
  
          # If file not in changes, try to fetch from repository using code provider
          if project_id and db:
              logger.info(
                  f"CodeChangesManager._get_current_content: Attempting to fetch '{file_path}' "
                  f"from repository using project_id={project_id}"
              )
              try:
                  from app.modules.code_provider.code_provider_service import (
                      CodeProviderService,
                  )
                  from app.modules.projects.projects_service import ProjectService
  
                  project_service = ProjectService(db)
                  # Project.id is Text (string) in the database, so query directly with the string project_id
                  # Note: get_project_from_db_by_id_sync has incorrect type hint (int), but actually accepts string
                  logger.debug(
                      f"CodeChangesManager._get_current_content: Fetching project details for project_id={project_id} (type: {type(project_id).__name__})"
                  )
                  try:
                      # Query directly - Project.id is Text column, so string works fine
                      # The method type hint says int, but the actual column accepts strings
                      from app.modules.projects.projects_model import Project
  
                      project = db.query(Project).filter(Project.id == project_id).first()
                      if project:
                          project_details = {
                              "project_name": project.repo_name,
                              "id": project.id,
                              "commit_id": project.commit_id,
                              "status": project.status,
                              "branch_name": project.branch_name,
                              "repo_path": project.repo_path,
                              "user_id": project.user_id,
                          }
                          logger.debug(
                              f"CodeChangesManager._get_current_content: Project details retrieved: "
                              f"repo={project_details.get('project_name')}, branch={project_details.get('branch_name')}"
                          )
                      else:
                          logger.warning(
                              f"CodeChangesManager._get_current_content: Project not found in database for project_id={project_id}"
                          )
                          project_details = None
                  except Exception as e:
                      logger.warning(
                          f"CodeChangesManager._get_current_content: Error querying project for project_id '{project_id}': {e}",
                          exc_info=True,
                      )
                      project_details = None
  
                  if project_details and "project_name" in project_details:
                      cp_service = CodeProviderService(db)
                      logger.debug(
                          f"CodeChangesManager._get_current_content: Fetching file content from repository "
                          f"for '{file_path}' in repo '{project_details['project_name']}'"
                      )
                      repo_content = cp_service.get_file_content(
                          repo_name=project_details["project_name"],
                          file_path=file_path,
                          branch_name=project_details.get("branch_name"),
                          start_line=None,
                          end_line=None,
                          project_id=project_id,
                          commit_id=project_details.get("commit_id"),
                      )
                      if repo_content:
                          lines = repo_content.split("\n")
                          logger.info(
                              f"CodeChangesManager._get_current_content: Successfully retrieved '{file_path}' "
                              f"from repository via code provider ({len(repo_content)} chars, {len(lines)} lines)"
                          )
                          return repo_content
                      else:
                          logger.warning(
                              f"CodeChangesManager._get_current_content: Repository returned empty content for '{file_path}'"
                          )
                  else:
                      logger.warning(
                          f"CodeChangesManager._get_current_content: Cannot fetch from repository - "
                          f"project_details={'missing project_name' if project_details else 'None'}"
                      )
              except Exception as e:
                  logger.warning(
                      f"CodeChangesManager._get_current_content: Error fetching '{file_path}' from repository: {str(e)}",
                      exc_info=True,
                  )
                  # Fall through to try filesystem
  
          # If not available via code provider, try to read from filesystem
          logger.debug(
              f"CodeChangesManager._get_current_content: Attempting to read '{file_path}' from filesystem"
          )
          codebase_content = self._read_file_from_codebase(file_path)
          if codebase_content is not None:
              lines = codebase_content.split("\n")
              logger.info(
                  f"CodeChangesManager._get_current_content: Retrieved '{file_path}' from filesystem "
                  f"({len(codebase_content)} chars, {len(lines)} lines)"
              )
              return codebase_content
          else:
              logger.warning(
                  f"CodeChangesManager._get_current_content: File '{file_path}' not found in filesystem"
              )
  
          # File doesn't exist in changes, repository, or filesystem - treat as new file
          logger.warning(
              f"CodeChangesManager._get_current_content: File '{file_path}' not found anywhere - treating as new file (empty content)"
          )
          return ""
  
      def _apply_update(
          self,
          file_path: str,
          new_content: str,
          description: Optional[str] = None,
          preserve_previous: bool = True,
      ) -> bool:
          """Internal method to apply a content update"""
          logger.debug(
              f"CodeChangesManager._apply_update: Applying update to '{file_path}' (preserve_previous={preserve_previous})"
          )
          previous_content = None
          if file_path in self.changes:
              existing = self.changes[file_path]
              if preserve_previous and existing.previous_content:
                  previous_content = existing.previous_content
              elif preserve_previous:
                  previous_content = existing.content
              # Update the change
              existing.content = new_content
              existing.change_type = ChangeType.UPDATE
              existing.updated_at = datetime.now().isoformat()
              if description:
                  existing.description = description
              if previous_content:
                  existing.previous_content = previous_content
          else:
              # New file in changes (not yet committed)
              change = FileChange(
                  file_path=file_path,
                  change_type=ChangeType.UPDATE,
                  content=new_content,
                  previous_content=previous_content,
                  description=description,
              )
              self.changes[file_path] = change
          return True
  
      def update_file(
          self,
          file_path: str,
          content: str,
          description: Optional[str] = None,
          preserve_previous: bool = True,
      ) -> bool:
          """Update an existing file with full content"""
          logger.info(
              f"CodeChangesManager.update_file: Updating file '{file_path}' with full content (content length: {len(content)} chars)"
          )
          result = self._apply_update(file_path, content, description, preserve_previous)
          logger.info(
              f"CodeChangesManager.update_file: Successfully updated file '{file_path}'"
          )
          return result
  
      def update_file_lines(
          self,
          file_path: str,
          start_line: int,
          end_line: Optional[int] = None,
          new_content: str = "",
          description: Optional[str] = None,
          project_id: Optional[str] = None,
          db: Optional[Session] = None,
      ) -> Dict[str, Any]:
          """
          Update specific lines in a file (1-indexed)
  
          Args:
              file_path: Path to the file
              start_line: Starting line number (1-indexed, inclusive)
              end_line: Ending line number (1-indexed, inclusive). If None, only start_line is replaced
              new_content: Content to replace the lines with
              description: Optional description of the change
  
          Returns:
              Dict with success status and information about the change
          """
          logger.info(
              f"CodeChangesManager.update_file_lines: Updating lines {start_line}-{end_line or start_line} in '{file_path}' "
              f"(project_id={project_id}, db={'provided' if db else 'None'})"
          )
          try:
              current_content = self._get_current_content(
                  file_path, project_id=project_id, db=db
              )
              lines = current_content.split("\n")
              logger.info(
                  f"CodeChangesManager.update_file_lines: Retrieved content for '{file_path}' - "
                  f"{len(lines)} lines, content length: {len(current_content)} chars"
              )
  
              if len(lines) == 1 and lines[0] == "":
                  logger.warning(
                      f"CodeChangesManager.update_file_lines: WARNING - File '{file_path}' appears to be empty or only contains empty string!"
                  )
  
              # Validate line numbers (1-indexed)
              logger.debug(
                  f"CodeChangesManager.update_file_lines: Validating start_line={start_line} against file with {len(lines)} lines"
              )
              if start_line < 1 or start_line > len(lines):
                  return {
                      "success": False,
                      "error": f"Invalid start_line {start_line}. File has {len(lines)} lines.",
                  }
  
              if end_line is None:
                  end_line = start_line
  
              if end_line < start_line:
                  return {
                      "success": False,
                      "error": f"end_line ({end_line}) must be >= start_line ({start_line})",
                  }
  
              if end_line > len(lines):
                  return {
                      "success": False,
                      "error": f"Invalid end_line {end_line}. File has {len(lines)} lines.",
                  }
  
              # Convert to 0-indexed for list operations
              start_idx = start_line - 1
              end_idx = end_line  # exclusive for slicing
  
              # Get the lines being replaced (for context)
              replaced_lines = lines[start_idx:end_idx]
              replaced_content = "\n".join(replaced_lines)
  
              # Split new_content into lines
              new_lines = new_content.split("\n")
  
              # Replace the lines
              updated_lines = lines[:start_idx] + new_lines + lines[end_idx:]
              updated_content = "\n".join(updated_lines)
  
              # Apply the update
              change_desc = description or f"Updated lines {start_line}-{end_line}"
              self._apply_update(file_path, updated_content, change_desc)
  
              # Calculate context around updated area
              context_lines_before = 3
              context_lines_after = 3
  
              # Get context before
              context_start_idx = max(0, start_idx - context_lines_before)
              context_lines_before_list = lines[context_start_idx:start_idx]
  
              # Get context after (in the updated file)
              context_after_start_idx = start_idx + len(new_lines)
              context_after_end_idx = min(
                  len(updated_lines), context_after_start_idx + context_lines_after
              )
              context_lines_after_list = updated_lines[
                  context_after_start_idx:context_after_end_idx
              ]
  
              # Get the updated section with context
              context_with_updated = (
                  context_lines_before_list + new_lines + context_lines_after_list
              )
  
              # Calculate line numbers for context display
              context_start_line = context_start_idx + 1
              context_end_line = context_after_end_idx
  
              logger.info(
                  f"CodeChangesManager.update_file_lines: Successfully updated lines {start_line}-{end_line} in '{file_path}' (replaced {len(replaced_lines)} lines with {len(new_lines)} new lines)"
              )
              return {
                  "success": True,
                  "file_path": file_path,
                  "start_line": start_line,
                  "end_line": end_line,
                  "lines_replaced": len(replaced_lines),
                  "lines_added": len(new_lines),
                  "replaced_content": replaced_content,
                  "updated_context": "\n".join(context_with_updated),
                  "context_start_line": context_start_line,
                  "context_end_line": context_end_line,
              }
          except Exception as e:
              logger.exception(
                  "CodeChangesManager.update_file_lines: Error updating lines",
                  file_path=file_path,
              )
              return {"success": False, "error": str(e)}
  
      def replace_in_file(
          self,
          file_path: str,
          pattern: str,
          replacement: str,
          count: int = 0,
          description: Optional[str] = None,
          case_sensitive: bool = False,
      ) -> Dict[str, Any]:
          """
          Replace pattern matches in a file using regex
  
          Args:
              file_path: Path to the file
              pattern: Regex pattern to search for
              replacement: Replacement string (supports \\1, \\2, etc. for groups)
              count: Maximum number of replacements (0 = all)
              description: Optional description of the change
              case_sensitive: Whether pattern matching is case-sensitive
  
          Returns:
              Dict with success status and replacement information
          """
          logger.info(
              f"CodeChangesManager.replace_in_file: Replacing pattern '{pattern}' in '{file_path}' (count={count}, case_sensitive={case_sensitive})"
          )
          try:
              current_content = self._get_current_content(file_path)
  
              # Compile regex pattern
              flags = 0 if case_sensitive else re.IGNORECASE
              try:
                  regex = re.compile(pattern, flags)
              except re.error as e:
                  return {"success": False, "error": f"Invalid regex pattern: {str(e)}"}
  
              # Find all matches first (for reporting)
              matches = list(regex.finditer(current_content))
              if not matches:
                  return {
                      "success": False,
                      "error": f"Pattern '{pattern}' not found in file",
                  }
  
              # Perform replacement
              if count == 0:
                  # Replace all
                  new_content = regex.sub(replacement, current_content)
                  replace_count = len(matches)
              else:
                  # Replace first N matches
                  new_content = regex.sub(replacement, current_content, count=count)
                  replace_count = min(count, len(matches))
  
              # Apply the update
              change_desc = (
                  description
                  or f"Replaced '{pattern}' with '{replacement}' ({replace_count} occurrences)"
              )
              self._apply_update(file_path, new_content, change_desc)
  
              # Get match locations for reporting
              match_locations = []
              for match in matches[:replace_count]:
                  # Calculate line number
                  line_num = current_content[: match.start()].count("\n") + 1
                  match_locations.append(
                      {
                          "line": line_num,
                          "match": match.group(0)[:100],  # First 100 chars
                          "position": match.start(),
                      }
                  )
  
              logger.info(
                  f"CodeChangesManager.replace_in_file: Successfully replaced {replace_count} occurrence(s) of pattern '{pattern}' in '{file_path}'"
              )
              return {
                  "success": True,
                  "file_path": file_path,
                  "pattern": pattern,
                  "replacement": replacement,
                  "replacements_made": replace_count,
                  "total_matches": len(matches),
                  "match_locations": match_locations,
              }
          except Exception as e:
              logger.exception(
                  "CodeChangesManager.replace_in_file: Error replacing pattern",
                  file_path=file_path,
              )
              return {"success": False, "error": str(e)}
  
      def insert_lines(
          self,
          file_path: str,
          line_number: int,
          content: str,
          description: Optional[str] = None,
          insert_after: bool = True,
          project_id: Optional[str] = None,
          db: Optional[Session] = None,
      ) -> Dict[str, Any]:
          """
          Insert content at a specific line in a file
  
          Args:
              file_path: Path to the file
              line_number: Line number to insert at (1-indexed)
              content: Content to insert
              description: Optional description of the change
              insert_after: If True, insert after line_number; if False, insert before
  
          Returns:
              Dict with success status and insertion information
          """
          position = "after" if insert_after else "before"
          logger.info(
              f"CodeChangesManager.insert_lines: Inserting {len(content.split(chr(10)))} line(s) {position} line {line_number} in '{file_path}' "
              f"(project_id={project_id}, db={'provided' if db else 'None'})"
          )
          try:
              current_content = self._get_current_content(
                  file_path, project_id=project_id, db=db
              )
              lines = current_content.split("\n")
              logger.info(
                  f"CodeChangesManager.insert_lines: Retrieved content for '{file_path}' - "
                  f"{len(lines)} lines, content length: {len(current_content)} chars"
              )
  
              if len(lines) == 1 and lines[0] == "":
                  logger.warning(
                      f"CodeChangesManager.insert_lines: WARNING - File '{file_path}' appears to be empty or only contains empty string!"
                  )
  
              # Validate line number
              logger.debug(
                  f"CodeChangesManager.insert_lines: Validating line_number={line_number} against file with {len(lines)} lines"
              )
              if line_number < 1:
                  return {"success": False, "error": "line_number must be >= 1"}
  
              # Calculate max valid line number based on insert mode
              if insert_after:
                  # Can insert after line 1 through line (len(lines) + 1)
                  max_valid_line = len(lines) + 1
              else:
                  # Can insert before line 1 through line len(lines)
                  max_valid_line = len(lines)
  
              if line_number > max_valid_line:
                  return {
                      "success": False,
                      "error": f"line_number {line_number} is beyond end of file ({len(lines)} lines). "
                      f"Valid range: 1 to {max_valid_line} when inserting {'after' if insert_after else 'before'}.",
                  }
  
              # Split content into lines
              new_lines = content.split("\n")
  
              # Convert to 0-indexed insertion index
              # When insert_after=True: insert after line N means insert at index N (0-indexed)
              # When insert_after=False: insert before line N means insert at index N-1 (0-indexed)
              if insert_after:
                  # Clamp to valid range: can insert at indices 0 through len(lines)
                  insert_idx = min(line_number, len(lines))
              else:
                  # Clamp to valid range: can insert at indices 0 through len(lines)-1
                  insert_idx = max(0, min(line_number - 1, len(lines) - 1))
  
              # Insert the lines
              updated_lines = lines[:insert_idx] + new_lines + lines[insert_idx:]
              updated_content = "\n".join(updated_lines)
  
              # Apply the update
              change_desc = (
                  description
                  or f"Inserted {len(new_lines)} lines {position} line {line_number}"
              )
              self._apply_update(file_path, updated_content, change_desc)
  
              # Calculate context around inserted area
              context_lines_before = 3
              context_lines_after = 3
  
              # Get context before (from original file)
              context_start_idx = max(0, insert_idx - context_lines_before)
              context_lines_before_list = lines[context_start_idx:insert_idx]
  
              # Get context after (in the updated file)
              context_after_start_idx = insert_idx + len(new_lines)
              context_after_end_idx = min(
                  len(updated_lines), context_after_start_idx + context_lines_after
              )
              context_lines_after_list = updated_lines[
                  context_after_start_idx:context_after_end_idx
              ]
  
              # Get the inserted section with context
              context_with_inserted = (
                  context_lines_before_list + new_lines + context_lines_after_list
              )
  
              # Calculate line numbers for context display
              context_start_line = context_start_idx + 1
              context_end_line = context_after_end_idx
  
              logger.info(
                  f"CodeChangesManager.insert_lines: Successfully inserted {len(new_lines)} line(s) {position} line {line_number} in '{file_path}'"
              )
              return {
                  "success": True,
                  "file_path": file_path,
                  "line_number": line_number,
                  "position": position,
                  "lines_inserted": len(new_lines),
                  "inserted_context": "\n".join(context_with_inserted),
                  "context_start_line": context_start_line,
                  "context_end_line": context_end_line,
              }
          except Exception as e:
              logger.exception(
                  "CodeChangesManager.insert_lines: Error inserting lines",
                  file_path=file_path,
              )
              return {"success": False, "error": str(e)}
  
      def delete_lines(
          self,
          file_path: str,
          start_line: int,
          end_line: Optional[int] = None,
          description: Optional[str] = None,
      ) -> Dict[str, Any]:
          """
          Delete specific lines from a file (1-indexed)
  
          Args:
              file_path: Path to the file
              start_line: Starting line number (1-indexed, inclusive)
              end_line: Ending line number (1-indexed, inclusive). If None, only start_line is deleted
              description: Optional description of the change
  
          Returns:
              Dict with success status and deletion information
          """
          logger.info(
              f"CodeChangesManager.delete_lines: Deleting lines {start_line}-{end_line or start_line} from '{file_path}'"
          )
          try:
              current_content = self._get_current_content(file_path)
              lines = current_content.split("\n")
  
              # Validate line numbers
              if start_line < 1 or start_line > len(lines):
                  return {
                      "success": False,
                      "error": f"Invalid start_line {start_line}. File has {len(lines)} lines.",
                  }
  
              if end_line is None:
                  end_line = start_line
  
              if end_line < start_line:
                  return {
                      "success": False,
                      "error": f"end_line ({end_line}) must be >= start_line ({start_line})",
                  }
  
              if end_line > len(lines):
                  return {
                      "success": False,
                      "error": f"Invalid end_line {end_line}. File has {len(lines)} lines.",
                  }
  
              # Convert to 0-indexed
              start_idx = start_line - 1
              end_idx = end_line  # exclusive for slicing
  
              # Get the lines being deleted (for reporting)
              deleted_lines = lines[start_idx:end_idx]
              deleted_content = "\n".join(deleted_lines)
  
              # Delete the lines
              updated_lines = lines[:start_idx] + lines[end_idx:]
              updated_content = "\n".join(updated_lines)
  
              # Apply the update
              change_desc = description or f"Deleted lines {start_line}-{end_line}"
              self._apply_update(file_path, updated_content, change_desc)
  
              logger.info(
                  f"CodeChangesManager.delete_lines: Successfully deleted {len(deleted_lines)} line(s) ({start_line}-{end_line}) from '{file_path}'"
              )
              return {
                  "success": True,
                  "file_path": file_path,
                  "start_line": start_line,
                  "end_line": end_line,
                  "lines_deleted": len(deleted_lines),
                  "deleted_content": deleted_content,
              }
          except Exception as e:
              logger.exception(
                  "CodeChangesManager.delete_lines: Error deleting lines",
                  file_path=file_path,
              )
              return {"success": False, "error": str(e)}
  
      def delete_file(
          self,
          file_path: str,
          description: Optional[str] = None,
          preserve_content: bool = True,
      ) -> bool:
          """Mark a file for deletion"""
          logger.info(
              f"CodeChangesManager.delete_file: Marking file '{file_path}' for deletion (preserve_content={preserve_content})"
          )
          previous_content = None
          if file_path in self.changes:
              existing = self.changes[file_path]
              if preserve_content and existing.content:
                  previous_content = existing.content
              existing.change_type = ChangeType.DELETE
              existing.content = None  # Clear content for deleted files
              existing.updated_at = datetime.now().isoformat()
              if description:
                  existing.description = description
              if previous_content:
                  existing.previous_content = previous_content
          else:
              # New deletion record
              change = FileChange(
                  file_path=file_path,
                  change_type=ChangeType.DELETE,
                  content=None,
                  previous_content=previous_content,
                  description=description,
              )
              self.changes[file_path] = change
          logger.info(
              f"CodeChangesManager.delete_file: Successfully marked file '{file_path}' for deletion"
          )
          return True
  
      def get_file(self, file_path: str) -> Optional[Dict[str, Any]]:
          """Get change information for a specific file"""
          logger.debug(f"CodeChangesManager.get_file: Retrieving file '{file_path}'")
          if file_path not in self.changes:
              logger.debug(
                  f"CodeChangesManager.get_file: File '{file_path}' not found in changes"
              )
              return None
  
          change = self.changes[file_path]
          result = asdict(change)
          # Convert enum to string for serialization
          result["change_type"] = change.change_type.value
          return result
  
      def list_files(
          self,
          change_type_filter: Optional[ChangeType] = None,
          path_pattern: Optional[str] = None,
      ) -> List[Dict[str, Any]]:
          """List all files with changes, optionally filtered"""
          logger.debug(
              f"CodeChangesManager.list_files: Listing files (filter: {change_type_filter}, pattern: {path_pattern})"
          )
          files = list(self.changes.values())
  
          # Filter by change type
          if change_type_filter:
              files = [f for f in files if f.change_type == change_type_filter]
  
          # Filter by path pattern (supports regex)
          if path_pattern:
              try:
                  pattern = re.compile(path_pattern, re.IGNORECASE)
                  files = [f for f in files if pattern.search(f.file_path)]
              except re.error:
                  # If regex is invalid, fall back to simple substring match
                  files = [
                      f for f in files if path_pattern.lower() in f.file_path.lower()
                  ]
  
          # Sort by file path
          files.sort(key=lambda x: x.file_path)
  
  