import Bliku

open Bliku.Tui

private def mkWorkspace : WorkspaceView :=
  {
    name := "test"
    desktop := {
      layout := .pane 0 { contentId := 0, cursor := { row := 0, col := 0 }, scrollRow := 0, scrollCol := 0 }
      activePaneId := 0
    }
  }

private def mkModel : Model :=
  {
    workspace := mkWorkspace
    buffers := #[]
    config := {
      resetStyle := "[/]"
      visualSelectionStyle := "[SEL]"
      cursorCharStyle := "[CUR]"
      cursorSpaceStyle := "[CUR]"
    }
  }

private def assertEq [ToString α] [BEq α] (name : String) (actual expected : α) : IO Unit := do
  if actual != expected then
    throw <| IO.userError s!"{name} failed\nexpected: {expected}\nactual:   {actual}"

private def utf8ByteOffsetTest : IO Unit := do
  let buf : BufferState :=
    {
      id := 0
      filename := none
      lines := #["あdef"]
      syntaxState := {
        lineSpans := #[(0, #[{ startByte := 3, endByte := 6, kind := .custom "test" }])]
        paletteOverrides := { custom := [("test", { fg := some (.ansi 196) })] }
      }
    }
  let rendered := renderVisibleLine mkModel {} buf 0 0 0 4 (some { row := 1, col := 0 })
  assertEq "utf8-byte-offset" rendered "あ\x1b[38;5;196mdef[/]"

private def builtinLeanHighlightTest : IO Unit := do
  let buf : BufferState :=
    {
      id := 0
      filename := some "Main.lean"
      lines := #["def あ := 1"]
    }
  let rendered := renderVisibleLine mkModel {} buf 0 0 0 20 none
  assertEq "builtin-lean-highlight" rendered "\x1b[38;5;12mdef[/] あ := \x1b[38;5;13m1[/]"

private def cursorSelectionPriorityTest : IO Unit := do
  let input : RenderInput := {
    selection := some { anchor := { row := 0, col := 0 }, cursor := { row := 0, col := 1 } }
  }
  let buf : BufferState :=
    {
      id := 0
      filename := none
      lines := #["abc"]
      syntaxState := {
        lineSpans := #[(0, #[{ startByte := 0, endByte := 3, kind := .custom "test" }])]
        paletteOverrides := { custom := [("test", { fg := some (.ansi 46) })] }
      }
    }
  let rendered := renderVisibleLine mkModel input buf 0 0 0 3 (some { row := 0, col := 1 })
  assertEq "cursor-selection-priority" rendered "[SEL]a[/][CUR]b[/]\x1b[38;5;46mc[/]"

private def floatingClusterLayoutTest : IO Unit := do
  let layout : Bliku.PaneLayout :=
    .hsplit
      (.pane 0 { contentId := 0, cursor := { row := 0, col := 0 }, scrollRow := 0, scrollCol := 0 })
      (.group 100 <|
        .vsplit
          (.pane 1 { contentId := 1, cursor := { row := 0, col := 0 }, scrollRow := 0, scrollCol := 0 })
          (.pane 2 { contentId := 2, cursor := { row := 0, col := 0 }, scrollRow := 0, scrollCol := 0 })
          0.5)
      0.5
  let desktop : Bliku.DesktopLayout := {
    layout := layout
    activePaneId := 2
    floating := {
      clusters := #[{ root := .group 100, rowOffset := 1, colOffset := 2 }]
    }
  }
  let ws : WorkspaceView := { name := "test", desktop := desktop }
  assertEq "floating-cluster-pane-1" (ws.isFloatingWindow 1) true
  assertEq "floating-cluster-pane-2" (ws.isFloatingWindow 2) true
  assertEq "floating-cluster-pane-0" (ws.isFloatingWindow 0) false
  let extracted := layout.extractFloatingRoot (.group 100) |>.map Bliku.PaneLayout.getPaneIds
  assertEq "floating-cluster-extract" extracted (some [1, 2])
  let removed := layout.removeFloatingRoot (.group 100) |>.map Bliku.PaneLayout.getPaneIds
  assertEq "floating-cluster-remove" removed (some [0])

private def floatingSizePolicyTest : IO Unit := do
  let defaultBounds :=
    Bliku.computeFloatingClusterBounds 50 160 0 { root := .pane 1, sizePolicy := .default }
  let multiBounds :=
    Bliku.computeFloatingClusterBounds 50 160 0 { root := .group 100, sizePolicy := .multiPane }
  let customBounds :=
    Bliku.computeFloatingClusterBounds 50 160 0 {
      root := .group 200
      sizePolicy := .custom {
        width := .fixed 120
        height := .ratio 0.5
        minWidth := some 80
        maxHeight := some 30
      }
    }
  let some (_, _, defaultH, defaultW) := defaultBounds
    | throw <| IO.userError "default bounds missing"
  let some (_, _, multiH, multiW) := multiBounds
    | throw <| IO.userError "multi bounds missing"
  let some (_, _, customH, customW) := customBounds
    | throw <| IO.userError "custom bounds missing"
  assertEq "floating-size-multipane-wider" (decide (multiW > defaultW)) true
  assertEq "floating-size-multipane-taller" (decide (multiH > defaultH)) true
  assertEq "floating-size-custom-width" customW 120
  assertEq "floating-size-custom-height" customH 24

private def floatingChromeBoxTest : IO Unit := do
  let box := Bliku.Widget.renderFloatingChromeBox
    { kind := .bordered, title := some "Explorer" }
    16 4
    "[A]" "[I]" "[/]"
    true
  assertEq "floating-chrome-line-count" box.lines.size 4
  assertEq "floating-chrome-inset-top" box.insetTop 1
  assertEq "floating-chrome-inset-left" box.insetLeft 1
  assertEq "floating-chrome-top-line" box.lines[0]! "[A]┌ Explorer ────┐[/]"

private def rectSafeLineRenderTest : IO Unit := do
  let buf : BufferState := {
    id := 0
    filename := none
    lines := #["abc"]
  }
  let rendered := renderVisibleLineInRect mkModel {} buf 0 0 0 5 none
  assertEq "rect-safe-line-render" rendered "abc  "
  assertEq "rect-safe-line-no-clear" (rendered.contains '\x1b' && rendered.contains 'K') false

private def eofMarkerPlacementTest : IO Unit := do
  let marker := renderEmptyLineMarkerInRect { emptyLineMarker := "・" } 6
  assertEq "eof-marker-placement" marker "・    "

def main : IO Unit := do
  utf8ByteOffsetTest
  builtinLeanHighlightTest
  cursorSelectionPriorityTest
  floatingClusterLayoutTest
  floatingSizePolicyTest
  floatingChromeBoxTest
  rectSafeLineRenderTest
  eofMarkerPlacementTest
  IO.println "bliku-test: ok"