# SPDX-License-Identifier: LSL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation

## Nexus Membrane: LittleFS Bindings (L0 Physics)
## Provides Nim bindings to the LittleFS C library.

import ../blk

type
  LfsConfig* {.importc: "struct lfs_config", header: "lfs.h", nodecl, final.} = object
    context*: pointer
    read*: proc(cfg: ptr LfsConfig, blk: uint32, off: uint32, buf: pointer, size: uint32): cint {.cdecl.}
    prog*: proc(cfg: ptr LfsConfig, blk: uint32, off: uint32, buf: pointer, size: uint32): cint {.cdecl.}
    erase*: proc(cfg: ptr LfsConfig, blk: uint32): cint {.cdecl.}
    sync*: proc(cfg: ptr LfsConfig): cint {.cdecl.}
    read_size*: uint32
    prog_size*: uint32
    block_size*: uint32
    block_count*: uint32
    block_cycles*: int32
    cache_size*: uint32
    lookahead_size*: uint32
    read_buffer*: pointer
    prog_buffer*: pointer
    lookahead_buffer*: pointer
    name_max*: uint32
    file_max*: uint32
    attr_max*: uint32
    metadata_max*: uint32

  Lfs* {.importc: "lfs_t", header: "lfs.h".} = object
  LfsFile* {.importc: "lfs_file_t", header: "lfs.h".} = object
  LfsInfo* {.importc: "struct lfs_info", header: "lfs.h".} = object
    lfsType*: uint8
    size*: uint32
    name*: array[256, char]

const
  LFS_TYPE_REG* = 1'u8
  LFS_TYPE_DIR* = 2'u8
  LFS_O_RDONLY* = 1
  LFS_O_WRONLY* = 2
  LFS_O_RDWR* = 3
  LFS_O_CREAT* = 0x0100
  LFS_O_EXCL* = 0x0200
  LFS_O_TRUNC* = 0x0400
  LFS_O_APPEND* = 0x0800

# LittleFS C API
proc lfs_mount(lfs: ptr Lfs, cfg: ptr LfsConfig): cint {.importc, header: "lfs.h", cdecl.}
proc lfs_format(lfs: ptr Lfs, cfg: ptr LfsConfig): cint {.importc, header: "lfs.h", cdecl.}
proc lfs_file_open(lfs: ptr Lfs, file: ptr LfsFile, path: cstring, flags: cint): cint {.importc, header: "lfs.h", cdecl.}
proc lfs_file_close(lfs: ptr Lfs, file: ptr LfsFile): cint {.importc, header: "lfs.h", cdecl.}
proc lfs_file_read(lfs: ptr Lfs, file: ptr LfsFile, buf: pointer, size: uint32): cint {.importc, header: "lfs.h", cdecl.}
proc lfs_file_write(lfs: ptr Lfs, file: ptr LfsFile, buf: pointer, size: uint32): cint {.importc, header: "lfs.h", cdecl.}
proc lfs_file_seek(lfs: ptr Lfs, file: ptr LfsFile, off: int32, whence: cint): int32 {.importc, header: "lfs.h", cdecl.}
proc lfs_file_rewind(lfs: ptr Lfs, file: ptr LfsFile): int32 {.importc, header: "lfs.h", cdecl.}
proc lfs_stat(lfs: ptr Lfs, path: cstring, info: ptr LfsInfo): cint {.importc, header: "lfs.h", cdecl.}
proc lfs_remove(lfs: ptr Lfs, path: cstring): cint {.importc, header: "lfs.h", cdecl.}
proc lfs_mkdir(lfs: ptr Lfs, path: cstring): cint {.importc, header: "lfs.h", cdecl.}

# =========================================================
# LFS Wrapper Procs (Callbacks)
# =========================================================

var lfs_start_sector: uint64 = 8192

proc lfs_block_read(cfg: ptr LfsConfig, blk: uint32, off: uint32, buf: pointer, size: uint32): cint {.cdecl.} =
  # Calculate absolute sector
  let sector = lfs_start_sector + uint64(blk) * (cfg.block_size div 512) + uint64(off div 512)
  var temp: array[512, byte]
  if size == 512 and off mod 512 == 0:
    if blk_read(sector, addr temp[0]) < 0: return -1
    copyMem(buf, addr temp[0], 512)
    return 0
  
  var remaining = int(size)
  var dst = cast[int](buf)
  var cur_off = off
  while remaining > 0:
    let sec = lfs_start_sector + uint64(blk) * (cfg.block_size div 512) + uint64(cur_off div 512)
    if blk_read(sec, addr temp[0]) < 0: return -1
    let in_sec_off = int(cur_off mod 512)
    let copy_len = min(remaining, 512 - in_sec_off)
    copyMem(cast[pointer](dst), addr temp[in_sec_off], copy_len)
    dst += copy_len
    cur_off += uint32(copy_len)
    remaining -= copy_len
  return 0

proc lfs_block_prog(cfg: ptr LfsConfig, blk: uint32, off: uint32, buf: pointer, size: uint32): cint {.cdecl.} =
  let sector = lfs_start_sector + uint64(blk) * (cfg.block_size div 512) + uint64(off div 512)
  var temp: array[512, byte]
  if size == 512 and off mod 512 == 0:
    copyMem(addr temp[0], buf, 512)
    if blk_write(sector, addr temp[0]) < 0: return -1
    return 0
  
  var remaining = int(size)
  var src = cast[int](buf)
  var cur_off = off
  while remaining > 0:
    let sec = lfs_start_sector + uint64(blk) * (cfg.block_size div 512) + uint64(cur_off div 512)
    if blk_read(sec, addr temp[0]) < 0: return -1
    let in_sec_off = int(cur_off mod 512)
    let copy_len = min(remaining, 512 - in_sec_off)
    copyMem(addr temp[in_sec_off], cast[pointer](src), copy_len)
    if blk_write(sec, addr temp[0]) < 0: return -1
    src += copy_len
    cur_off += uint32(copy_len)
    remaining -= copy_len
  return 0

proc lfs_block_erase(cfg: ptr LfsConfig, blk: uint32): cint {.cdecl.} =
  var zeros: array[512, byte]
  let sectors_per_block = cfg.block_size div 512
  for i in 0'u32 ..< sectors_per_block:
    let sec = lfs_start_sector + uint64(blk) * uint64(sectors_per_block) + uint64(i)
    if blk_write(sec, addr zeros[0]) < 0: return -1
  return 0

proc lfs_block_sync(cfg: ptr LfsConfig): cint {.cdecl.} =
  discard blk_sync()
  return 0

# =========================================================
# High-Level API
# =========================================================

var g_lfs: Lfs
var g_cfg: LfsConfig
var g_mounted: bool = false

# Static buffers
var lfs_read_buf: array[512, byte]
var lfs_prog_buf: array[512, byte]
var lfs_lookahead_buf: array[16, byte]

proc lfs_nim_init*(start_sector: uint64 = 8192, block_count: uint32 = 1024) =
  lfs_start_sector = start_sector
  
  g_cfg.context = nil
  g_cfg.read = lfs_block_read
  g_cfg.prog = lfs_block_prog
  g_cfg.erase = lfs_block_erase
  g_cfg.sync = lfs_block_sync
  g_cfg.read_size = 512
  g_cfg.prog_size = 512
  g_cfg.block_size = 4096
  g_cfg.block_count = block_count
  g_cfg.block_cycles = 500
  g_cfg.cache_size = 512
  g_cfg.lookahead_size = 16
  
  g_cfg.read_buffer = addr lfs_read_buf
  g_cfg.prog_buffer = addr lfs_prog_buf
  g_cfg.lookahead_buffer = addr lfs_lookahead_buf

proc lfs_nim_format*(): bool =
  lfs_nim_init()
  return lfs_format(addr g_lfs, addr g_cfg) == 0

proc lfs_nim_mount*(): bool =
  if g_mounted: return true
  lfs_nim_init()
  if lfs_mount(addr g_lfs, addr g_cfg) == 0:
    g_mounted = true
    return true
  return false
