// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========

import type { RuntimeVFS, VFSEntry } from '../../vfs.ts'

export interface FSLike {
  mkdirTree(path: string): void
  writeFile(path: string, bytes: Uint8Array): void
  /**
   * Note a listed file whose content could not be fetched. A target that
   * does not implement this simply omits the file, which is only safe
   * when nothing will write to it.
   */
  markUnreadable?(path: string): void
  /**
   * Note a namespace symlink and its target. A target that cannot hold
   * links omits them, which is what every seed did before links were
   * reachable at all.
   */
  symlink?(path: string, target: string): void
  /**
   * The mount's permission bits for a path already written. Spelled the
   * way Emscripten's own FS spells it, so a target that is a real FS
   * satisfies it as it stands; a target with no notion of a mode omits
   * it and its files keep the tree's default.
   */
  chmod?(path: string, mode: number): void
  /** The mount's stamps, in milliseconds, as Emscripten's FS takes them. */
  utime?(path: string, atimeMs: number, mtimeMs: number): void
}

/**
 * Carry a row's mode and stamp onto the target it was just written to.
 *
 * The row already holds both (the door stats every entry it does not
 * slash-mark), so this costs no extra call. Without it a seeded tree
 * reports the tree's own defaults: 0o644 whatever the mount says, and
 * an mtime of the moment the node was built, so every file a guest
 * stats looks like it was modified this second.
 *
 * A slash-marked row carries neither and is left alone; so is a link,
 * whose mode is fixed at 0o777 on every POSIX system.
 */
function applyMeta(fs: FSLike, entry: VFSEntry): void {
  if (entry.mode !== undefined) fs.chmod?.(entry.path, entry.mode)
  if (entry.mtimeMs !== undefined) fs.utime?.(entry.path, entry.mtimeMs, entry.mtimeMs)
}

async function preloadEntry(fs: FSLike, vfs: RuntimeVFS, entry: VFSEntry): Promise<void> {
  // A namespace symlink is copied as a link, never followed: stat
  // reports the target, so a directory link would copy its whole
  // subtree here and a cyclic one would never terminate. The target is
  // one readlink, which the walk pays only for the entries the
  // namespace already marked.
  if (entry.isLink === true) {
    if (fs.symlink === undefined) return
    try {
      fs.symlink(entry.path, await vfs.readlink(entry.path))
    } catch (err) {
      // A link the namespace listed and then would not resolve: leaving
      // it out is the honest seed, since inventing a target would make
      // the guest read some other file's bytes.
      console.warn(
        `mirage preload: cannot read link ${entry.path}: ${err instanceof Error ? err.message : String(err)}`,
      )
    }
    return
  }
  if (entry.isDir) {
    fs.mkdirTree(entry.path)
    applyMeta(fs, entry)
    const next = entry.path.endsWith('/') ? entry.path : entry.path + '/'
    if (vfs.mountOf(entry.path) === next) {
      // A nested mount served through its parent keeps the failure
      // boundary it had as a top-level prefix: its root readdir failing
      // must fail the whole collection, so syncMounts keeps the
      // previous healthy snapshot instead of replacing it with one
      // where this subtree reads as empty.
      await preloadInto(fs, vfs, next)
      return
    }
    try {
      await preloadInto(fs, vfs, next)
    } catch (err) {
      console.warn(
        `mirage preload: skipping subtree ${next}: ${err instanceof Error ? err.message : String(err)}`,
      )
    }
    return
  }
  try {
    const bytes = await vfs.read(entry.path)
    fs.writeFile(entry.path, bytes)
    applyMeta(fs, entry)
  } catch (err) {
    // The mount listed it, so it exists; we just cannot serve it. Say so
    // rather than leaving a hole the guest would read as absence and an
    // append would fill by replacing the file.
    fs.markUnreadable?.(entry.path)
    applyMeta(fs, entry)
    console.warn(
      `mirage preload: cannot read ${entry.path}: ${err instanceof Error ? err.message : String(err)}`,
    )
  }
}

/**
 * Copy one mount prefix into a synchronous target.
 *
 * Args:
 *   fs: the tree collector to fill.
 *   vfs: the runtime's mount vocabulary to read through.
 *   prefix: the mount prefix to walk.
 */
export async function preloadInto(fs: FSLike, vfs: RuntimeVFS, prefix: string): Promise<void> {
  const prefixWithSlash = prefix.endsWith('/') ? prefix : prefix + '/'
  const prefixWithoutSlash = prefixWithSlash.slice(0, -1)
  fs.mkdirTree(prefixWithoutSlash)
  const entries = await vfs.readdir(prefixWithSlash)
  await Promise.all(entries.map((entry) => preloadEntry(fs, vfs, entry)))
}
