"""Blink detection using peak-prominence analysis of eyelid parameters."""

import numpy as np
from scipy.ndimage import gaussian_filter1d
from scipy.signal import find_peaks, peak_widths

from ..constants import EXPR_BLINK_INDICES, FPS
from ..models import BlinkEvent
from ..processing.utils import build_idx_to_frame


def detect_blinks_in_segment(
    signal, *, sigma=1.5, prominence=0.07, min_distance=5, min_width=1, max_width=18
):
    """Detect blinks in a single contiguous valid segment using peak-finding."""
    if len(signal) < 4:
        return []

    smoothed = gaussian_filter1d(signal, sigma=sigma)

    peaks, properties = find_peaks(
        smoothed,
        prominence=prominence,
        distance=min_distance,
        width=[min_width, max_width],
    )

    if len(peaks) == 0:
        return []

    widths, width_heights, left_ips, right_ips = peak_widths(smoothed, peaks, rel_height=0.5)

    results = []
    for i, peak_idx in enumerate(peaks):
        left = max(0, int(np.floor(left_ips[i])))
        right = min(len(signal) - 1, int(np.ceil(right_ips[i])))

        left_base = properties["left_bases"][i]
        right_base = properties["right_bases"][i]
        baseline = (smoothed[left_base] + smoothed[right_base]) / 2.0

        results.append(
            {
                "local_idx": int(peak_idx),
                "left": left,
                "right": right,
                "prominence": float(properties["prominences"][i]),
                "width_frames": float(widths[i]),
                "peak_value": float(signal[peak_idx]),
                "baseline": float(baseline),
                "left_base": int(left_base),
                "right_base": int(right_base),
            }
        )

    return results


def compute_confidence(
    peak_info, segment_signal, expression=None, global_idx=0, min_prominence=0.04
):
    """Multi-factor confidence score for a detected blink."""
    prom = peak_info["prominence"]
    width = peak_info["width_frames"]
    left_base = peak_info["left_base"]
    right_base = peak_info["right_base"]
    local_idx = peak_info["local_idx"]

    prom_score = float(np.clip(0.3 + 0.7 * (prom / min_prominence - 1.0) / 2.0, 0, 1))

    ideal_width = 4.0
    width_score = float(np.exp(-0.5 * ((width - ideal_width) / 3.0) ** 2))

    rise = local_idx - left_base
    fall = right_base - local_idx
    if rise > 0 and fall > 0:
        ratio = min(rise, fall) / max(rise, fall)
        symmetry_score = float(max(ratio, 0.3))
    else:
        symmetry_score = 0.5

    expr_score = 0.5
    if expression is not None and global_idx < len(expression):
        valid_blink_cols = [i for i in EXPR_BLINK_INDICES if i < expression.shape[1]]
        if valid_blink_cols:
            expr_sub = expression[:, valid_blink_cols]
            peak_expr = expr_sub[global_idx]
            left_end = max(0, global_idx - 5)
            right_start = min(len(expr_sub), global_idx + 5)
            base_frames = np.concatenate(
                [
                    expr_sub[max(0, global_idx - 20) : left_end],
                    expr_sub[right_start : min(len(expr_sub), global_idx + 20)],
                ],
                axis=0,
            )
            if len(base_frames) > 2:
                base_expr = np.mean(base_frames, axis=0)
                delta = float(np.max(np.abs(peak_expr - base_expr)))
                expr_score = float(np.clip(delta / 1.0, 0, 1))

    confidence = 0.50 * prom_score + 0.25 * width_score + 0.15 * symmetry_score + 0.10 * expr_score
    return float(np.clip(confidence, 0, 1))


def detect_blinks(
    eyelid,
    successful_frames,
    valid,
    segments,
    *,
    sigma=1.5,
    prominence=0.07,
    min_distance=5,
    min_width=1,
    max_width=18,
    min_confidence=0.15,
    expression=None,
    idx_to_frame=None,
    verbose=False,
):
    """Detect blinks across all valid segments."""
    if idx_to_frame is None:
        idx_to_frame = build_idx_to_frame(successful_frames)

    all_blinks = []
    rejected = 0

    for seg_start, seg_end in segments:
        seg_signal = eyelid[seg_start:seg_end]

        raw_peaks = detect_blinks_in_segment(
            seg_signal,
            sigma=sigma,
            prominence=prominence,
            min_distance=min_distance,
            min_width=min_width,
            max_width=max_width,
        )

        for peak in raw_peaks:
            global_idx = seg_start + peak["local_idx"]
            global_left = seg_start + peak["left"]
            global_right = seg_start + peak["right"]

            conf = compute_confidence(
                peak,
                seg_signal,
                expression,
                global_idx,
                min_prominence=prominence,
            )

            if conf < min_confidence:
                rejected += 1
                continue

            width_ms = peak["width_frames"] * (1000.0 / FPS)
            duration_frames = peak["right"] - peak["left"] + 1
            duration_ms = duration_frames * (1000.0 / FPS)

            all_blinks.append(
                BlinkEvent(
                    start_frame=idx_to_frame.get(global_left, global_left),
                    end_frame=idx_to_frame.get(global_right, global_right),
                    peak_frame=idx_to_frame.get(global_idx, global_idx),
                    peak_value=peak["peak_value"],
                    prominence=peak["prominence"],
                    width_ms=width_ms,
                    duration_ms=duration_ms,
                    confidence=conf,
                    segment_baseline=peak["baseline"],
                    data_index=global_idx,
                )
            )

    all_blinks.sort(key=lambda b: b.peak_frame)

    if verbose:
        print(f"  Segments analyzed: {len(segments)}")
        total = len(all_blinks) + rejected
        print(
            f"  Peaks found: {total}, kept: {len(all_blinks)}, "
            f"rejected (conf < {min_confidence}): {rejected}"
        )

    return all_blinks
