"""WizardKit: tmux Functions"""
# vim: sts=2 sw=2 ts=2

import logging
import pathlib

from typing import Any

from wk.exe import run_program
from wk.std import PLATFORM


# STATIC_VARIABLES
LOG = logging.getLogger(__name__)


# Functions
def capture_pane(pane_id: str | None = None) -> str:
  """Capture text from current or target pane, returns str."""
  cmd = ['tmux', 'capture-pane', '-p']
  if pane_id:
    cmd.extend(['-t', pane_id])

  # Capture and return
  proc = run_program(cmd, check=False)
  return proc.stdout.strip()


def clear_pane(pane_id: str | None = None) -> None:
  """Clear pane buffer for current or target pane."""
  commands = [
    ['tmux', 'send-keys', '-R'],
    ['tmux', 'clear-history'],
    ]
  if pane_id:
    commands = [[*cmd, '-t', pane_id] for cmd in commands]

  # Clear pane
  for cmd in commands:
    run_program(cmd, check=False)


def fix_layout(
    layout: dict[str, dict[str, Any]],
    clear_on_resize: bool = False,
    forced: bool = False,
    ) -> None:
  """Fix pane sizes based on layout.

  NOTE: The magic +/- 1 values are for the split rows/columns.
  """
  resize_kwargs = []

  # Bail early
  if not (forced or layout_needs_fixed(layout)):
    # Layout should be fine
    return

  # Clear current pane if needed
  if clear_on_resize:
    clear_pane()

  # Remove closed panes
  for data in layout.values():
    data['Panes'] = [pane for pane in data['Panes'] if poll_pane(pane)]

  # Calculate constraints
  avail_horizontal, avail_vertical = get_window_size()
  avail_vertical -= layout['Current'].get('height', 0)
  for group in ('Title', 'Subtitle', 'Info'):
    if not layout[group]['Panes']:
      continue
    avail_vertical -= layout[group].get('height', 0) + 1
  num_workers = len(layout['Workers']['Panes'])
  avail_vertical -= num_workers * (layout['Workers'].get('height', 0) + 1)
  avail_horizontal -= layout['Progress']['width'] + 1

  # Fix heights
  for group, data in layout.items():
    if not data['Panes'] or group in ('Started', 'Progress'):
      continue
    resize_kwargs.append(
      {'pane_id': data['Panes'][0], 'height': data.get('height', avail_vertical)}
    )
    if group == 'Workers' and len(data['Panes']) > 1:
      for pane_id in data['Panes'][1:]:
        resize_kwargs.append(
          {'pane_id': pane_id, 'height': data.get('height', avail_vertical)}
        )

  # Fix widths
  resize_kwargs.append(
    {'pane_id': layout['Progress']['Panes'][0], 'width': layout['Progress']['width']}
    )
  resize_kwargs.append(
    {'pane_id': layout['Started']['Panes'][0], 'height': layout['Started']['height']}
    )
  for group, data in layout.items():
    num_panes = len(data['Panes'])
    if num_panes < 2 or group not in ('Title', 'Subtitle', 'Info'):
      continue
    pane_width, remainder = divmod(avail_horizontal - (num_panes-1), num_panes)
    for pane_id in data['Panes']:
      new_width = pane_width
      if remainder > 0:
        new_width += 1
        remainder -= 1
      resize_kwargs.append({'pane_id': pane_id, 'width': new_width})

  # Resize panes
  for kwargs in resize_kwargs:
    try:
      resize_pane(**kwargs)
    except RuntimeError:
      # Assuming pane was closed just before resizing
      pass


def get_pane_size(pane_id: str | None = None) -> tuple[int, int]:
  """Get current or target pane size, returns tuple."""
  cmd = ['tmux', 'display', '-p']
  if pane_id:
    cmd.extend(['-t', pane_id])
  cmd.append('#{pane_width} #{pane_height}')

  # Get resolution
  proc = run_program(cmd, check=False)
  try:
    width, height = proc.stdout.strip().split()
  except ValueError:
    # Assuming this is a race condition as it usually happens inside the
    # background fix layout loop
    return 0, 0
  width = int(width)
  height = int(height)

  # Done
  return (width, height)


def get_window_size() -> tuple[int, int]:
  """Get current window size, returns tuple."""
  cmd = ['tmux', 'display', '-p', '#{window_width} #{window_height}']

  # Get resolution
  proc = run_program(cmd, check=False)
  width, height = proc.stdout.strip().split()
  width = int(width)
  height = int(height)

  # Done
  return (width, height)


def kill_all_panes(pane_id: str | None = None) -> None:
  """Kill all panes except for the current or target pane."""
  cmd = ['tmux', 'kill-pane', '-a']
  if pane_id:
    cmd.extend(['-t', pane_id])

  # Kill
  run_program(cmd, check=False)


def kill_pane(*pane_ids: str) -> None:
  """Kill pane(s) by id."""
  cmd = ['tmux', 'kill-pane', '-t']

  # Iterate over all passed pane IDs
  for pane_id in pane_ids:
    run_program(cmd+[pane_id], check=False)


def layout_needs_fixed(layout: dict[str, dict[str, Any]]) -> bool:
  """Check if layout needs fixed, returns bool."""
  needs_fixed = False

  # Check panes
  for data in layout.values():
    if 'height' in data:
      needs_fixed = needs_fixed or any(
        get_pane_size(pane)[1] != data['height'] for pane in data['Panes']
        )
    if 'width' in data:
      needs_fixed = needs_fixed or any(
        get_pane_size(pane)[0] != data['width'] for pane in data['Panes']
        )

  # Done
  return needs_fixed


def poll_pane(pane_id: str) -> bool:
  """Check if pane exists, returns bool."""
  cmd = ['tmux', 'list-panes', '-F', '#D']

  # Get list of panes
  proc = run_program(cmd, check=False)
  existant_panes = proc.stdout.splitlines()

  # Check if pane exists
  return pane_id in existant_panes


def prep_action(
    cmd: str | None = None,
    working_dir: pathlib.Path | str | None = None,
    text: str | None = None,
    watch_file: pathlib.Path | str | None = None,
    watch_cmd: str = 'cat',
    ) -> list[str]:
  """Prep action to perform during a tmux call, returns list.

  This will prep for running a basic command, displaying text on screen,
  or monitoring a file. The last option uses cat by default but can be
  overridden by using the watch_cmd.
  """
  action_cmd = []
  if working_dir:
    action_cmd.extend(['-c', working_dir])

  if cmd:
    # Basic command
    action_cmd.append(cmd)
  elif text:
    # Display text
    echo_cmd = ['echo']
    if PLATFORM == 'Linux':
      echo_cmd.append('-e')
    action_cmd.extend([
      'watch',
      '--color',
      '--exec',
      '--no-title',
      '--interval', '1',
      ])
    action_cmd.extend(echo_cmd)
    action_cmd.append(text)
  elif watch_file:
    # Monitor file
    prep_file(watch_file)
    if watch_cmd == 'cat':
      action_cmd.extend([
        'watch',
        '--color',
        '--no-title',
        '--interval', '1',
        'cat',
        ])
    elif watch_cmd == 'tail':
      action_cmd.extend(['tail', '-f'])
    action_cmd.append(watch_file)
  else:
    LOG.error('No action specified')
    raise RuntimeError('No action specified')

  # Done
  return action_cmd


def prep_file(path: pathlib.Path | str) -> None:
  """Check if file exists and create empty file if not."""
  path = pathlib.Path(path).resolve()
  try:
    path.touch(exist_ok=False)
  except FileExistsError:
    # Leave existing files alone
    pass


def resize_pane(
    pane_id: str | None = None,
    width: int | None = None,
    height: int | None = None,
    ) -> None:
  """Resize current or target pane.

  NOTE: kwargs is only here to make calling this function easier
        by dropping any extra kwargs passed.
  """
  cmd = ['tmux', 'resize-pane']

  # Safety checks
  if not (width or height):
    LOG.error('Neither width nor height specified')
    raise RuntimeError('Neither width nor height specified')

  # Finish building cmd
  if pane_id:
    cmd.extend(['-t', pane_id])
  if width:
    cmd.extend(['-x', str(width)])
  if height:
    cmd.extend(['-y', str(height)])

  # Resize
  run_program(cmd, check=False)


def respawn_pane(pane_id: str, **action) -> None:
  """Respawn pane with action."""
  cmd = ['tmux', 'respawn-pane', '-k', '-t', pane_id]
  cmd.extend(prep_action(**action))

  # Respawn
  run_program(cmd, check=False)


def split_window(
    lines: int | None = None,
    percent: int | None = None,
    behind: bool = False,
    vertical: bool = False,
    target_id: str | None = None,
    **action) -> str:
  """Split tmux window, run action, and return pane_id as str."""
  cmd = ['tmux', 'split-window', '-d', '-PF', '#D']

  # Safety checks
  if not (lines or percent):
    LOG.error('Neither lines nor percent specified')
    raise RuntimeError('Neither lines nor percent specified')

  # New pane placement
  if behind:
    cmd.append('-b')
  if vertical:
    cmd.append('-v')
  else:
    cmd.append('-h')
  if target_id:
    cmd.extend(['-t', target_id])

  # New pane size
  if lines:
    cmd.extend(['-l', str(lines)])
  elif percent:
    cmd.extend(['-p', str(percent)])

  # New pane action
  cmd.extend(prep_action(**action))

  # Run and return pane_id
  proc = run_program(cmd, check=False)
  return proc.stdout.strip()


def zoom_pane(pane_id: str | None = None) -> None:
  """Toggle zoom status for current or target pane."""
  cmd = ['tmux', 'resize-pane', '-Z']
  if pane_id:
    cmd.extend(['-t', pane_id])

  # Toggle
  run_program(cmd, check=False)


if __name__ == '__main__':
  print("This file is not meant to be called directly.")
