package de.qwertyuiop.copret
import os.Path
import org.jline.terminal.TerminalBuilder
import org.jline.reader.LineReaderBuilder
import scala.io.StdIn
import cats.*
import scala.collection.IndexedSeqView

object Terminal:
  def runProcess(cmd: Vector[String])(using cwd: os.Path): String =
    try os.proc(cmd.map(identity)).call(cwd = cwd).out.text()
    catch case e: os.SubprocessException => e.result.err.text()

  def tryCmd[T](cmd: => T, default: => T) =
    try cmd
    catch case e: os.SubprocessException => default

  def enterRawMode(): Unit =
    os.proc("sh", "-c", "stty -icanon min 1 < /dev/tty")
      .call(stdin = os.Inherit, stdout = os.Inherit, stderr = os.Inherit)
    os.proc("sh", "-c", "stty -echo < /dev/tty").call(stdin = os.Inherit, stdout = os.Inherit, stderr = os.Inherit)

  private[copret] lazy val jterm      = org.jline.terminal.TerminalBuilder.terminal()
  private[copret] lazy val lineReader =
    LineReaderBuilder.builder().terminal(jterm).build()

  def waitkey(implicit keymap: Keymap): SlideAction =
    // ignore keypresses done during slide animations
    while (System.in.available() > 0) System.in.read

    var key = scala.collection.mutable.ArrayBuffer[Int]()
    key += System.in.read
    while (System.in.available() > 0)
      key += System.in.read
    keymap(key.toList)

  def queryTerm(query: String): String =
    val ttyIn    = new java.io.FileInputStream("/dev/tty")
    val ttyOut   = new java.io.PrintStream(new java.io.FileOutputStream("/dev/tty"))
    ttyOut.print(query)
    var response = scala.collection.mutable.ArrayBuffer[Int]()
    response += ttyIn.read()
    while (ttyIn.available > 0 && response.last != '\u0007')
      response += ttyIn.read()
    new String(response.toArray, 0, response.length)

  case class EscapeSequence()
  def parseEscape(sequence: String): Option[EscapeSequence] = ???

  def prompt[T](prefix: String, parse: String => T)(
      retry: (T, String) => Boolean = (t: T, s: String) => false,
      error: String => String = in => s"Invalid input: $in",
  ): T =
    val input  = lineReader.readLine(prefix + " ")
    val result = parse(input)
    if retry(result, input) then
      println(error(input))
      prompt(prefix, parse)(retry, error)
    else result

  def isTmux = sys.env.contains("TMUX") || sys.env("TERM").startsWith("screen")

  def csi = if (isTmux) "\u001bPtmux\u001b\u001b[" else "\u001b["
  def osc = if (isTmux) "\u001bPtmux\u001b\u001b]" else "\u001b]"
  def apc = if (isTmux) "\u001bPtmux;\u001b\u001b_" else "\u001b_"
  def st  = if (isTmux) "\u0007\u001b\\" else "\u001b\\"

  def stripEscapes(str: String) =
    // match CSI until letter or OSC/APC until ST
    str.replaceAll("\u001b(\\[[^@-~]*[@-~]|(\\]|_).*?(\u0007|\u001b\\\\))", "")

  def hideCursor()                   = print(s"${csi}?25l")
  def showCursor()                   = print(s"${csi}?25h")
  def cursorTo(row: Int, col: Int)   = print(s"${csi}${row};${col}H")
  def clear()                        = print(s"${csi}2J${csi};H")
  def printStatus(msg: String): Unit =
    cursorTo(jterm.getSize.getColumns, 1)
    print(msg)

  def showImage(
      img: os.Path,
      width: String = "100%",
      height: String = "100%",
      keepAspect: Boolean = true,
  ) =
    import java.util.Base64
    val image  = Base64.getEncoder.encodeToString(os.read.bytes(img))
    val aspect = if (keepAspect) 1 else 0 // TODO
    if image.length > 4096 then
      val chunks = image.grouped(4096).toVector

      s"${apc}Gf=100,t=d,m=1,a=T;${chunks.head}${st}" +
        chunks.tail.init.map(c => s"${apc}Gm=1;${c}${st}").mkString +
        s"${apc}Gm=0;${chunks.last}${st}"
    else s"${apc}Gf=100,t=d,a=T;${image}${st}"

  private val CursorPosResponse = """\u001b\[(\d+);(\d+)R""".r

  def getCursorPos(): Option[CursorPos] =
    queryTerm(s"${csi}6n") match
      case CursorPosResponse(rows, cols) => Some(CursorPos(cols.toInt, rows.toInt))
      case _                             => None

  def getSizeCells(): Option[TermCells] =
    queryTerm(s"${csi}s${csi}999;999H${csi}6n${csi}u") match
      case CursorPosResponse(rows, cols) => Some(TermCells(cols.toInt, rows.toInt))
      case _                             => None

  private val SizeResponse = """\u001b\[4;(\d+);(\d+)t""".r

  case class TermPixels(width: Int, height: Int)
  case class TermCells(cols: Int, rows: Int)
  case class TermSize(pixels: TermPixels, cells: TermCells)
  case class CursorPos(cols: Int, rows: Int)
  def getSizePixels(): Option[TermPixels] = queryTerm(s"${csi}14t") match
    case SizeResponse(rows, cols) => Some(TermPixels(cols.toInt, rows.toInt))
    case _                        => None

  def getSize(): Option[TermSize] =
    for
      pixels <- getSizePixels()
      cells  <- getSizeCells()
    yield TermSize(pixels, cells)

end Terminal

private[copret] trait TerminalSyntax:
  import Terminal._

  def %%%(cmd: String*)(using cwd: os.Path): String = runProcess(cmd.toVector)
  def columns                                       = jterm.getSize.getColumns
  def rows                                          = jterm.getSize.getRows

/* vim:set tw=120: */
