# SPDX-License-Identifier: LUL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus SDK.
# See legal/LICENSE_UNBOUND.md for license terms.

# MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI)
# Scribe v3: The Sovereign TUI Editor
# Phase 24: Full TUI with Navigation & Multi-Sector IO

import strutils, sequtils
import ../../libs/membrane/libc as lb

# --- CONSTANTS ---
const
  KEY_CTRL_Q = char(17)
  KEY_CTRL_S = char(19)
  KEY_CTRL_X = char(24) # Alternative exit
  KEY_ESC = char(27)
  KEY_BACKSPACE = char(127)
  KEY_ENTER = char(13)  # CR
  KEY_LF = char(10)

# --- STATE ---
var lines: seq[string]
var cursor_x: int = 0
var cursor_y: int = 0 # Line index in buffer
var scroll_y: int = 0 # Index of top visible line
var screen_rows: int = 20 # Fixed for now, or detect?
var screen_cols: int = 80
var filename: string = ""
var status_msg: string = "CTRL-S: Save | CTRL-Q: Quit"
var is_running: bool = true

# --- TERMINAL HELPERS ---

proc write_raw(s: string) =
  if s.len > 0:
    discard lb.write(cint(1), cast[pointer](unsafeAddr s[0]), csize_t(s.len))

proc term_clear() =
  write_raw("\x1b[2J") # Clear entire screen

proc term_move(row, col: int) =
  # ANSI is 1-indexed
  write_raw("\x1b[" & $(row + 1) & ";" & $(col + 1) & "H")

proc term_hide_cursor() = write_raw("\x1b[?25l")
proc term_show_cursor() = write_raw("\x1b[?25h")

# --- FILE IO ---

proc load_file(fname: string) =
  lines = @[]
  let fd = lb.open(fname.cstring, 0)
  if fd >= 0:
    var content = ""
    var buf: array[512, char]
    while true:
      let n = lb.read(fd, addr buf[0], 512)
      if n <= 0: break
      for i in 0..<n: content.add(buf[i])
    discard lb.close(fd)

    if content.len > 0:
      lines = content.splitLines()
    else:
      lines.add("")
  else:
    # New File
    lines.add("")

  if lines.len == 0: lines.add("")

proc save_file() =
  var content = lines.join("\n")
  # Ensure trailing newline often expected
  if content.len > 0 and content[^1] != '\n': content.add('\n')

  # FLAGS: O_WRONLY(1) | O_CREAT(64) | O_TRUNC(512) = 577
  let fd = lb.open(filename.cstring, 577)
  if fd < 0:
    status_msg = "Error: Save Failed (VFS Open)."
    return

  let n = lb.write(fd, cast[pointer](unsafeAddr content[0]), csize_t(content.len))
  discard lb.close(fd)
  status_msg = "Saved " & $n & " bytes."

# --- LOGIC ---

proc scroll_to_cursor() =
  if cursor_y < scroll_y:
    scroll_y = cursor_y
  if cursor_y >= scroll_y + screen_rows:
    scroll_y = cursor_y - screen_rows + 1

proc render() =
  term_hide_cursor()
  term_move(0, 0)

  # Draw Content
  for i in 0..<screen_rows:
    let line_idx = scroll_y + i
    term_move(i, 0)
    write_raw("\x1b[K") # Clear Line

    if line_idx < lines.len:
      var line = lines[line_idx]
      # Truncate for display if needed? For now wrap or let terminal handle
      if line.len > screen_cols: line = line[0..<screen_cols]
      write_raw(line)
    else:
      write_raw("~") # Vim style empty lines

  # Draw Status Bar
  term_move(screen_rows, 0)
  write_raw("\x1b[7m") # Invert
  var bar = " " & filename & " - " & $cursor_x & ":" & $cursor_y & " | " & status_msg
  while bar.len < screen_cols: bar.add(" ")
  if bar.len > screen_cols: bar = bar[0..<screen_cols]
  write_raw(bar)
  write_raw("\x1b[0m") # Reset

  # Position Cursor
  term_move(cursor_y - scroll_y, cursor_x)
  term_show_cursor()

proc insert_char(c: char) =
  if cursor_y >= lines.len: lines.add("")
  var line = lines[cursor_y]
  if cursor_x > line.len: cursor_x = line.len

  if cursor_x == line.len:
    line.add(c)
  else:
    line.insert($c, cursor_x)

  lines[cursor_y] = line
  cursor_x += 1

proc insert_newline() =
  if cursor_y >= lines.len: lines.add("") # Should catch

  let current_line = lines[cursor_y]
  if cursor_x >= current_line.len:
    # Append new empty line
    lines.insert("", cursor_y + 1)
  else:
    # Split line
    let left = current_line[0..<cursor_x]
    let right = current_line[cursor_x..^1]
    lines[cursor_y] = left
    lines.insert(right, cursor_y + 1)

  cursor_y += 1
  cursor_x = 0

proc backspace() =
  if cursor_y >= lines.len: return

  if cursor_x > 0:
    var line = lines[cursor_y]
    # Delete char at x-1
    if cursor_x - 1 < line.len:
      line.delete(cursor_x - 1, cursor_x - 1)
      lines[cursor_y] = line
      cursor_x -= 1
  elif cursor_y > 0:
    # Merge with previous line
    let current = lines[cursor_y]
    let prev_len = lines[cursor_y - 1].len
    lines[cursor_y - 1].add(current)
    lines.delete(cursor_y)
    cursor_y -= 1
    cursor_x = prev_len

proc handle_arrow(code: char) =
  case code:
  of 'A': # UP
    if cursor_y > 0: cursor_y -= 1
  of 'B': # DOWN
    if cursor_y < lines.len - 1: cursor_y += 1
  of 'C': # RIGHT
    if cursor_y < lines.len:
      if cursor_x < lines[cursor_y].len: cursor_x += 1
      elif cursor_y < lines.len - 1: # Wrap to next line
        cursor_y += 1
        cursor_x = 0
  of 'D': # LEFT
    if cursor_x > 0: cursor_x -= 1
    elif cursor_y > 0: # Wrap to prev line end
      cursor_y -= 1
      cursor_x = lines[cursor_y].len
  else: discard

  # Snap cursor to line length
  if cursor_y < lines.len:
    if cursor_x > lines[cursor_y].len: cursor_x = lines[cursor_y].len

# --- MAIN LOOP ---

proc read_input() =
  # We need a custom input loop that handles escapes
  # This uses libc.read on fd 0 (stdin)

  var c: char
  let n = lb.read(0, addr c, 1)
  if n <= 0: return

  if c == KEY_CTRL_Q or c == KEY_CTRL_X:
    is_running = false
    return

  if c == KEY_CTRL_S:
    save_file()
    return

  if c == KEY_ESC:
    # Potential Sequence
    # Busy wait briefly for next char to confirm sequence vs lone ESC
    # In a real OS we'd have poll/timeout. Here we hack.
    # Actually, let's just try to read immediately.
    var c2: char
    let n2 = lb.read(0, addr c2, 1) # This might block if not buffered?
                                    # Our lb.read is non-blocking if ring is empty, returns 0.
                                    # But for a sequence, the chars should be in the packet together or close.
                                    # If 0, it was just ESC.

    if n2 > 0 and c2 == '[':
      var c3: char
      let n3 = lb.read(0, addr c3, 1)
      if n3 > 0:
        handle_arrow(c3)
    return

  if c == KEY_BACKSPACE or c == '\b':
    backspace()
    return

  if c == KEY_ENTER or c == KEY_LF:
    insert_newline()
    return

  # Normal char
  if c >= ' ' and c <= '~':
    insert_char(c)

proc start_editor*(fname: string) =
  filename = fname
  is_running = true
  cursor_x = 0
  cursor_y = 0
  scroll_y = 0
  status_msg = "CTRL-S: Save | CTRL-Q: Quit"

  write_raw("[Scribe] Loading " & fname & "...\n")
  load_file(fname)

  term_clear()

  while is_running:
    # lb.pump_membrane_stack() - Handled by Kernel
    scroll_to_cursor()
    render()

    # Input Loop (Non-blocking check mostly)
    # We loop quickly to feel responsive
    read_input()

    # Yield slightly
    for i in 0..5000: discard

  term_clear()
  term_move(0, 0)
  write_raw("Scribe Closed.\n")
