text editor inspired vim and yi
import ViE.State
import ViE.Types
import ViE.Window.Analysis

namespace ViE.Window

open ViE

def switchWindow (state : EditorState) (dir : Direction) : EditorState :=
  let ws := state.getCurrentWorkspace
  let bounds := ViE.Window.getAllWindowBounds ws.layout (if state.windowHeight > 0 then state.windowHeight - 1 else 0) state.windowWidth
  let current := bounds.find? (fun (id, _, _, _, _) => id == ws.activeWindowId)

  match current with
  | none => state
  | some (_, r, c, h, w) =>
    -- Find candidates with orthogonal overlap
    let candidates := bounds.filter fun (id, r', c', h', w') =>
      id != ws.activeWindowId &&
      match dir with
      | .left => c' < c && (r' < r + h && r' + h' > r)
      | .right => c' >= c + w && (r' < r + h && r' + h' > r)
      | .up => r' < r && (c' < c + w && c' + w' > c)
      | .down => r' >= r + h && (c' < c + w && c' + w' > c)

    -- Filter again for adjacency/overlap
    let best := candidates.foldl (fun acc p =>
      let (_, r', c', _, _) := p
      match acc with
      | none => some p
      | some (_, br, bc, _, _) =>
         match dir with
         | .left => if c' > bc then some p else some acc.get!
         | .right => if c' < bc then some p else some acc.get!
         | .up => if r' > br then some p else some acc.get!
         | .down => if r' < br then some p else some acc.get!
    ) none

    match best with
    | some (id, _, _, _, _) => state.updateCurrentWorkspace fun ws => { ws with activeWindowId := id }
    | none => state

def closeActiveWindow (state : EditorState) : EditorState :=
  let doClose (st : EditorState) : EditorState :=
    let ws := st.getCurrentWorkspace
    match ws.layout.remove ws.activeWindowId with
    | none => { st with shouldQuit := true }
    | some newLayout =>
      st.updateCurrentWorkspace fun ws =>
        let newIds := ViE.Window.getWindowIds newLayout
        let newActiveId := match newIds with
          | [] => 0 -- Should not happen if layout valid
          | h :: _ => h
        { ws with layout := newLayout, activeWindowId := newActiveId } |>.pruneFloatingWindows

  let activeBuf := state.getActiveBuffer
  let explorerOpt := state.explorers.find? (fun (id, _) => id == activeBuf.id)
  match explorerOpt with
  | none => doClose state
  | some (bufId, explorer) =>
      let s1 := { state with explorers := state.explorers.filter (fun (id, _) => id != bufId) }
      let s2 :=
        match explorer.previewWindowId with
        | none => s1
        | some previewId =>
          s1.updateCurrentWorkspace fun ws =>
            match ws.layout.remove previewId with
            | some newLayout => { ws with layout := newLayout }
            | none => ws
      let s3 :=
        match explorer.previewBufferId with
        | some previewBufId => ViE.Buffer.removeBuffer s2 previewBufId
        | none => s2
      doClose s3

def splitWindow (state : EditorState) (direction : Bool) : EditorState :=
  -- direction: true = vertical (top/bottom), false = horizontal (left/right)
  let s' := state.updateCurrentWorkspace fun ws =>
    let activeId := ws.activeWindowId
    let nextId := ws.nextWindowId
    let activeWasFloating := ws.isFloatingWindow activeId
    let (activeRowOff, activeColOff) := ws.getFloatingWindowOffset activeId
    let currentView := ws.layout.findView activeId |>.getD initialView
    let newView := currentView

    let rec updateLayout (l : Layout) : Layout :=
      match l with
      | Layout.window wid v =>
        if wid == activeId then
          if direction then Layout.vsplit (Layout.window wid v) (Layout.window nextId newView) 0.5
          else Layout.hsplit (Layout.window wid v) (Layout.window nextId newView) 0.5
        else Layout.window wid v
      | Layout.hsplit left right r => Layout.hsplit (updateLayout left) (updateLayout right) r
      | Layout.vsplit top bottom r => Layout.vsplit (updateLayout top) (updateLayout bottom) r

    let ws' := { ws with layout := updateLayout ws.layout, nextWindowId := nextId + 1 }
    let ws' :=
      if activeWasFloating then
        (ws'.setWindowFloating nextId true).setFloatingWindowOffset nextId activeRowOff activeColOff
      else
        ws'
    ws'.pruneFloatingWindows

  { s' with message := "Split window" }

def nudgeActiveFloatingWindow (state : EditorState) (dir : Direction) (step : Int := 1) : EditorState :=
  let ws := state.getCurrentWorkspace
  let activeId := ws.activeWindowId
  if !ws.isFloatingWindow activeId then
    { state with message := "active window is not floating" }
  else
    match ws.findFloatingSubtree activeId with
    | none => { state with message := "floating window subtree not found" }
    | some subtree =>
      let ids := ViE.Window.getWindowIds subtree
      let dRow : Int :=
        match dir with
        | .up => -step
        | .down => step
        | _ => 0
      let dCol : Int :=
        match dir with
        | .left => -step
        | .right => step
        | _ => 0
      let s' := state.updateCurrentWorkspace fun ws =>
        ids.foldl (fun acc wid => acc.adjustFloatingWindowOffset wid dRow dCol) ws
      { s' with dirty := true, message := "floating window moved" }

/-- Result of resize operation: None (not found), Some (layout, handled) -/
abbrev ResizeResult := Option (Layout × Bool)

def resizeWindow (state : EditorState) (dir : Direction) (amount : Float) : EditorState :=
  state.updateCurrentWorkspace fun ws =>
    let activeId := ws.activeWindowId

    let rec traverse (l : Layout) : ResizeResult :=
      match l with
      | Layout.window wid v =>
        if wid == activeId then some (Layout.window wid v, false) else none
      | Layout.hsplit left right r =>
        match traverse left with
        | some (newLeft, handled) =>
          if handled then some (Layout.hsplit newLeft right r, true)
          else
            -- We are in left child, unhandled.
            match dir with
            | .left | .right => -- Horizontal resize
              let delta := if dir == .right then amount else -amount
              let newR := max 0.05 (min 0.95 (r + delta))
              some (Layout.hsplit newLeft right newR, true)
            | _ => some (Layout.hsplit newLeft right r, false)
        | none =>
          match traverse right with
          | some (newRight, handled) =>
            if handled then some (Layout.hsplit left newRight r, true)
            else
              -- We are in right child, unhandled.
              match dir with
              | .left | .right => -- Horizontal resize
                -- Consistent direction: Right (l) -> Increase ratio (move split right). Left (h) -> Decrease ratio (move split left).
                let delta := if dir == .right then amount else -amount
                let newR := max 0.05 (min 0.95 (r + delta))
                some (Layout.hsplit left newRight newR, true)
              | _ => some (Layout.hsplit left newRight r, false)
          | none => none
      | Layout.vsplit top bottom r =>
        match traverse top with
        | some (newTop, handled) =>
          if handled then some (Layout.vsplit newTop bottom r, true)
          else
            -- Top child
            match dir with
            | .up | .down => -- Vertical resize
               -- Top child: Down (j) -> Increase ratio (move split down). Up (k) -> Decrease ratio (move split up).
               let delta := if dir == .down then amount else -amount
               let newR := max 0.05 (min 0.95 (r + delta))
               some (Layout.vsplit newTop bottom newR, true)
            | _ => some (Layout.vsplit newTop bottom r, false)
        | none =>
          match traverse bottom with
          | some (newBottom, handled) =>
            if handled then some (Layout.vsplit top newBottom r, true)
            else
               -- Bottom child
               match dir with
               | .up | .down => -- Vertical resize
                 -- Bottom child: Down (j) -> Increase ratio (move split down). Up (k) -> Decrease ratio (move split up).
                 let delta := if dir == .down then amount else -amount
                 let newR := max 0.05 (min 0.95 (r + delta))
                 some (Layout.vsplit top newBottom newR, true)
               | _ => some (Layout.vsplit top newBottom r, false)
          | none => none

    match traverse ws.layout with
    | some (newLayout, _) => { ws with layout := newLayout }
    | none => ws

def cycleWindow (state : EditorState) : EditorState :=
  state.updateCurrentWorkspace fun ws =>
    let ids := ViE.Window.getWindowIds ws.layout
    if ids.isEmpty then ws
    else
      let current := ws.activeWindowId
      -- Manual index search to avoid import issues if any
      let rec findNext (list : List Nat) (sawCurrent : Bool) : Option Nat :=
        match list with
        | [] => if sawCurrent then ids.head? else none
        | id :: rest =>
          if sawCurrent then some id
          else if id == current then findNext rest true
          else findNext rest false

      match findNext ids false with
      | some nextId => { ws with activeWindowId := nextId }
      | none => { ws with activeWindowId := match ids with | [] => ws.activeWindowId | h :: _ => h }

def enforceScroll (state : EditorState) : EditorState :=
  let (h, w) := state.getActiveWindowScrollBounds
  let linesInView := if h > 1 then h - 1 else 1

  -- Vertical Scroll
  let (sRow, sCol) := state.getScroll
  let cursor := state.getCursor

  let state :=
    if cursor.row.val < sRow.val then
      state.setScroll cursor.row sCol
    else if cursor.row.val >= sRow.val + linesInView then
      let newScrollRow : Row := ⟨cursor.row.val - linesInView + 1⟩
      state.setScroll newScrollRow sCol
    else
      state

  -- Refresh scroll after potential update
  let (sRow, sCol) := state.getScroll

  -- Horizontal Scroll
  let lnWidth := if state.config.showLineNumbers then 4 else 0
  let colsInView := if w > lnWidth then w - lnWidth else 1

  let state :=
    if cursor.col.val < sCol.val then
      state.setScroll sRow cursor.col
    else if cursor.col.val >= sCol.val + colsInView then
      let newScrollCol : Col := ⟨cursor.col.val - colsInView + 1⟩
      state.setScroll sRow newScrollCol
    else
      state

  state

end ViE.Window