package de.qwertyuiop.copret
import os.Path

import Terminal.*
import syntax.*

case class ImageSize(width: Double, height: Double, keepAspect: Boolean)

sealed trait Slide
case class Paragraph(contents: String) extends Slide:
  def centerVertical(height: Int): Paragraph =
    val lines = contents.toString.count(_ == '\n') + 1
    val pad   = "\n" * ((height - lines) / 2)
    Paragraph(pad + contents + pad)

object Paragraph:
  def apply(str: fansi.Str): Paragraph = Paragraph(str.toString)

case class Image(path: Path, sizing: Option[ImageSize]) extends Slide
object Image:
  def apply(path: Path)                                                      = new Image(path, None)
  def scaled(path: Path, width: Double, height: Double, keepAspect: Boolean) =
    Image(path, Some(ImageSize(width, height, keepAspect)))

case object Clear                                       extends Slide
case class Pause(millisec: Long)                        extends Slide
case object PauseKey                                    extends Slide
case class Meta(contents: (Presentation, Int) => Slide) extends Slide

case class TypedCommand[T](
    exec: T => String,
    display: String,
    cmd: T,
    cmdIsHidden: Boolean,
    outputIsHidden: Boolean,
) extends Slide:
  private lazy val _output = exec(cmd)
  def output               = _output

  infix def showing(s: String): TypedCommand[T] = TypedCommand(exec, s, cmd)

  def show() =
    force()
    if !cmdIsHidden then
      prompt()
      Terminal.showCursor()
      typeCmd()
    if !outputIsHidden then print(output)
    if !cmdIsHidden then Terminal.hideCursor()

  def quickShow() =
    force()
    if !cmdIsHidden then
      prompt()
      println(display)
    if !outputIsHidden then print(output)

  def prompt() = print(fansi.Color.LightGreen("user@host % "))
  def force()  = _output

  def display(s: String) = copy(display = s)

  private def typeCmd() =
    for char <- display do
      print(char)
      Thread.sleep(50 + scala.util.Random.nextInt(80))
    println()

  /* Conditionally disable execution. Useful for e.g. a debug mode, or a non-interactive mode */
  def disable(altDisplay: String = display, condition: Boolean = true) =
    if condition then copy(display = altDisplay, exec = (_: T) => "")
    else this

  /* Conditionally replace the executed command (but still displaying the same). Useful for e.g. a non-interactive mode,
   * where a call to an editor is replaced with a file operation */
  def replaceIf(condition: Boolean)(tc: TypedCommand[_]): TypedCommand[_] =
    if condition then tc.showing(display)
    else this
end TypedCommand

object TypedCommand:
  val shell = sys.env.getOrElse("SHELL", "sh")

  def run(using Path): Vector[String] => String =
    c => runProcess(c)

  def runShell(using Path): Vector[String] => String =
    c => runProcess(Vector(shell, "-c", c.mkString(" ")))

  def runInteractive(using cwd: Path): Vector[String] => String =
    c =>
      Terminal.showCursor()
      os.proc(c).call(stdin = os.Inherit, stdout = os.Inherit, stderr = os.Inherit, cwd = cwd)
      Terminal.hideCursor()
      ""

  def apply(cmd: String*)(using Path): TypedCommand[Vector[String]] =
    TypedCommand(run, cmd.mkString(" "), cmd.toVector)

  def apply[T](exec: T => String, display: String, cmd: T): TypedCommand[T] =
    TypedCommand(exec, display, cmd, false, false)

  def shell(cmd: String*)(using Path): TypedCommand[Vector[String]] =
    TypedCommand(runShell, cmd.mkString(" "), cmd.toVector)

  def fake(cmd: String): TypedCommand[String] =
    TypedCommand(_ => "", cmd, cmd)

  def interactive(cmd: String*)(using Path): TypedCommand[Vector[String]] =
    TypedCommand(runInteractive, cmd.mkString(" "), cmd.toVector)

  def interactiveUntyped(cmd: String*)(using Path): TypedCommand[Vector[String]] =
    TypedCommand(runInteractive, cmd.mkString(" "), cmd.toVector, cmdIsHidden = true, outputIsHidden = false)

  def untyped(cmd: String*)(using Path): TypedCommand[Vector[String]] =
    TypedCommand(exec = run, display = "", cmd.toVector, cmdIsHidden = true, outputIsHidden = false)
end TypedCommand

sealed abstract case class Silent[T] private (doStuff: () => T) extends Slide
object Silent:
  def apply[T](doStuff: => T) = new Silent(() => doStuff) {}

case class Group(slides: List[Slide]) extends Slide
object Group:
  def apply(slides: Slide*): Group = Group(slides.toList)

case class LazyIOSlide[T](runOnce: () => T, display: T => Slide) extends Slide:
  private lazy val data = runOnce()
  def genSlide(): Slide = display(data)

trait SlideSyntax:
  private[copret] class LazyIOSlideBuilder[T](runOnce: => T):
    def useIn(display: T => Slide) = LazyIOSlide(() => runOnce, display)

  def prepare[T](runOnce: => T): LazyIOSlideBuilder[T] = LazyIOSlideBuilder(
    runOnce,
  )

/* vim:set tw=120: */
