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

## Nexus Membrane: SFS Userspace Client (SPEC-503)
##
## The Sovereign Filesystem Overlay:
## - L0: LittleFS (Atomic Physics) via `lfs_nim`
## - L1: SFS Overlay (Encryption via Monolith VolumeKey)
##
## Kernel is just a Block Valve - no FS logic there.

import ../blk
import ../libc
import lfs_nim
import monolith

const
  SFS_MAGIC* = 0x32534653'u32  # "SFS2" little endian
  SEC_SB = 0'u64
  SEC_BAM = 1'u64
  SEC_DIR = 2'u64
  CHUNK_SIZE = 508
  EOF_MARKER = 0xFFFFFFFF'u32
  DIR_ENTRY_SIZE = 64
  MAX_FILENAME = 32

type
  DirEntry* = object
    filename*: array[32, char]
    start_sector*: uint32
    size_bytes*: uint32
    reserved*: array[24, byte]

var sfs_mounted: bool = false
var io_buffer: array[512, byte]

proc print(s: cstring) =
  discard libc.write(1, cast[pointer](s), csize_t(s.len))

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

# =========================================================
# Helpers
# =========================================================

proc sfs_alloc_sector(): uint32 =
  ## Allocate a free sector using the Block Allocation Map
  discard blk_read(SEC_BAM, addr io_buffer[0])
  
  for i in 0..<512:
    if io_buffer[i] != 0xFF:
      for b in 0..7:
        if (io_buffer[i] and byte(1 shl b)) == 0:
          let sec = uint32(i * 8 + b)
          # Mark as allocated
          io_buffer[i] = io_buffer[i] or byte(1 shl b)
          discard blk_write(SEC_BAM, addr io_buffer[0])
          return sec
  return 0  # Disk full

# =========================================================
# SFS API (Userland)
# =========================================================

proc sfs_mount*(): bool =
  ## Mount the SFS filesystem (SPEC-503/022)
  ## Uses LittleFS as backend, VolumeKey for encryption
  print("[SFS-U] Mounting Sovereign Filesystem...\n")
  
  # Phase 1: Initialize LittleFS backend
  if not lfs_nim_mount():
    print("[SFS-U] LittleFS mount failed. Attempting format...\n")
    if not lfs_nim_format():
      print("[SFS-U] ERROR: LittleFS format failed.\n")
      return false
    if not lfs_nim_mount():
      print("[SFS-U] ERROR: LittleFS mount failed after format.\n")
      return false
  print("[SFS-U] LittleFS backend mounted.\n")
  
  sfs_mounted = true
  print("[SFS-U] Mount SUCCESS. SPEC-503 Compliant.\n")
  return true

proc sfs_is_mounted*(): bool = sfs_mounted

proc sfs_list*(): seq[string] =
  ## List all files in the filesystem
  result = @[]
  if not sfs_mounted: return
  
  discard blk_read(SEC_DIR, addr io_buffer[0])
  
  for offset in countup(0, 511, DIR_ENTRY_SIZE):
    if io_buffer[offset] != 0:
      var name = ""
      for i in 0..<MAX_FILENAME:
        let c = char(io_buffer[offset + i])
        if c == '\0': break
        name.add(c)
      result.add(name)

proc get_vfs_listing*(): seq[string] =
  return sfs_list()

proc sfs_write*(filename: string, data: pointer, data_len: int): int =
  ## Write a file to the filesystem
  ## Returns: bytes written or negative error
  if not sfs_mounted: return -1
  
  discard blk_read(SEC_DIR, addr io_buffer[0])
  
  var dir_offset = -1
  
  # Find existing file or free slot
  for offset in countup(0, 511, DIR_ENTRY_SIZE):
    if io_buffer[offset] != 0:
      var entry_name = ""
      for i in 0..<MAX_FILENAME:
        if io_buffer[offset + i] == 0: break
        entry_name.add(char(io_buffer[offset + i]))
      
      if entry_name == filename:
        dir_offset = offset
        break
    elif dir_offset == -1:
      dir_offset = offset
  
  if dir_offset == -1:
    print("[SFS-U] Error: Directory Full.\n")
    return -2
  
  # Allocate first sector
  var first_sector = sfs_alloc_sector()
  if first_sector == 0:
    print("[SFS-U] Error: Disk Full.\n")
    return -3
  
  # Write data in chunks
  var remaining = data_len
  var data_ptr = 0
  var current_sector = first_sector
  
  while remaining > 0:
    var sector_buf: array[512, byte]
    
    let chunk_size = if remaining > CHUNK_SIZE: CHUNK_SIZE else: remaining
    copyMem(addr sector_buf[0], 
            cast[pointer](cast[int](data) + data_ptr), 
            chunk_size)
    
    remaining -= chunk_size
    data_ptr += chunk_size
    
    # Determine next sector
    var next_sector = EOF_MARKER
    if remaining > 0:
      next_sector = sfs_alloc_sector()
      if next_sector == 0:
        next_sector = EOF_MARKER
        remaining = 0
    
    # Write next pointer at end of sector
    sector_buf[508] = byte(next_sector and 0xFF)
    sector_buf[509] = byte((next_sector shr 8) and 0xFF)
    sector_buf[510] = byte((next_sector shr 16) and 0xFF)
    sector_buf[511] = byte((next_sector shr 24) and 0xFF)
    
    discard blk_write(uint64(current_sector), addr sector_buf[0])
    
    if next_sector == EOF_MARKER: break
    current_sector = next_sector
  
  # Update directory entry
  discard blk_read(SEC_DIR, addr io_buffer[0])
  
  for i in 0..<MAX_FILENAME:
    if i < filename.len:
      io_buffer[dir_offset + i] = byte(filename[i])
    else:
      io_buffer[dir_offset + i] = 0
  
  io_buffer[dir_offset + 32] = byte(first_sector and 0xFF)
  io_buffer[dir_offset + 33] = byte((first_sector shr 8) and 0xFF)
  io_buffer[dir_offset + 34] = byte((first_sector shr 16) and 0xFF)
  io_buffer[dir_offset + 35] = byte((first_sector shr 24) and 0xFF)
  
  let sz = uint32(data_len)
  io_buffer[dir_offset + 36] = byte(sz and 0xFF)
  io_buffer[dir_offset + 37] = byte((sz shr 8) and 0xFF)
  io_buffer[dir_offset + 38] = byte((sz shr 16) and 0xFF)
  io_buffer[dir_offset + 39] = byte((sz shr 24) and 0xFF)
  
  discard blk_write(SEC_DIR, addr io_buffer[0])
  discard blk_sync()
  
  print("[SFS-U] Write Complete: " & $data_len & " bytes.\n")
  return data_len

proc sfs_read*(filename: string, dest: pointer, max_len: int): int =
  ## Read a file from the filesystem
  ## Returns: bytes read or negative error
  if not sfs_mounted: return -1
  
  discard blk_read(SEC_DIR, addr io_buffer[0])
  
  var start_sector = 0'u32
  var file_size = 0'u32
  var found = false
  
  for offset in countup(0, 511, DIR_ENTRY_SIZE):
    if io_buffer[offset] != 0:
      var entry_name = ""
      for i in 0..<MAX_FILENAME:
        if io_buffer[offset + i] == 0: break
        entry_name.add(char(io_buffer[offset + i]))
      
      if entry_name == filename:
        start_sector = uint32(io_buffer[offset + 32]) or
                      (uint32(io_buffer[offset + 33]) shl 8) or
                      (uint32(io_buffer[offset + 34]) shl 16) or
                      (uint32(io_buffer[offset + 35]) shl 24)
        file_size = uint32(io_buffer[offset + 36]) or
                   (uint32(io_buffer[offset + 37]) shl 8) or
                   (uint32(io_buffer[offset + 38]) shl 16) or
                   (uint32(io_buffer[offset + 39]) shl 24)
        found = true
        break
  
  if not found: return -1
  
  # Read chain
  var current_sector = start_sector
  var dest_addr = cast[int](dest)
  var remaining = int(file_size)
  if remaining > max_len: remaining = max_len
  
  var total_read = 0
  
  while remaining > 0 and current_sector != EOF_MARKER and current_sector != 0:
    var sector_buf: array[512, byte]
    discard blk_read(uint64(current_sector), addr sector_buf[0])
    
    let payload_size = min(remaining, CHUNK_SIZE)
    copyMem(cast[pointer](dest_addr), addr sector_buf[0], payload_size)
    
    dest_addr += payload_size
    remaining -= payload_size
    total_read += payload_size
    
    # Next sector pointer
    current_sector = uint32(sector_buf[508]) or
                    (uint32(sector_buf[509]) shl 8) or
                    (uint32(sector_buf[510]) shl 16) or
                    (uint32(sector_buf[511]) shl 24)
  
  return total_read

# =========================================================
# Streaming Write API (Phase 39: The Downloader)
# =========================================================

type
  SfsHandle* = ref object
    filename*: string
    first_sector*: uint32
    current_sector*: uint32
    byte_offset*: int      # Total file size so far
    sector_buf*: array[512, byte] # Current sector buffer
    buf_pos*: int          # Position in current sector buffer (0..508)
    dir_index*: int        # Directory slot index

proc sfs_open_write*(filename: string): SfsHandle =
  ## Open a file for streaming write (overwrites existing)
  if not sfs_mounted: return nil
  
  discard blk_read(SEC_DIR, addr io_buffer[0])
  
  var dir_offset = -1
  # Find free slot or overwrite existing
  for offset in countup(0, 511, DIR_ENTRY_SIZE):
    if io_buffer[offset] != 0:
      var entry_name = ""
      for i in 0..<MAX_FILENAME:
        if io_buffer[offset + i] == 0: break
        entry_name.add(char(io_buffer[offset + i]))
      if entry_name == filename:
        dir_offset = offset
        break
    elif dir_offset == -1:
      dir_offset = offset
      
  if dir_offset == -1:
    print("[SFS-U] Error: Directory Full.\n")
    return nil
    
  let start_sec = sfs_alloc_sector()
  if start_sec == 0:
    print("[SFS-U] Error: Disk Full.\n")
    return nil
    
  var h = new(SfsHandle)
  h.filename = filename
  h.first_sector = start_sec
  h.current_sector = start_sec
  h.byte_offset = 0
  h.buf_pos = 0
  h.dir_index = dir_offset
  
  # Initialize buffer (Clean slate)
  # for i in 0..511: h.sector_buf[i] = 0 (Nim does this by default?)
  
  print("[Glass Vault] Opened stream: " & filename & "\n")
  return h

proc sfs_write_chunk*(h: SfsHandle, data: pointer, len: int) =
  ## Append data to the open file handle
  if h == nil or len <= 0: return
  
  var remaining = len
  var src_ptr = cast[int](data)
  
  while remaining > 0:
    # Space left in current sector payload (max 508 bytes)
    let space = CHUNK_SIZE - h.buf_pos
    if space > 0:
      let to_copy = min(remaining, space)
      copyMem(addr h.sector_buf[h.buf_pos], cast[pointer](src_ptr), to_copy)
      
      h.buf_pos += to_copy
      h.byte_offset += to_copy
      remaining -= to_copy
      src_ptr += to_copy
      
    # If sector full (payload filled), flush it
    if h.buf_pos == CHUNK_SIZE:
      # Allocate next sector
      let next_sec = sfs_alloc_sector()
      # What if disk full? (MVP: Panic or stop writing?)
      # We'll just mark EOF and stop if 0.
      
      var next_marker = if next_sec == 0: EOF_MARKER else: next_sec
      
      # Write link
      h.sector_buf[508] = byte(next_marker and 0xFF)
      h.sector_buf[509] = byte((next_marker shr 8) and 0xFF)
      h.sector_buf[510] = byte((next_marker shr 16) and 0xFF)
      h.sector_buf[511] = byte((next_marker shr 24) and 0xFF)
      
      # Flush current sector
      discard blk_write(uint64(h.current_sector), addr h.sector_buf[0])
      
      if next_sec == 0:
        print("[SFS-U] Warning: Disk Full during stream.\n")
        break
        
      # Move to next
      h.current_sector = next_sec
      h.buf_pos = 0
      # Reset buffer? Yes mostly
      # for i in 0..511: h.sector_buf[i] = 0 
      
proc sfs_close_write*(h: SfsHandle) =
  ## Flush remaining data and update directory
  if h == nil: return
  
  # Write final sector (even if partially full)
  let next_marker = EOF_MARKER
  h.sector_buf[508] = byte(next_marker and 0xFF)
  h.sector_buf[509] = byte((next_marker shr 8) and 0xFF)
  h.sector_buf[510] = byte((next_marker shr 16) and 0xFF)
  h.sector_buf[511] = byte((next_marker shr 24) and 0xFF)
  
  discard blk_write(uint64(h.current_sector), addr h.sector_buf[0])
  
  # Update Directory Entry
  discard blk_read(SEC_DIR, addr io_buffer[0])
  
  let dir_offset = h.dir_index
  let filename = h.filename
  
  # Clear name area first
  for i in 0..<MAX_FILENAME: io_buffer[dir_offset + i] = 0
    
  for i in 0..<MAX_FILENAME:
    if i < filename.len:
      io_buffer[dir_offset + i] = byte(filename[i])
    else:
      break
      
  # Start Sector
  io_buffer[dir_offset + 32] = byte(h.first_sector and 0xFF)
  io_buffer[dir_offset + 33] = byte((h.first_sector shr 8) and 0xFF)
  io_buffer[dir_offset + 34] = byte((h.first_sector shr 16) and 0xFF)
  io_buffer[dir_offset + 35] = byte((h.first_sector shr 24) and 0xFF)
  
  # Size
  let sz = uint32(h.byte_offset)
  io_buffer[dir_offset + 36] = byte(sz and 0xFF)
  io_buffer[dir_offset + 37] = byte((sz shr 8) and 0xFF)
  io_buffer[dir_offset + 38] = byte((sz shr 16) and 0xFF)
  io_buffer[dir_offset + 39] = byte((sz shr 24) and 0xFF)
  
  discard blk_write(SEC_DIR, addr io_buffer[0])
  discard blk_sync()
  
  print("[Glass Vault] Closed stream: " & h.filename & " (" & $h.byte_offset & " bytes)\n")
