"""Main API class for the SQ API."""

import ctypes
import os
import asyncio
import time
import logging
import threading
from typing import Tuple, Optional, Any
from ctypes import c_int, c_double, c_char_p, c_void_p, POINTER

from .constants import cmaxphases, LIB_NAME, SQErrorCode, SaveTaskFlags
from .types import (
    TOpenTaskRec, TLoadPatternRec, TStartRefinementRec,
    TSearchMatchCondition, TReportRec,
    TSetTraceRec, TGetTraceRec, TGlobalsRec,
    ASearchMatchRec, PASearchMatchRec, AResultRec, PAResultRec,
    ATrace, PATrace, APhaseRefinablesRec, PAPhaseRefinablesRec,
    PACyrstalliteRec, PAAmorphous, PGlobalsRec
)
from .exceptions import SQAPIError, SQAPITaskError, SQAPIRefinementError
from .dataclasses import (
    TaskInfo, PatternInfo, RefinementParams, RefinementResult
)
from .config import SQAPIConfig
from .utils import (
    validate_task_file, validate_pattern_file, validate_job_handle,
    validate_lib_dir, format_error_message
)

# Module-level logger
logger = logging.getLogger(__name__)

# Thread safety: RLock for all DLL operations
_dll_lock = threading.RLock()


def _configure_logger(log_level: int) -> None:
    """Configure module logger if not already configured."""
    logger.setLevel(log_level)
    # Only add handler if logger has no handlers and isn't propagating to a configured parent
    if not logger.handlers and not (logger.parent and logger.parent.handlers):
        handler = logging.StreamHandler()
        handler.setLevel(log_level)
        handler.setFormatter(logging.Formatter('%(name)s - %(levelname)s - %(message)s'))
        logger.addHandler(handler)


class TaskContext:
    """Context manager for task operations with automatic cleanup."""
    
    __slots__ = ('api', 'filename', 'use_unicode', 'job_handle', 'task_info')
    
    def __init__(self, api: 'SQAPI', filename: str, use_unicode: bool = True) -> None:
        """Initialize the task context manager."""
        self.api = api
        self.filename = filename
        self.use_unicode = use_unicode
        self.job_handle: Optional[int] = None
        self.task_info: Optional[TaskInfo] = None
    
    def __enter__(self) -> Tuple[int, TaskInfo]:
        """Open task and return job handle and task info."""
        logger.info(f"Opening task context for file: {self.filename}")
        self.task_info = self.api.open_task(self.filename, self.use_unicode)
        self.job_handle = self.task_info.job_handle
        return self.job_handle, self.task_info
    
    def __exit__(self, exc_type: Optional[type], exc_val: Optional[Exception], exc_tb: Optional[Any]) -> bool:
        """Close task on context exit."""
        if self.job_handle is not None:
            try:
                self.api.close_task(self.job_handle)
            except Exception as e:
                logger.warning(f"Error closing task {self.job_handle}: {e}")
        return False


class SQAPI:
    """Main API class for interacting with the Siroquant SQ4 DLL.
    
    Thread-safe: All DLL operations are protected by a module-level RLock.
    """
    
    __slots__ = ('config', 'lib', '_initialized', '_lib_dir_set', '_job_files')
    
    def __init__(self, config: Optional[SQAPIConfig] = None, log_level: int = logging.WARNING) -> None:
        """Initialize the SQ API instance.
        
        Args:
            config: Configuration object. If None, uses defaults.
            log_level: Logging level (default: WARNING).
        """
        _configure_logger(log_level)
        logger.info(f"Initializing SQAPI")
        
        self.config = config or SQAPIConfig(validate=False)
        
        # Find DLL from config
        package_dir = os.path.dirname(os.path.dirname(__file__))
        dll_path = self.config.find_dll(package_dir)
        logger.debug(f"Using DLL path: {dll_path}")
        
        try:
            self.lib = ctypes.WinDLL(dll_path)
            logger.debug(f"DLL loaded successfully")
        except OSError as e:
            raise SQAPIError(f"Failed to load DLL: {dll_path}. Error: {e}")
        
        self._setup_prototypes()
        self._initialized = False
        self._lib_dir_set = False
        self._job_files: dict = {}
    
    def _setup_prototypes(self) -> None:
        """Setup function prototypes for the DLL."""
        logger.debug("Setting up DLL function prototypes")
        
        self.lib.SQ4Initialize.argtypes = []
        self.lib.SQ4Initialize.restype = c_int

        self.lib.SQ4LihDir2.argtypes = [c_void_p]
        self.lib.SQ4LihDir2.restype = c_int

        self.lib.SQ4OpenTask1.argtypes = [c_char_p, POINTER(TOpenTaskRec)]
        self.lib.SQ4OpenTask1.restype = c_int

        self.lib.SQ4OpenTask2.argtypes = [c_void_p, POINTER(TOpenTaskRec)]
        self.lib.SQ4OpenTask2.restype = c_int

        self.lib.SQ4LoadPattern1.argtypes = [c_int, c_char_p, POINTER(TLoadPatternRec)]
        self.lib.SQ4LoadPattern1.restype = c_int

        try:
            self.lib.SQ4LoadPattern2.argtypes = [c_int, c_void_p, POINTER(TLoadPatternRec)]
            self.lib.SQ4LoadPattern2.restype = c_int
        except AttributeError:
            pass

        self.lib.SQ4SetPattern1.argtypes = [c_int, PATrace, c_int, c_char_p, TSetTraceRec]
        self.lib.SQ4SetPattern1.restype = c_int

        self.lib.SQ4StartRefinement.argtypes = [c_int, POINTER(TStartRefinementRec)]
        self.lib.SQ4StartRefinement.restype = c_int

        self.lib.SQ4SearchMatch.argtypes = [c_int, TSearchMatchCondition, POINTER(PASearchMatchRec)]
        self.lib.SQ4SearchMatch.restype = c_int

        self.lib.SQ4Eliminate.argtypes = [c_int, c_int, TSearchMatchCondition, POINTER(PASearchMatchRec)]
        self.lib.SQ4Eliminate.restype = c_int

        self.lib.SQ4CheckRefinement.argtypes = [c_int]
        self.lib.SQ4CheckRefinement.restype = c_int

        self.lib.SQ4ReportResultsFull.argtypes = [
            c_int, POINTER(TReportRec), PAResultRec, c_int, POINTER(TGlobalsRec), PAPhaseRefinablesRec
        ]
        self.lib.SQ4ReportResultsFull.restype = c_int

        self.lib.SQ4ReportResults.argtypes = [c_int, POINTER(TReportRec), PAResultRec, c_int]
        self.lib.SQ4ReportResults.restype = c_int

        self.lib.SQ4CrystalliteSize.argtypes = [c_int, c_double, c_int, c_int, PACyrstalliteRec]
        self.lib.SQ4CrystalliteSize.restype = c_int

        self.lib.SQ4AmorphousContent.argtypes = [c_int, c_double, c_int, c_int, PAAmorphous]
        self.lib.SQ4AmorphousContent.restype = c_int

        self.lib.SQ4LoadTrace.argtypes = [c_int, PATrace, POINTER(c_int), c_int, POINTER(TGetTraceRec)]
        self.lib.SQ4LoadTrace.restype = c_int

        self.lib.SQ4SaveTask1.argtypes = [c_int, c_char_p, c_int]
        self.lib.SQ4SaveTask1.restype = c_int

        self.lib.SQ4SaveTask2.argtypes = [c_int, c_void_p, c_int]
        self.lib.SQ4SaveTask2.restype = c_int

        self.lib.SQ4CloseTask.argtypes = [c_int]
        self.lib.SQ4CloseTask.restype = c_int

        self.lib.SQ4Finalize.argtypes = []
        self.lib.SQ4Finalize.restype = c_int

    def initialize(self) -> None:
        """Initialize the SQ API. Must be called before any operations."""
        if not self._lib_dir_set and self.config.lib_dir:
            self.set_lib_dir(self.config.get_lib_dir())
        
        logger.info("Calling SQ4Initialize()")
        with _dll_lock:
            result = self.lib.SQ4Initialize()
        
        if result in (SQErrorCode.INITIALIZED, SQErrorCode.OK):
            self._initialized = True
            logger.info("API initialized successfully")
        elif result < 0:
            logger.error(f"API initialization failed with error code: {result}")
            error_msg = self._get_init_error_message(result)
            raise SQAPIError(error_msg, error_code=result)
    
    def _get_init_error_message(self, result: int) -> str:
        """Get detailed error message for initialization failures."""
        if result == SQErrorCode.FINALIZATION_FAILURE:
            return (
                "API initialization failed: Previous finalization did not complete successfully.\n"
                "Please restart the application."
            )
        elif result == SQErrorCode.DONGL_FAILURE:
            from .constants import ERROR_MESSAGES
            return ERROR_MESSAGES.get(result, f"Unknown error: {result}")
        elif result == SQErrorCode.UNKNOWN_EXCEPTION:
            return "API initialization failed: An unexpected exception occurred in the DLL."
        else:
            return format_error_message(result, "operation: initialize")

    def set_lib_dir(self, path: str) -> None:
        """Set the library directory for phase database files."""
        abs_path = validate_lib_dir(path)
        logger.debug(f"Setting library directory: {abs_path}")
        
        with _dll_lock:
            result = self.lib.SQ4LihDir2(ctypes.c_wchar_p(abs_path))
        
        if result == SQErrorCode.OK:
            self._lib_dir_set = True
        elif result < 0:
            raise SQAPIError(format_error_message(result, f"path: {abs_path}"), error_code=result)

    def open_task(self, filename: str, use_unicode: bool = True) -> TaskInfo:
        """Open a Siroquant task file (.tsk or .tmk).
        
        Args:
            filename: Task file path (relative or absolute).
            use_unicode: Whether to use Unicode API (default: True).
            
        Returns:
            TaskInfo object containing task information.
        """
        abs_filename = validate_task_file(filename)
        logger.info(f"Opening task file: {abs_filename}")
        
        open_rec = TOpenTaskRec()
        with _dll_lock:
            if use_unicode:
                job_handle = self.lib.SQ4OpenTask2(ctypes.c_wchar_p(abs_filename), ctypes.byref(open_rec))
            else:
                job_handle = self.lib.SQ4OpenTask1(
                    ctypes.c_char_p(abs_filename.encode('mbcs')), 
                    ctypes.byref(open_rec)
                )
        
        if open_rec.return_val < 0:
            error_msg = format_error_message(open_rec.return_val, f"filename: {abs_filename}")
            raise SQAPITaskError(error_msg, error_code=open_rec.return_val)
        
        task_info = TaskInfo.from_ctypes(job_handle, open_rec)
        self._job_files[job_handle] = abs_filename
        logger.info(f"Task opened: {task_info}")
        return task_info

    def load_pattern(self, n: int, filename: str, use_unicode: bool = True) -> PatternInfo:
        """Load an X-ray diffraction pattern file into a task.
        
        Args:
            n: Job handle.
            filename: Pattern file path (relative or absolute).
            use_unicode: Whether to use Unicode API (default: True).
            
        Returns:
            PatternInfo object containing pattern information.
        """
        validate_job_handle(n)
        abs_filename = validate_pattern_file(filename)
        logger.info(f"Loading pattern: {abs_filename} for job {n}")
        
        load_rec = TLoadPatternRec()
        with _dll_lock:
            if use_unicode and hasattr(self.lib, 'SQ4LoadPattern2'):
                res = self.lib.SQ4LoadPattern2(n, ctypes.c_wchar_p(abs_filename), ctypes.byref(load_rec))
            else:
                res = self.lib.SQ4LoadPattern1(
                    n, 
                    ctypes.c_char_p(abs_filename.encode('mbcs')), 
                    ctypes.byref(load_rec)
                )
        
        pattern_info = PatternInfo.from_ctypes(load_rec)
        
        if res != SQErrorCode.OK or pattern_info.return_code < 0:
            error_code = pattern_info.return_code if pattern_info.return_code < 0 else res
            raise SQAPIError(format_error_message(error_code, f"filename: {filename}"), error_code=error_code)
        
        logger.info(f"Pattern loaded: {pattern_info}")
        return pattern_info

    def start_refinement(self, n: int, params: Optional[RefinementParams] = None) -> None:
        """Start a quantitative phase analysis refinement.
        
        Args:
            n: Job handle from open_task().
            params: Refinement parameters. If None, a zeroed record is passed to
                the DLL which triggers the "run as is" branch — the task's stored
                configuration (AutoPreScale, stages, etc.) is used unchanged.
        
        Raises:
            SQAPIRefinementError: If refinement cannot be started.
        """
        validate_job_handle(n)
        logger.info(f"Starting refinement for job {n}")
        
        if params is not None:
            start_rec = params.to_ctypes()
        else:
            start_rec = TStartRefinementRec()  # zero-initialised → DLL "run as is" branch
        with _dll_lock:
            result = self.lib.SQ4StartRefinement(n, ctypes.byref(start_rec))
        
        if params is not None:
            params.ret_flags = start_rec.RetFlags
        
        if result != SQErrorCode.OK:
            raise SQAPIRefinementError(
                format_error_message(result, f"job_handle: {n}"), 
                error_code=result
            )

    def check_refinement(self, n: int) -> int:
        """Check the status of a running refinement.
        
        Args:
            n: Job handle.
        
        Returns:
            Status code (TASK_COMPLETED, TASK_REFINING, REFINEMENT_FAILED, etc.)
        """
        validate_job_handle(n)
        with _dll_lock:
            status = self.lib.SQ4CheckRefinement(n)
        logger.debug(f"Refinement status for job {n}: {status}")
        return status

    def _poll_refinement(self, job_handle: int, timeout: float, check_interval: float) -> int:
        """Internal polling loop for refinement completion.
        
        Args:
            job_handle: Job handle.
            timeout: Maximum wait time in seconds.
            check_interval: Time between status checks in seconds.
        
        Returns:
            Final refinement status code.
        
        Raises:
            TimeoutError: If refinement doesn't complete within timeout.
        """
        start_time = time.time()
        while time.time() - start_time < timeout:
            status = self.check_refinement(job_handle)
            
            if status == SQErrorCode.TASK_COMPLETED:
                logger.info("Refinement completed successfully")
                return status
            elif status == SQErrorCode.REFINEMENT_FAILED:
                logger.warning("Refinement failed")
                return status
            elif status not in (SQErrorCode.TASK_REFINING, SQErrorCode.TASK_NOT_STARTED):
                # Other error codes
                return status
            
            time.sleep(check_interval)
        
        raise TimeoutError(f"Refinement did not complete within {timeout} seconds")

    def report_results(self, n: int, flags: int = 0) -> RefinementResult:
        """Get the results from a completed refinement.
        
        Args:
            n: Job handle.
            flags: Bitfield of :class:`~pysq.constants.ReportResultsFlags` values
                controlling which result set is returned and optional sort order.
                Defaults to 0 (uncorrected results, no sort).

                Common values:

                * ``ReportResultsFlags.UNCORRECTED`` (0) – raw weight percentages.
                * ``ReportResultsFlags.CORRECTED`` (16) – TRACSCAL-corrected weight
                  percentages.  Use this to match the Delphi client's
                  "Use Corrected Results" option.
                * ``ReportResultsFlags.CORRECTED | ReportResultsFlags.SORT_RESULT``
                  – corrected results sorted by weight % descending.
        
        Returns:
            RefinementResult object with chi-squared, R-factor, and phase results.
        """
        validate_job_handle(n)
        logger.info(f"Retrieving results for job {n} (flags={flags})")
        
        report_rec = TReportRec()
        report_rec.flags = flags
        phases = AResultRec()
        
        with _dll_lock:
            res = self.lib.SQ4ReportResults(n, ctypes.byref(report_rec), ctypes.pointer(phases), cmaxphases)
        
        if res < 0:
            raise SQAPIError(format_error_message(res, f"job_handle: {n}"), error_code=res)
        
        result = RefinementResult.from_ctypes(report_rec, phases, report_rec.nPhases)
        logger.info(f"Results: {result}")
        return result

    def close_task(self, n: int) -> None:
        """Close a task and free associated resources.
        
        Args:
            n: Job handle.
        """
        validate_job_handle(n)
        logger.info(f"Closing task {n}")
        
        with _dll_lock:
            result = self.lib.SQ4CloseTask(n)
        
        self._job_files.pop(n, None)
        
        if result != SQErrorCode.OK:
            raise SQAPITaskError(format_error_message(result, f"job_handle: {n}"), error_code=result)

    def save_task(
        self,
        n: int,
        filename: Optional[str] = None,
        flags: int = SaveTaskFlags.DEFAULT,
        use_unicode: bool = True,
    ) -> None:
        """Save a task to disk.

        Persists the current state of the task (phases, parameters, and
        optionally refinement results) to a ``.tsk`` or ``.tmk`` file.

        The task must not be actively refining when this is called.

        Args:
            n: Job handle from open_task().
            filename: Destination file path.  If ``None`` (the default), the
                task is saved back to the original file it was opened from,
                overwriting it in place.  Relative paths are resolved to
                absolute paths before being passed to the DLL.
            flags: Bitfield of :class:`SaveTaskFlags` values controlling save
                behaviour.  Defaults to ``SaveTaskFlags.DEFAULT`` (0).

                Common combinations:

                * ``SaveTaskFlags.DEFAULT`` – overwrite original file,
                  current format.
                * ``SaveTaskFlags.ENFORCE_DIRECTORIES`` – create missing
                  output directories.
                * ``SaveTaskFlags.AS_TSK`` – force ``.tsk`` format.
                * ``SaveTaskFlags.AS_TMK`` – force ``.tmk`` template format.
                * ``SaveTaskFlags.TRACSCAL`` – embed TRACSCAL data.

            use_unicode: Use the Unicode API (``SQ4SaveTask2``). Set to
                ``False`` to use the ANSI API (``SQ4SaveTask1``).
                Defaults to ``True``.

        Raises:
            ValueError: If the job handle is invalid or ``filename`` is
                ``None`` and no source path is known for the handle.
            SQAPITaskError: If the task is still refining or the save fails.

        Example:
            >>> # Overwrite the original task file (default behaviour)
            >>> api.save_task(task_info.job_handle)

            >>> # Save to a new path and create any missing directories
            >>> from pysq import SaveTaskFlags
            >>> api.save_task(
            ...     task_info.job_handle,
            ...     r"output\\subdir\\result.tsk",
            ...     SaveTaskFlags.ENFORCE_DIRECTORIES,
            ... )

            >>> # Save as a template file with TRACSCAL data embedded
            >>> api.save_task(
            ...     task_info.job_handle,
            ...     "template.tmk",
            ...     SaveTaskFlags.AS_TMK | SaveTaskFlags.TRACSCAL,
            ... )
        """
        validate_job_handle(n)

        if filename is None:
            if n not in self._job_files:
                raise ValueError(
                    f"No source file known for job handle {n}; pass an explicit filename."
                )
            abs_filename = self._job_files[n]
        else:
            # IMPORTANT: the DLL calls SetCurrentDir to the task file's directory
            # when a task is opened (Delphi Otask.LoadFromFile line: SetCurrentDir).
            # This changes the process-wide CWD, so os.path.abspath() would resolve
            # relative paths against the task directory rather than the caller's CWD.
            # The caller is responsible for passing an already-absolute path, or for
            # resolving it before any DLL call is made.  We still call abspath() here
            # as a safety net for paths that are already absolute (no-op) but we
            # cannot reliably fix truly relative paths at this point.
            abs_filename = os.path.abspath(filename)

        # Create the output directory on the Python side before the DLL call.
        # The DLL's own ForceDirectories (ENFORCE_DIRECTORIES flag) also uses
        # the DLL-changed CWD, so it can fail for relative paths.  Creating the
        # directory here with an absolute path is always reliable.
        if flags & SaveTaskFlags.ENFORCE_DIRECTORIES:
            out_dir = os.path.dirname(abs_filename)
            if out_dir:
                os.makedirs(out_dir, exist_ok=True)

        logger.info(f"Saving task {n} to: {abs_filename} (flags={flags})")

        with _dll_lock:
            if use_unicode:
                result = self.lib.SQ4SaveTask2(n, ctypes.c_wchar_p(abs_filename), c_int(flags))
            else:
                result = self.lib.SQ4SaveTask1(n, ctypes.c_char_p(abs_filename.encode('mbcs')), c_int(flags))

        if result != SQErrorCode.OK:
            raise SQAPITaskError(
                format_error_message(result, f"filename: {abs_filename}"),
                error_code=result,
            )

        logger.info(f"Task {n} saved successfully to: {abs_filename}")

    def finalize(self) -> None:
        """Finalize the SQ API and release all resources."""
        logger.info("Finalizing API")
        
        with _dll_lock:
            result = self.lib.SQ4Finalize()
        
        self._initialized = False
        
        if result != SQErrorCode.OK:
            raise SQAPIError(format_error_message(result, "operation: finalize"), error_code=result)
        
        logger.info("API finalized successfully")

    # High-level convenience methods
    
    def refine(
        self,
        job_handle: int,
        params: Optional[RefinementParams] = None,
        timeout: float = 300.0,
        check_interval: float = 1.0,
        report_flags: int = 0,
    ) -> RefinementResult:
        """Refine a task synchronously.
        
        Starts refinement, waits for completion, and returns results.
        
        Args:
            job_handle: Job handle from open_task().
            params: Refinement parameters. If None, the task's stored
                configuration is used unchanged (AutoPreScale, stages, etc.).
            timeout: Maximum wait time in seconds.
            check_interval: Time between status checks.
            report_flags: Bitfield of :class:`~pysq.constants.ReportResultsFlags`
                values controlling which result set is returned.  Defaults to 0
                (uncorrected results).  Pass
                ``ReportResultsFlags.CORRECTED`` (16) to obtain
                TRACSCAL-corrected weight percentages, matching the Delphi
                client's "Use Corrected Results" option.
        
        Returns:
            RefinementResult object with analysis results.
        """
        validate_job_handle(job_handle)
        
        self.start_refinement(job_handle, params)
        status = self._poll_refinement(job_handle, timeout, check_interval)
        
        if status != SQErrorCode.TASK_COMPLETED:
            raise SQAPIRefinementError(
                format_error_message(status, f"job_handle: {job_handle}"),
                error_code=status
            )
        
        return self.report_results(job_handle, flags=report_flags)

    # Async methods
    
    async def wait_for_refinement(
        self,
        job_handle: int,
        timeout: float = 300.0,
        check_interval: float = 1.0
    ) -> int:
        """Wait for refinement to complete asynchronously.
        
        Args:
            job_handle: Job handle.
            timeout: Maximum wait time in seconds.
            check_interval: Time between status checks.
        
        Returns:
            Final refinement status code.
        
        Raises:
            TimeoutError: If refinement doesn't complete within timeout.
        """
        validate_job_handle(job_handle)
        logger.info(f"Waiting for refinement (timeout={timeout}s)")
        
        start_time = time.time()
        while time.time() - start_time < timeout:
            status = self.check_refinement(job_handle)
            
            if status == SQErrorCode.TASK_COMPLETED:
                return status
            elif status == SQErrorCode.REFINEMENT_FAILED:
                return status
            elif status not in (SQErrorCode.TASK_REFINING, SQErrorCode.TASK_NOT_STARTED):
                return status
            
            await asyncio.sleep(check_interval)
        
        raise TimeoutError(f"Refinement did not complete within {timeout} seconds")

    async def refine_async(
        self,
        job_handle: int,
        params: Optional[RefinementParams] = None,
        timeout: float = 300.0,
        check_interval: float = 1.0,
        report_flags: int = 0,
    ) -> RefinementResult:
        """Start refinement and wait for completion asynchronously.
        
        Args:
            job_handle: Job handle.
            params: Refinement parameters. If None, the task's stored
                configuration is used unchanged.
            timeout: Maximum wait time in seconds.
            check_interval: Time between status checks.
            report_flags: Bitfield of :class:`~pysq.constants.ReportResultsFlags`
                values controlling which result set is returned.  Defaults to 0
                (uncorrected results).  Pass ``ReportResultsFlags.CORRECTED``
                (16) to obtain TRACSCAL-corrected weight percentages.
        
        Returns:
            RefinementResult object with analysis results.
        """
        self.start_refinement(job_handle, params)
        status = await self.wait_for_refinement(job_handle, timeout, check_interval)
        
        if status != SQErrorCode.TASK_COMPLETED:
            raise SQAPIRefinementError(
                format_error_message(status, f"job_handle: {job_handle}"),
                error_code=status
            )
        
        return self.report_results(job_handle, flags=report_flags)

    # Context manager support
    
    def __enter__(self) -> 'SQAPI':
        """Context manager entry point."""
        return self
    
    def __exit__(self, exc_type: Optional[type], exc_val: Optional[Exception], exc_tb: Optional[Any]) -> bool:
        """Context manager exit point. Finalizes API automatically."""
        if self._initialized:
            try:
                self.finalize()
            except Exception as e:
                logger.warning(f"Error during API cleanup: {e}")
        return False
    
    def __repr__(self) -> str:
        return f"SQAPI(initialized={self._initialized}, lib_dir_set={self._lib_dir_set})"
