package de.qwertyuiop.copret

import os.Path
import Terminal.*
import syntax.*

case class Presentation(
    slides: Vector[Slide],
    meta: Map[String, String] = Map.empty,
):
  def start(using keymap: Keymap = Keymap.default) =
    Terminal.enterRawMode()
    Terminal.hideCursor()
    Thread.sleep(1000)
    run()

  import Presentation._
  def run()(using keymap: Keymap) =
    logger.info("Starting presentation")
    import SlideAction.*
    @annotation.tailrec
    def rec(currentPos: Int, action: SlideAction): Unit =
      logger.info(s"Executing slide $currentPos with action $action")

      inline def redraw() =
        logger.info(s"redrawing ${currentPos}")
        // rec(currentPos - 1, QuickNext)
        navigate(currentPos, true, 0)(executeQuick)

      inline def navigate(pos: Int, condition: Boolean, direction: Int)(
          executor: Int => Slide => Unit,
      ) =
        logger.info(s"Navigating from ${pos}, condition is $condition, direction is ${direction}")
        if condition then
          executor(pos + direction)(slides(pos + direction))
          rec(pos + direction, waitkey)
        else rec(pos, waitkey)

      inline def runInteractive(cmd: Vector[String], path: Path) =
        logger.info(s"Running interactive command ${cmd} with working directory ${path}")
        Terminal.showCursor()
        try os.proc(cmd).call(cwd = path)
        catch case _ => ()
        Terminal.hideCursor()
        Terminal.clear()
        redraw()

      action match
        case Start                  => navigate(0, true, 0)(executeSlide)
        case Next                   => navigate(currentPos, currentPos + 1 < slides.size, 1)(executeSlide)
        case QuickNext              => navigate(currentPos, currentPos + 1 < slides.size, 1)(executeQuick)
        case Prev                   => navigate(currentPos, currentPos > 0, -1)(executeQuick)
        case Interactive(cmd, path) => runInteractive(cmd, path)

        case Goto(target) =>
          for i <- 0 until target
          do executeSilent(i)()
          rec(target - 1, QuickNext)

        case GotoSelect =>
          promptSlide() match
            case Some(i) => rec(currentPos, Goto(i))
            case None    => redraw()

        case Help =>
          Terminal.clear()
          println(keymap.help)
          waitkey
          redraw()

        case Other(codes) =>
          Terminal.printStatus(action.show)
          rec(currentPos, waitkey)

        case Quit =>
          Terminal.showCursor()
      end match
    end rec
    rec(0, Start)
  end run

  def promptSlide() =
    val maxSlide = slides.size
    prompt(s"Go to slide (1 - $maxSlide):", _.toIntOption)(
      (res, input) => res.filter((1 to maxSlide).contains).isEmpty && input.nonEmpty,
      in => s"No such slide: $in (empty input to abort)",
    ).map(_ - 1)

  def executeSlide(pos: Int)(
      slide: Slide = slides(pos),
  ): Unit = slide match
    case Paragraph(contents)                        => println(contents)
    case Clear                                      => Terminal.clear()
    case PauseKey                                   => waitkey(using Keymap.empty)
    case Pause(msec)                                => Thread.sleep(msec)
    case Image(file, None)                          => println(KittyGraphicsProtocol.showImage(file))
    case Image(file, Some(ImageSize(w, h, aspect))) =>
      import KittyGraphicsProtocol.Sizing.*
      println(KittyGraphicsProtocol.showImage(file, Absolute(w.toInt), Absolute(h.toInt), aspect)) // TODO
    case cmd: TypedCommand[_]                       => cmd.show()
    case Silent(actions)                            => actions()
    case Group(slides)                              => slides.foreach(executeSlide(pos))
    case lios @ LazyIOSlide(_, display)             => executeSlide(pos)(lios.genSlide())
    case Meta(genSlide)                             => executeSlide(pos)(genSlide(this, pos))

  def executeQuick(pos: Int)(
      slide: Slide = slides(pos),
  ): Unit = slide match
    case Pause(msec)                    => ()
    case PauseKey                       => ()
    case cmd: TypedCommand[_]           => cmd.quickShow()
    case Group(slides)                  => slides.foreach(executeQuick(pos))
    case lios @ LazyIOSlide(_, display) => executeQuick(pos)(lios.genSlide())
    case _                              => executeSlide(pos)(slide)

  def executeSilent(pos: Int)(
      slide: Slide = slides(pos),
  ): Unit = slide match
    case cmd: TypedCommand[_]                         => cmd.force()
    case Group(slides)                                => slides.foreach(executeSilent(pos))
    case lios @ LazyIOSlide(_, display)               =>
      executeSilent(pos)(lios.genSlide())
    case Paragraph(_) | Image(_, _) | Clear | Meta(_) => ()
    case _                                            => executeQuick(pos)(slide)
end Presentation
