from __future__ import annotations

"""
CISV - High-performance CSV parser with SIMD optimizations

This module provides Python bindings to the CISV C library using nanobind,
offering 10-100x better performance than ctypes-based bindings.
"""

from typing import TYPE_CHECKING, Iterator, List
from importlib.metadata import PackageNotFoundError, version as _pkg_version

if TYPE_CHECKING:
    import numpy as np

from ._core import (
    parse_file as _parse_file,
    parse_string as _parse_string,
    parse_file_parallel as _parse_file_parallel,
    parse_file_raw as _parse_file_raw,
    parse_file_count_only as _parse_file_count_only,
    count_rows,
    CisvIterator,
)

try:
    __version__ = _pkg_version('cisv')
except PackageNotFoundError:
    __version__ = '0.0.0'
__all__ = [
    'parse_file',
    'parse_file_fast',
    'parse_file_benchmark',
    'parse_string',
    'count_rows',
    'CisvResult',
    'CisvBenchmarkResult',
    'CisvError',
    'CisvIterator',
    'open_iterator',
]


class CisvBenchmarkResult:
    """
    Minimal result class for benchmarking raw parsing speed.
    Only stores row/field counts without materializing any data.
    """
    __slots__ = ('_row_count', '_field_count', '_first_row_cols')

    def __init__(self, row_count: int, field_count: int, first_row_cols: int):
        self._row_count = row_count
        self._field_count = field_count
        self._first_row_cols = first_row_cols

    def __len__(self) -> int:
        return self._row_count

    def __getitem__(self, idx: int) -> List[str]:
        # Return dummy data for API compatibility
        if idx == 0:
            return [''] * self._first_row_cols
        raise IndexError("CisvBenchmarkResult only supports index 0")

    @property
    def row_count(self) -> int:
        return self._row_count

    @property
    def field_count(self) -> int:
        return self._field_count


class CisvError(Exception):
    """Base exception for CISV errors."""
    pass


def parse_file(
    path: str,
    delimiter: str = ',',
    quote: str = '"',
    *,
    trim: bool = False,
    skip_empty_lines: bool = False,
    parallel: bool = False,
    num_threads: int = 0,
) -> List[List[str]]:
    """
    Parse a CSV file and return all rows.

    This function uses SIMD-optimized C code for maximum performance.
    For large files (>10MB), consider using parallel=True for even
    better performance on multi-core systems.

    Args:
        path: Path to the CSV file
        delimiter: Field delimiter character (default: ',')
        quote: Quote character (default: '"')
        trim: Whether to trim whitespace from fields
        skip_empty_lines: Whether to skip empty lines
        parallel: Use multi-threaded parsing (faster for large files)
        num_threads: Number of threads for parallel parsing (0 = auto-detect)

    Returns:
        List of rows, where each row is a list of field values.

    Raises:
        CisvError: If parsing fails
        ValueError: If invalid arguments are provided

    Example:
        >>> import cisv
        >>> rows = cisv.parse_file('data.csv')
        >>> for row in rows:
        ...     print(row)
        ['header1', 'header2', 'header3']
        ['value1', 'value2', 'value3']
    """
    try:
        if parallel:
            return _parse_file_parallel(
                path, num_threads, delimiter, quote, trim, skip_empty_lines
            )
        return _parse_file(path, delimiter, quote, trim, skip_empty_lines)
    except ValueError:
        raise
    except RuntimeError as e:
        raise CisvError(str(e)) from e
    except Exception as e:
        raise CisvError(f"Failed to parse file: {e}") from e


def parse_string(
    content: str,
    delimiter: str = ',',
    quote: str = '"',
    *,
    trim: bool = False,
    skip_empty_lines: bool = False,
) -> List[List[str]]:
    """
    Parse a CSV string and return all rows.

    Args:
        content: CSV content as a string
        delimiter: Field delimiter character (default: ',')
        quote: Quote character (default: '"')
        trim: Whether to trim whitespace from fields
        skip_empty_lines: Whether to skip empty lines

    Returns:
        List of rows, where each row is a list of field values.

    Raises:
        CisvError: If parsing fails
        ValueError: If invalid arguments are provided

    Example:
        >>> import cisv
        >>> csv_data = "a,b,c\\n1,2,3"
        >>> rows = cisv.parse_string(csv_data)
        >>> print(rows)
        [['a', 'b', 'c'], ['1', '2', '3']]
    """
    try:
        return _parse_string(content, delimiter, quote, trim, skip_empty_lines)
    except ValueError:
        raise
    except RuntimeError as e:
        raise CisvError(str(e)) from e
    except Exception as e:
        raise CisvError(f"Failed to parse string: {e}") from e


class CisvResult:
    """
    Ultra-fast CSV parse result using numpy arrays.

    This class provides lazy access to parsed CSV data without creating
    Python string objects upfront. Data is decoded on-demand when accessed.

    This is 10-50x faster than parse_file() for large files because it
    avoids creating millions of Python string objects during parsing.
    """

    __slots__ = ('_data', '_field_offsets', '_field_lengths', '_row_offsets', '_row_count')

    def __init__(self, data: np.ndarray, field_offsets: np.ndarray,
                 field_lengths: np.ndarray, row_offsets: np.ndarray):
        self._data = data
        self._field_offsets = field_offsets
        self._field_lengths = field_lengths
        self._row_offsets = row_offsets
        self._row_count = len(row_offsets) - 1

    def __len__(self) -> int:
        """Return the number of rows."""
        return self._row_count

    def __getitem__(self, idx: int) -> List[str]:
        """Get a row by index, decoding fields on demand."""
        if idx < 0:
            idx = self._row_count + idx
        if idx < 0 or idx >= self._row_count:
            raise IndexError(f"Row index {idx} out of range")

        start = self._row_offsets[idx]
        end = self._row_offsets[idx + 1]

        row = []
        for i in range(start, end):
            offset = self._field_offsets[i]
            length = self._field_lengths[i]
            field_bytes = self._data[offset:offset + length].tobytes()
            row.append(field_bytes.decode('utf-8'))
        return row

    def __iter__(self) -> Iterator[List[str]]:
        """Iterate over rows, decoding each on demand."""
        for i in range(self._row_count):
            yield self[i]

    @property
    def row_count(self) -> int:
        """Number of rows in the result."""
        return self._row_count

    @property
    def field_count(self) -> int:
        """Total number of fields across all rows."""
        return len(self._field_offsets)

    def to_list(self) -> List[List[str]]:
        """Convert to a list of lists (materializes all data)."""
        return [self[i] for i in range(self._row_count)]

    def get_field(self, row: int, col: int) -> str:
        """Get a single field value by row and column index."""
        if row < 0:
            row = self._row_count + row
        if row < 0 or row >= self._row_count:
            raise IndexError(f"Row index {row} out of range")

        start = self._row_offsets[row]
        end = self._row_offsets[row + 1]
        field_idx = start + col

        if col < 0 or field_idx >= end:
            raise IndexError(f"Column index {col} out of range")

        offset = self._field_offsets[field_idx]
        length = self._field_lengths[field_idx]
        return self._data[offset:offset + length].tobytes().decode('utf-8')

    def get_column(self, col: int) -> List[str]:
        """Get all values in a column."""
        result = []
        for i in range(self._row_count):
            start = self._row_offsets[i]
            end = self._row_offsets[i + 1]
            if col < end - start:
                field_idx = start + col
                offset = self._field_offsets[field_idx]
                length = self._field_lengths[field_idx]
                result.append(self._data[offset:offset + length].tobytes().decode('utf-8'))
            else:
                result.append('')  # Missing field
        return result


def parse_file_fast(
    path: str,
    delimiter: str = ',',
    quote: str = '"',
    *,
    trim: bool = False,
    skip_empty_lines: bool = False,
    num_threads: int = 0,
) -> CisvResult:
    """
    Ultra-fast parallel CSV parsing returning a CisvResult object.

    This is the fastest parsing mode, using parallel C parsing and
    avoiding Python string object creation. Data is decoded on-demand
    when accessed through the CisvResult object.

    For benchmarking or when you need list[list[str]], this is 10-50x
    faster than parse_file() because the expensive string creation
    only happens for fields you actually access.

    Args:
        path: Path to the CSV file
        delimiter: Field delimiter character (default: ',')
        quote: Quote character (default: '"')
        trim: Whether to trim whitespace from fields
        skip_empty_lines: Whether to skip empty lines
        num_threads: Number of threads for parallel parsing (0 = auto-detect)

    Returns:
        CisvResult object with lazy field access.

    Example:
        >>> import cisv
        >>> result = cisv.parse_file_fast('data.csv')
        >>> print(len(result))  # Number of rows
        1000000
        >>> print(result[0])    # First row (decoded on demand)
        ['header1', 'header2', 'header3']
        >>> print(result.get_field(1, 0))  # Single field access
        'value1'
    """
    try:
        data, offsets, lengths, rows = _parse_file_raw(
            path, num_threads, delimiter, quote, trim, skip_empty_lines
        )
        return CisvResult(data, offsets, lengths, rows)
    except ValueError:
        raise
    except RuntimeError as e:
        raise CisvError(str(e)) from e
    except Exception as e:
        raise CisvError(f"Failed to parse file: {e}") from e


def parse_file_benchmark(
    path: str,
    delimiter: str = ',',
    quote: str = '"',
    *,
    num_threads: int = 0,
) -> CisvBenchmarkResult:
    """
    Ultra-fast parallel CSV parsing for benchmarking raw parse speed.

    This mode parses the entire file but only returns counts, not data.
    Use this to measure raw parsing throughput without data marshaling overhead.

    Args:
        path: Path to the CSV file
        delimiter: Field delimiter character (default: ',')
        quote: Quote character (default: '"')
        num_threads: Number of threads for parallel parsing (0 = auto-detect)

    Returns:
        CisvBenchmarkResult with row/field counts.

    Example:
        >>> import cisv
        >>> result = cisv.parse_file_benchmark('data.csv')
        >>> print(len(result))  # Number of rows parsed
        1000000
    """
    try:
        row_count, field_count, first_row_cols = _parse_file_count_only(
            path, num_threads, delimiter, quote
        )
        return CisvBenchmarkResult(row_count, field_count, first_row_cols)
    except ValueError:
        raise
    except RuntimeError as e:
        raise CisvError(str(e)) from e
    except Exception as e:
        raise CisvError(f"Failed to parse file: {e}") from e


def open_iterator(
    path: str,
    delimiter: str = ',',
    quote: str = '"',
    *,
    trim: bool = False,
    skip_empty_lines: bool = False,
) -> CisvIterator:
    """
    Open a CSV file for row-by-row iteration.

    This function returns a CisvIterator that provides fgetcsv-style
    streaming with minimal memory footprint. It supports early exit -
    breaking out of iteration stops parsing immediately with no wasted work.

    Args:
        path: Path to the CSV file
        delimiter: Field delimiter character (default: ',')
        quote: Quote character (default: '"')
        trim: Whether to trim whitespace from fields
        skip_empty_lines: Whether to skip empty lines

    Returns:
        CisvIterator that can be used with for-loops or as a context manager.

    Raises:
        CisvError: If the file cannot be opened

    Example:
        >>> import cisv
        >>> # Using as context manager (recommended)
        >>> with cisv.open_iterator('data.csv') as reader:
        ...     for row in reader:
        ...         print(row)
        ...         if row[0] == 'stop':
        ...             break  # Early exit - no wasted work
        ['header1', 'header2']
        ['value1', 'value2']

        >>> # Using in a simple for loop
        >>> for row in cisv.open_iterator('data.csv'):
        ...     print(row[0])
    """
    try:
        return CisvIterator(path, delimiter, quote, trim, skip_empty_lines)
    except ValueError:
        raise
    except RuntimeError as e:
        raise CisvError(str(e)) from e
    except Exception as e:
        raise CisvError(f"Failed to open iterator: {e}") from e
