// Router HTML rendering helpers

///|
/// Format page title with prefix
fn format_title(
  title : String,
  title_prefix~ : String,
  default_title~ : String,
) -> String {
  if title.is_empty() {
    default_title
  } else if title_prefix.is_empty() {
    title
  } else {
    "\{title_prefix} - \{title}"
  }
}

///|
/// Collect preload URLs: libs first, then chunk deps, then island entries (pure, no IO)
fn collect_preload_urls(
  preload_libs : Array[String],
  island_urls : Array[String],
  chunk_manifest : ChunkManifest?,
) -> Array[String] {
  let seen : @hashset.HashSet[String] = @hashset.HashSet([])
  let all_urls : Array[String] = []
  let add = fn(url : String) {
    if !seen.contains(url) {
      seen.add(url)
      all_urls.push(url)
    }
  }
  for u in preload_libs {
    add(u)
  }
  match chunk_manifest {
    Some(manifest) =>
      for url in island_urls {
        for chunk in manifest.get_chunks(url) {
          add(chunk)
        }
      }
    None => ()
  }
  for url in island_urls {
    add(url)
  }
  all_urls
}

///|
fn merge_asset_paths(
  base : Array[String],
  extra : Array[String],
) -> Array[String] {
  let merged = base.copy()
  for path in extra {
    if !merged.contains(path) {
      merged.push(path)
    }
  }
  merged
}

///|
fn is_external_asset_url(path : String) -> Bool {
  path.has_prefix("http://") ||
  path.has_prefix("https://") ||
  path.has_prefix("//") ||
  path.has_prefix("/")
}

///|
fn strip_dot_slash(path : String) -> String {
  if path.has_prefix("./") {
    path[2:].to_owned()
  } else {
    path
  }
}

///|
fn normalize_asset_url(path : String) -> String {
  if path.is_empty() {
    return ""
  }
  if is_external_asset_url(path) {
    return path
  }
  let local_path = strip_dot_slash(path)
  if local_path.has_prefix("static/") {
    "/" + local_path
  } else {
    "/static/" + local_path
  }
}

///|
fn push_normalized_asset(paths : Array[String], path : String) -> Unit {
  let url = normalize_asset_url(path)
  if !url.is_empty() && !paths.contains(url) {
    paths.push(url)
  }
}

///|
fn normalize_asset_urls(paths : Array[String]) -> Array[String] {
  let normalized : Array[String] = []
  for path in paths {
    push_normalized_asset(normalized, path)
  }
  normalized
}

///|
fn render_asset_tags(styles : Array[String], scripts : Array[String]) -> String {
  let buf = StringBuilder::new()
  for href in normalize_asset_urls(styles) {
    buf.write_string("")
  }
  for src in normalize_asset_urls(scripts) {
    buf.write_string("")
  }
  buf.to_string()
}

///|
fn build_page_head(
  base_head : String,
  styles : Array[String],
  scripts : Array[String],
  assets : @server_dom.ServerAssets,
) -> String {
  base_head +
  render_asset_tags(
    merge_asset_paths(styles, assets.styles),
    merge_asset_paths(scripts, assets.scripts),
  )
}

///|
/// Render VNode to full HTML page with template-based rendering
fn[E] render_page_html(
  node : @luna.Node[E, String],
  title : String,
  head : String,
  hydration : Bool,
  template : String,
  enable_preload? : Bool = true,
  preload_libs? : Array[String] = [],
  chunk_manifest? : ChunkManifest? = None,
) -> String {
  // Collect preload URLs from initial render (shared for both paths)
  let result = @render.render_to_string(node, preload=enable_preload)
  let preload_tags = if enable_preload {
    let all_urls = collect_preload_urls(
      preload_libs,
      result.preload_urls,
      chunk_manifest,
    )
    @render.generate_preload_tags(all_urls)
  } else {
    ""
  }
  // Hydration path adds markers; non-hydration uses the already-rendered HTML
  let body_html = if hydration {
    @render.render_to_string_with_hydration(node)
  } else {
    result.html
  }
  let full_head = @hot_reload.with_dev_head_script(head)
  @page_shell.render_template(
    template, title, full_head, preload_tags, body_html,
  )
}