from __future__ import annotations

from collections import defaultdict
import csv
import datetime
from enum import Enum
import itertools
from typing import (
    Any,
    Callable,
    DefaultDict,
    Iterable,
    Sequence,
    cast,
)
import warnings

import numpy as np

import pandas._libs.lib as lib
import pandas._libs.ops as libops
import pandas._libs.parsers as parsers
from pandas._libs.parsers import STR_NA_VALUES
from pandas._libs.tslibs import parsing
from pandas._typing import (
    DtypeArg,
    FilePathOrBuffer,
    final,
)
from pandas.errors import (
    ParserError,
    ParserWarning,
)

from pandas.core.dtypes.cast import astype_nansafe
from pandas.core.dtypes.common import (
    ensure_object,
    ensure_str,
    is_bool_dtype,
    is_categorical_dtype,
    is_dict_like,
    is_dtype_equal,
    is_extension_array_dtype,
    is_integer,
    is_integer_dtype,
    is_list_like,
    is_object_dtype,
    is_scalar,
    is_string_dtype,
    pandas_dtype,
)
from pandas.core.dtypes.dtypes import CategoricalDtype
from pandas.core.dtypes.missing import isna

from pandas.core import algorithms
from pandas.core.arrays import Categorical
from pandas.core.indexes.api import (
    Index,
    MultiIndex,
    ensure_index_from_sequences,
)
from pandas.core.series import Series
from pandas.core.tools import datetimes as tools

from pandas.io.common import (
    IOHandles,
    get_handle,
)
from pandas.io.date_converters import generic_parser

parser_defaults = {
    "delimiter": None,
    "escapechar": None,
    "quotechar": '"',
    "quoting": csv.QUOTE_MINIMAL,
    "doublequote": True,
    "skipinitialspace": False,
    "lineterminator": None,
    "header": "infer",
    "index_col": None,
    "names": None,
    "prefix": None,
    "skiprows": None,
    "skipfooter": 0,
    "nrows": None,
    "na_values": None,
    "keep_default_na": True,
    "true_values": None,
    "false_values": None,
    "converters": None,
    "dtype": None,
    "cache_dates": True,
    "thousands": None,
    "comment": None,
    "decimal": ".",
    # 'engine': 'c',
    "parse_dates": False,
    "keep_date_col": False,
    "dayfirst": False,
    "date_parser": None,
    "usecols": None,
    # 'iterator': False,
    "chunksize": None,
    "verbose": False,
    "encoding": None,
    "squeeze": False,
    "compression": None,
    "mangle_dupe_cols": True,
    "infer_datetime_format": False,
    "skip_blank_lines": True,
    "encoding_errors": "strict",
    "on_bad_lines": "error",
}


class ParserBase:
    class BadLineHandleMethod(Enum):
        ERROR = 0
        WARN = 1
        SKIP = 2

    _implicit_index: bool = False
    _first_chunk: bool

    def __init__(self, kwds):

        self.names = kwds.get("names")
        self.orig_names: list | None = None
        self.prefix = kwds.pop("prefix", None)

        self.index_col = kwds.get("index_col", None)
        self.unnamed_cols: set = set()
        self.index_names: list | None = None
        self.col_names = None

        self.parse_dates = _validate_parse_dates_arg(kwds.pop("parse_dates", False))
        self.date_parser = kwds.pop("date_parser", None)
        self.dayfirst = kwds.pop("dayfirst", False)
        self.keep_date_col = kwds.pop("keep_date_col", False)

        self.na_values = kwds.get("na_values")
        self.na_fvalues = kwds.get("na_fvalues")
        self.na_filter = kwds.get("na_filter", False)
        self.keep_default_na = kwds.get("keep_default_na", True)

        self.true_values = kwds.get("true_values")
        self.false_values = kwds.get("false_values")
        self.mangle_dupe_cols = kwds.get("mangle_dupe_cols", True)
        self.infer_datetime_format = kwds.pop("infer_datetime_format", False)
        self.cache_dates = kwds.pop("cache_dates", True)

        self._date_conv = _make_date_converter(
            date_parser=self.date_parser,
            dayfirst=self.dayfirst,
            infer_datetime_format=self.infer_datetime_format,
            cache_dates=self.cache_dates,
        )

        # validate header options for mi
        self.header = kwds.get("header")
        if isinstance(self.header, (list, tuple, np.ndarray)):
            if not all(map(is_integer, self.header)):
                raise ValueError("header must be integer or list of integers")
            if any(i < 0 for i in self.header):
                raise ValueError(
                    "cannot specify multi-index header with negative integers"
                )
            if kwds.get("usecols"):
                raise ValueError(
                    "cannot specify usecols when specifying a multi-index header"
                )
            if kwds.get("names"):
                raise ValueError(
                    "cannot specify names when specifying a multi-index header"
                )

            # validate index_col that only contains integers
            if self.index_col is not None:
                is_sequence = isinstance(self.index_col, (list, tuple, np.ndarray))
                if not (
                    is_sequence
                    and all(map(is_integer, self.index_col))
                    or is_integer(self.index_col)
                ):