"""
Data conversion helpers: seq_data / sync_data -> GUI-friendly structures.
No Qt imports - safe to import in tests.
"""
from __future__ import annotations

from dataclasses import dataclass
from typing import List, Optional

import numpy as np


@dataclass
class BlockRow:
    sync_index: int
    orig_index: int          # -1 for inserted delay blocks
    duration: float          # seconds
    block_type: str
    has_rf: bool
    has_adc: bool
    has_grad: bool
    is_delay: bool
    delay_type: str          # "START" | "RF" | "TR" | ""
    gate_rf: int
    gate_adc: int
    gate_tr: int
    t_start: float           # cumulative start time (s)
    t_end: float             # cumulative end time (s)


def build_block_rows(seq_data: dict, sync_data: dict) -> List[BlockRow]:
    """
    Reconstruct the mapping between sync blocks and original sequence blocks.

    The Synchronizer inserts:
      - one START_DELAY block at position 0
      - one TR_DELAY block before each ADC block
      - one RF_DELAY block before each RF (non-ADC) block

    Returns one BlockRow per entry in sync_data["blocks_duration"].
    """
    blocks = seq_data.get("blocks", [])
    gate_adc = sync_data.get("gate_adc", [])
    gate_rf = sync_data.get("gate_rf", [])
    gate_tr = sync_data.get("gate_tr_switch", [])
    durs = sync_data.get("blocks_duration", [])

    if not durs:
        return []

    cumtimes = np.cumsum([0.0] + list(durs))

    def _row(si: int, oi: int, btype: str,
             has_rf=False, has_adc=False, has_grad=False,
             is_delay=False, delay_type="") -> BlockRow:
        return BlockRow(
            sync_index=si,
            orig_index=oi,
            duration=float(durs[si]) if si < len(durs) else 0.0,
            block_type=btype,
            has_rf=has_rf,
            has_adc=has_adc,
            has_grad=has_grad,
            is_delay=is_delay,
            delay_type=delay_type,
            gate_rf=int(gate_rf[si]) if si < len(gate_rf) else 0,
            gate_adc=int(gate_adc[si]) if si < len(gate_adc) else 0,
            gate_tr=int(gate_tr[si]) if si < len(gate_tr) else 0,
            t_start=float(cumtimes[si]),
            t_end=float(cumtimes[si + 1]) if si + 1 < len(cumtimes) else float(cumtimes[-1]),
        )

    rows: List[BlockRow] = []

    # Index 0 is always the START_DELAY block
    r = _row(0, -1, "START_DELAY", is_delay=True, delay_type="START")
    rows.append(r)

    si = 1
    for oi, blk in enumerate(blocks):
        if si >= len(durs):
            break

        has_adc = blk.get("has_adc", False)
        has_rf = "RF" in blk.get("type", [])
        has_grad = "GRAD" in blk.get("type", [])

        # Inserted delay before this block
        if has_adc:
            rows.append(_row(si, oi, "TR_DELAY", is_delay=True, delay_type="TR"))
            si += 1
        elif has_rf:
            rows.append(_row(si, oi, "RF_DELAY", is_delay=True, delay_type="RF"))
            si += 1

        if si >= len(durs):
            break

        btype = ", ".join(blk.get("type", [])) or "DELAY"
        rows.append(_row(si, oi, btype,
                         has_rf=has_rf, has_adc=has_adc, has_grad=has_grad))
        si += 1

    return rows


def gate_to_step(gate: list, durs: list):
    """
    Convert gate signal + block durations to (t, v) arrays for step-function plot.
    Returns two numpy arrays of shape (2*n,).
    """
    n = min(len(gate), len(durs))
    cumtimes = np.cumsum([0.0] + list(durs[:n]))
    t = np.empty(n * 2)
    v = np.empty(n * 2, dtype=float)
    for i in range(n):
        t[2 * i] = cumtimes[i]
        t[2 * i + 1] = cumtimes[i + 1]
        v[2 * i] = gate[i]
        v[2 * i + 1] = gate[i]
    return t, v


def block_cumtimes(durs: list) -> np.ndarray:
    """Cumulative time boundaries from blocks_duration list."""
    return np.cumsum([0.0] + list(durs))


def find_block_at_time(t: float, block_rows: List[BlockRow]) -> Optional[BlockRow]:
    """Return the BlockRow whose [t_start, t_end) interval contains t."""
    for row in block_rows:
        if row.t_start <= t < row.t_end:
            return row
    return None


def validate_timing(hw, seq_data: dict, sync_data: dict) -> List[str]:
    """Return a list of human-readable warning strings."""
    warnings: List[str] = []
    mbd = hw.MIN_BLOCK_DURATION

    for name, val in [("RF_DELAY", hw.RF_DELAY),
                      ("TR_DELAY", hw.TR_DELAY),
                      ("START_DELAY", hw.START_DELAY)]:
        if val < mbd:
            warnings.append(
                f"{name} ({val * 1e9:.1f} ns) < MIN_BLOCK_DURATION ({mbd * 1e9:.1f} ns)"
            )

    timer = sync_data.get("synchro_block_timer", mbd)
    for i, dur in enumerate(sync_data.get("blocks_duration", [])):
        if dur > 0 and timer > 0:
            cl = dur / timer
            if int(cl) == 0:
                warnings.append(
                    f"Sync block {i}: CL rounds to zero (dur={dur * 1e9:.1f} ns)"
                )

    # Raster alignment - warn once if any block violates
    raster = hw.block_duration_raster
    if raster > 0:
        for i, dur in enumerate(sync_data.get("blocks_duration", [])):
            if dur > 0:
                rem = dur % raster
                if 1e-15 < rem < raster - 1e-15:
                    warnings.append(
                        f"Block {i}: duration {dur * 1e6:.3f} uss not aligned to "
                        f"block_duration_raster {raster * 1e6:.3f} uss (first occurrence)"
                    )
                    break

    for key in ["rf", "t_rf"]:
        if key not in seq_data:
            warnings.append(f"Waveform '{key}' not found in sequence data")

    if "rf" in seq_data and "t_rf" in seq_data:
        if len(seq_data["rf"]) != len(seq_data["t_rf"]):
            warnings.append(
                f"RF length mismatch: rf={len(seq_data['rf'])}, t_rf={len(seq_data['t_rf'])}"
            )

    for axis in ["gx", "gy", "gz"]:
        t_key = f"t_{axis}"
        if axis in seq_data and t_key in seq_data:
            if len(seq_data[axis]) != len(seq_data[t_key]):
                warnings.append(f"{axis.upper()} length mismatch")

    return warnings


def seq_metadata(seq_data: dict, hw) -> dict:
    """Return a flat dict of human-readable sequence metadata."""
    blocks = seq_data.get("blocks", [])
    params = seq_data.get("params", {})
    return {
        "Total blocks (orig)": len(blocks),
        "RF blocks": sum(1 for b in blocks if "RF" in b.get("type", [])),
        "ADC blocks": sum(1 for b in blocks if b.get("has_adc", False)),
        "Grad blocks": sum(1 for b in blocks if "GRAD" in b.get("type", [])),
        "RF raster (uss)": f"{hw.rf_raster_time * 1e6:.4f}",
        "Grad raster (uss)": f"{hw.grad_raster_time * 1e6:.4f}",
        "ADC raster (ns)": f"{hw.adc_raster_time * 1e9:.4f}",
        "Block raster (uss)": f"{hw.block_duration_raster * 1e6:.4f}",
        "RF delay (ns)": f"{hw.RF_DELAY * 1e9:.1f}",
        "TR delay (ns)": f"{hw.TR_DELAY * 1e9:.1f}",
        "Start delay (uss)": f"{hw.START_DELAY * 1e6:.4f}",
        "Min block dur (ns)": f"{hw.MIN_BLOCK_DURATION * 1e9:.1f}",
        "Gamma (MHz/T)": f"{hw.gamma / 1e6:.4f}",
        "RF scale": f"{params.get('scale_rf', 1.0):.4f}",
    }
