import itertools
import threading
import time
import sys
from datetime import timedelta


def _safe_write(text: str) -> None:
    """Write to stdout, falling back to ASCII replacement on encoding errors (e.g. Windows cp1252)."""
    try:
        sys.stdout.write(text)
    except UnicodeEncodeError:
        sys.stdout.write(text.encode("ascii", errors="replace").decode("ascii"))
    sys.stdout.flush()


def loop_status(
    self,
    iterable,
    message="Processing",
    delay=0.1,
    show_spinner=True,
    show_percent=False,
    end_message="done",
):
    """
    Displays a live terminal spinner while looping through an iterable.

    Yields each item from iterable while a spinner runs in a background thread.
    The background thread is always stopped — even if the loop breaks early or
    raises an exception — because cleanup happens inside a finally block.

    Args:
        iterable: The iterable to loop through.
        message: Text to show alongside the spinner.
        delay: Delay between spinner frames (in seconds).
        show_spinner: Toggle spinner display.
        show_percent: Display progress as a percentage.
        end_message: Message displayed after loop completion.
    """
    spinner = itertools.cycle(["|", "/", "-", "\\"])
    stop_event = threading.Event()
    total = len(iterable) if hasattr(iterable, "__len__") else None
    start_time = time.time()

    def spin():
        index = 0
        while not stop_event.is_set():
            spin_char = next(spinner) if show_spinner else ""
            percent_text = ""
            if show_percent and total:
                percent = int((index / total) * 100)
                percent_text = f" {percent:3d}%"
            _safe_write(f"\r[{self.name}] {message}{percent_text} {spin_char}")
            time.sleep(delay)
            index += 1

        elapsed = timedelta(seconds=time.time() - start_time)
        elapsed_str = (
            str(elapsed).split(".")[0]
            if elapsed.total_seconds() > 60
            else f"{elapsed.total_seconds():.2f}s"
        )
        _safe_write(
            f"\r[{self.name}] {message} {end_message} (Elapsed time: {elapsed_str})\n"
        )

    def wrapped():
        t = threading.Thread(target=spin)
        t.daemon = True
        t.start()
        try:
            for item in iterable:
                yield item
        finally:
            stop_event.set()
            t.join()

    return wrapped()
