# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../pts/mod/_utils/00_base.pct.py.

# %% auto 0
__all__ = ['get_repo_full_name_from_sub_path', 'get_hostname', 'run_fzf', 'check_last_time_modified', 'run_cmd_async',
           'async_throttler', 'SoftInterruption', 'enable_soft_interruption', 'check_interrupted']

# %% ../../../pts/mod/_utils/00_base.pct.py 3
import subprocess
import shlex
import json
import asyncio
from enum import Enum
from .. import const
from pathlib import Path
from typing import Any, Coroutine

import repoyard.config

# %% ../../../pts/mod/_utils/00_base.pct.py 5
def get_repo_full_name_from_sub_path(
    config: repoyard.config.Config,
    sub_path: str,
) -> Path|None:
    """
    Get the full name of a synced repo from a path inside of the repo.
    """
    sub_path = Path(sub_path).expanduser().resolve() # Need to resolve to replace symlinks
    is_in_local_store_path = sub_path.is_relative_to(config.local_store_path)
    
    if not is_in_local_store_path:
        return None
    
    rel_path = sub_path.relative_to(config.local_store_path)
    
    if len(rel_path.parts) < 2: # The path is not inside a repo
        return None
    
    repo_full_name = rel_path.parts[1]
    return repo_full_name

# %% ../../../pts/mod/_utils/00_base.pct.py 7
import platform
import subprocess

def get_hostname():
    system = platform.system()
    hostname = None
    if system == "Darwin":
        # Mac
        try:
            result = subprocess.run(["scutil", "--get", "ComputerName"], capture_output=True, text=True, check=True)
            hostname = result.stdout.strip()
        except Exception:
            hostname = None
    if hostname is None:
        hostname = platform.node()
    return hostname

# %% ../../../pts/mod/_utils/00_base.pct.py 9
def run_fzf(terms: list[str], disp_terms: list[str]|None=None):
    """
    Launches the fzf command-line fuzzy finder with a list of terms and returns
    the selected term.

    Parameters:
    terms (List[str]): A list of strings to be presented to fzf for selection.

    Returns:
    str or None: The selected string from fzf, or None if no selection was made
    or if fzf encountered an error.

    Raises:
    RuntimeError: If fzf is not installed or not found in the system PATH.
    """
    import subprocess
    if disp_terms is None: disp_terms = terms
    try:
        # Launch fzf with the list of strings
        result = subprocess.run(
            ['fzf'],
            input='\n'.join(disp_terms),
            text=True,
            capture_output=True
        )
        res_term = result.stdout.strip()
        term_index = [t.strip() for t in disp_terms].index(res_term)
        sel_term = terms[term_index]
        # Return the selected string or None if no selection was made
        if result.returncode != 0: 
            return None, None
        else: 
            return term_index, sel_term
    except FileNotFoundError:
        raise RuntimeError("fzf is not installed or not found in PATH.")

# %% ../../../pts/mod/_utils/00_base.pct.py 11
def check_last_time_modified(path: str | Path) -> float | None:
    from datetime import datetime, timezone
    path = Path(path).expanduser().resolve()
    if path.is_file():
        mtimes = [path.stat().st_mtime]
    else:
        mtimes = (p.stat().st_mtime for p in path.rglob("*") if p.is_file())
    max_mtime =  max(mtimes, default=None)
    return datetime.fromtimestamp(max_mtime, tz=timezone.utc) if max_mtime is not None else None

# %% ../../../pts/mod/_utils/00_base.pct.py 13
async def run_cmd_async(cmd: list[str]) -> subprocess.Popen:
    proc = await asyncio.create_subprocess_exec(
        *cmd,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE
    )
    stdout, stderr = await proc.communicate()
    stdout = stdout.decode('utf-8')
    stderr = stderr.decode('utf-8')
    return proc.returncode, stdout, stderr

# %% ../../../pts/mod/_utils/00_base.pct.py 16
async def async_throttler(
    coros: list[Coroutine],
    max_concurrency: int,
    timeout: float | None = None,
) -> list[Any]:
    """
    Throttle a list of coroutines to run concurrently.
    """

    sem = asyncio.Semaphore(max_concurrency)
    
    async def _task(coro: Coroutine) -> Any:
        async with sem:
            try:
                if timeout is None:
                    return await coro
                else:
                    return await asyncio.wait_for(coro, timeout)
            except asyncio.TimeoutError as e:
                return e
            except Exception as e:
                return e

    tasks = [_task(coro) for coro in coros]
    res = await asyncio.gather(*tasks, return_exceptions=True)
    for r in res:
        if isinstance(r, Exception):
            raise r
    return res

# %% ../../../pts/mod/_utils/00_base.pct.py 19
import signal
import sys

_interrupted = False
_interrupt_count = 0

class SoftInterruption(Exception):
    pass

def _soft_interruption_handler(signum, frame):
    global _interrupted, _interrupt_count
    _interrupt_count += 1
    sig_name = signal.Signals(signum).name

    if _interrupt_count < const.SOFT_INTERRUPT_COUNT:
        print(f"\nWARNING: {sig_name} received ({_interrupt_count}/3) — "
              f"will stop after the current operation.")
        _interrupted = True
    else:
        print(f"\n{sig_name} received 3 times — exiting immediately.")
        sys.exit(1)   # or: raise KeyboardInterrupt

def enable_soft_interruption():
    signal.signal(signal.SIGINT, _soft_interruption_handler)  # Ctrl-C
    signal.signal(signal.SIGTERM, _soft_interruption_handler)  # shutdown
    signal.signal(signal.SIGHUP, _soft_interruption_handler)  # logout / terminal closed

def check_interrupted():
    global _interrupted
    return _interrupted
