"""Data loading and column parsing utilities."""
from __future__ import annotations

from dataclasses import dataclass
from typing import Iterable, List, Optional, Sequence, Tuple

import re
import numpy as np
import pandas as pd


@dataclass
class TargetSpec:
    original_label: str
    label: str
    mode: str = "max"  # max, min, value
    value: Optional[float] = None
    plot_index: Optional[int] = None


def read_table(path: str, sheet_name=0, header: int = 0, index_col=None) -> pd.DataFrame:
    lower = path.lower()
    if lower.endswith(".csv"):
        return pd.read_csv(path, header=header, index_col=index_col)
    return pd.read_excel(path, sheet_name=sheet_name, header=header, index_col=index_col)


def parse_target_columns(columns: Sequence[str]) -> Tuple[List[TargetSpec], List[str]]:
    targets: List[TargetSpec] = []
    descriptors: List[str] = []
    next_plot_index = 0

    for col in columns:
        s = str(col)
        m = re.match(r"=([+\-\.\deE]+):(.*)", s)
        if m:
            targets.append(TargetSpec(s, m.group(2), mode="value", value=float(m.group(1))))
            continue

        m = re.match(r"(max|min|[to])(\d*):(.*)", s, flags=re.IGNORECASE)
        if m:
            head = m.group(1).lower()
            mode = "max" if head in ["max", "t", "o"] else "min"
            idx_text = m.group(2)
            if idx_text == "":
                plot_index = next_plot_index
                next_plot_index += 1
            else:
                plot_index = int(idx_text) - 1
            targets.append(TargetSpec(s, m.group(3), mode=mode, plot_index=plot_index))
            continue

        if s.startswith("-") or s.startswith("'-") or s.strip() == "":
            continue
        descriptors.append(s)

    if not targets and columns:
        s = str(columns[0])
        targets.append(TargetSpec(s, s, mode="max", plot_index=0))
        descriptors = [str(c) for c in columns[1:]]

    target_originals = {t.original_label for t in targets}
    descriptors = [d for d in descriptors if d not in target_originals]
    return targets, descriptors


def transform_target(y: np.ndarray, spec: TargetSpec) -> np.ndarray:
    y = np.asarray(y, dtype=float)
    if spec.mode == "max":
        return y
    if spec.mode == "min":
        return -y
    if spec.mode == "value":
        if spec.value is None:
            raise ValueError("Target mode 'value' requires spec.value.")
        return -((y - spec.value) ** 2)
    raise ValueError(f"Unknown target mode: {spec.mode}")


def make_xy_from_table(df: pd.DataFrame, target: Optional[str] = None, descriptors: Optional[Sequence[str]] = None, dropna: bool = True):
    specs, descriptor_labels = parse_target_columns(list(df.columns))
    if target is None:
        spec = specs[0]
    else:
        matched = [s for s in specs if s.original_label == target or s.label == target]
        if not matched:
            raise ValueError(f"Target '{target}' was not found. Available targets: {[s.original_label for s in specs]}")
        spec = matched[0]
    if descriptors is None:
        descriptors = descriptor_labels

    cols = list(descriptors) + [spec.original_label]
    work = df[cols]
    if dropna:
        work = work.dropna(how="any")
    X = work[list(descriptors)].to_numpy(dtype=float)
    y_raw = work[spec.original_label].to_numpy(dtype=float)
    y = transform_target(y_raw, spec)
    return X, y, spec, list(descriptors), work
