// Sol Routes - Mars registration and request handling
//
// Registers compiled SolRoutes to the Hono/Mars app and handles requests.

// ============================================================================
// Route Registration to Hono
// ============================================================================

///|
fn ensure_mars_to_handler_compat() -> Unit {
  ffi_ensure_mars_to_handler_reschedule_symbol()
}

///|
/// Register Sol routes to Hono app
pub fn register_sol_routes(
  app : App,
  routes : Array[SolRoutes],
  config? : RouterConfig,
) -> Unit {
  ensure_mars_to_handler_compat()
  let compiled = compile_sol_routes(routes)
  let cfg = config.unwrap_or(RouterConfig::default())
  register_compiled_sol_routes(app, compiled, cfg)
}

///|
fn is_isr_with_cache(config : RouterConfig, route_revalidate : Int?) -> Bool {
  match (route_revalidate, config.isr_cache) {
    (Some(_), Some(_)) => true
    _ => false
  }
}

///|
fn should_use_streaming(
  config : RouterConfig,
  is_fragment : Bool,
  route_revalidate : Int?,
) -> Bool {
  config.enable_streaming &&
  !(is_fragment) &&
  !(is_isr_with_cache(config, route_revalidate))
}

///|
fn cache_strategy_to_header_value(strategy : CacheStrategy) -> String {
  match strategy {
    CacheStrategy::AlwaysFetch => "always-fetch"
    CacheStrategy::StaleWhileRevalidate => "stale-while-revalidate"
  }
}

///|
/// Apply CDN cache headers based on CDNCacheStrategy configuration.
/// Sets Cache-Control and X-Sol-CDN-Cache headers for ISR pages.
fn apply_cdn_cache_headers(
  c : @mars.Context,
  strategy : @isr.CDNCacheStrategy?,
  revalidate : Int,
) -> Unit {
  match strategy {
    Some(s) => {
      c.set_header("Cache-Control", s.cache_control(revalidate))
      c.set_header("X-Sol-CDN-Cache", s.header_value())
    }
    None => ()
  }
}

///|
fn apply_layouts(
  content : @server_dom.ServerNode,
  props : PageProps,
  layouts : Array[
    (PageProps, @server_dom.ServerNode) -> @server_dom.ServerNode raise Error,
  ],
  is_fragment : Bool,
) -> @server_dom.ServerNode raise Error {
  // Apply layouts from innermost to outermost
  // For fragments: skip root layout (index 0), apply nested layouts
  // For full pages: apply all layouts
  let mut node = content
  let end_idx = if is_fragment { 1 } else { 0 }
  let mut i = layouts.length() - 1
  while i >= end_idx {
    let layout_fn = layouts[i]
    node = layout_fn(props, node)
    i -= 1
  }
  node
}

///|
/// Render page and return HTML (helper for ISR)
async fn render_page_to_html(
  c : Ctx,
  props : PageProps,
  handler : async (PageProps) -> @server_dom.ServerNode,
  layouts : Array[
    (PageProps, @server_dom.ServerNode) -> @server_dom.ServerNode raise Error,
  ],
  is_fragment : Bool,
  title : String,
  page_head : String,
  root_template : String,
  cache_strategy : CacheStrategy,
) -> String raise Error {
  // Call handler and apply layouts
  let content : @server_dom.ServerNode = handler(props)
  if c.is_response_sent() {
    fail("ISR render attempted to send a direct response")
  }
  let node = apply_layouts(content, props, layouts, is_fragment)
  if c.is_response_sent() {
    fail("ISR render attempted to mutate the response")
  }
  if is_fragment {
    render_server_fragment(node, title, cache_strategy)
  } else {
    render_server_page_html(node, title, page_head, root_template)
  }
}

///|
async fn send_non_isr_page_response(
  c : Ctx,
  props : PageProps,
  handler : async (PageProps) -> @server_dom.ServerNode,
  layouts : Array[
    (PageProps, @server_dom.ServerNode) -> @server_dom.ServerNode raise Error,
  ],
  is_fragment : Bool,
  page_title : String,
  page_head : String,
  root_template : String,
  cache_strategy : CacheStrategy,
  route_revalidate : Int?,
  config : RouterConfig,
) -> Unit {
  let content : @server_dom.ServerNode = handler(props) catch {
    e => {
      log_server_error("Page handler error", e)
      return c.text("Internal Server Error", status=500)
    }
  }
  if c.is_response_sent() {
    return
  }
  let node = apply_layouts(content, props, layouts, is_fragment) catch {
    e => {
      log_server_error("Layout error", e)
      return c.text("Internal Server Error", status=500)
    }
  }
  if is_fragment {
    let fragment_html = render_server_fragment(node, page_title, cache_strategy)
    send_fragment_response(c, fragment_html)
  } else if should_use_streaming(config, is_fragment, route_revalidate) {
    send_streaming_server_page(
      c,
      node,
      page_title,
      page_head,
      config.loader_url,
      root_template,
    )
  } else {
    let html = render_server_page_html(
      node, page_title, page_head, root_template,
    )
    c.html(html)
  }
}

///|
async fn handle_compiled_api_route(
  c : Ctx,
  route : CompiledSolRoute,
  middleware_chain : @middleware.Middleware,
) -> Unit {
  // Run middleware chain first
  let halted = @middleware.run_or_500(middleware_chain, c)
  if halted {
    return
  }
  let params = extract_route_params(
    c,
    is_fragment=false,
    param_names=route.param_names,
  )

  let props = PageProps::from_ctx_with_params(c, params)
  match route.api_handler {
    Some(ApiHandler(handler)) => {
      let json : Json = handler(props) catch {
        e => {
          log_server_error("API handler error", e)
          @mars_response.send_internal_error(c)
          return
        }
      }
      if c.is_response_sent() {
        return
      }
      @mars_response.send_json(c, json)
    }
    None => @mars_response.send_json_error(c, "No handler", status=404)
  }
}

///|
fn is_api_route_kind(kind : SolRouteKind) -> Bool {
  match kind {
    SolRouteKind::GetApi => true
    SolRouteKind::PostApi => true
    SolRouteKind::PutApi => true
    SolRouteKind::DeleteApi => true
    SolRouteKind::PatchApi => true
    SolRouteKind::Page => false
  }
}

///|
fn register_compiled_api_route(
  app : App,
  kind : SolRouteKind,
  hono_pattern : String,
  route : CompiledSolRoute,
  middleware_chain : @middleware.Middleware,
) -> Unit {
  let handler = async fn(c) {
    handle_compiled_api_route(c, route, middleware_chain)
  }
  match kind {
    SolRouteKind::GetApi => app.get(hono_pattern, handler)
    SolRouteKind::PostApi => app.post(hono_pattern, handler)
    SolRouteKind::PutApi => app.put(hono_pattern, handler)
    SolRouteKind::DeleteApi => app.delete(hono_pattern, handler)
    SolRouteKind::PatchApi => app.patch(hono_pattern, handler)
    SolRouteKind::Page => ()
  }
}

///|
/// Handle an ISR-enabled page request. Called when revalidate and isr_cache
/// are both present and the request is not a fragment.
async fn handle_isr_page_request(
  c : Ctx,
  props : PageProps,
  handler : async (PageProps) -> @server_dom.ServerNode,
  layouts : Array[
    (PageProps, @server_dom.ServerNode) -> @server_dom.ServerNode raise Error,
  ],
  page_title : String,
  page_head : String,
  root_template : String,
  cache_strategy : CacheStrategy,
  route_revalidate : Int?,
  revalidate : Int,
  cache : @isr.MemoryCache,
  config : RouterConfig,
) -> Unit {
  if !(props.params.query.is_empty()) {
    c.set_header("Cache-Control", "private, no-store")
    c.set_header("X-Sol-ISR-Status", "BYPASS")
    return send_non_isr_page_response(
      c,
      props,
      handler,
      layouts,
      false,
      page_title,
      page_head,
      root_template,
      cache_strategy,
      route_revalidate,
      config,
    )
  }
  // Apply CDN cache headers if strategy is configured
  apply_cdn_cache_headers(c, config.cache_strategy, revalidate)
  // ISR enabled - check cache
  let cache_key = @isr.cache_key(props.params.path, props.params.query)
  let entry = @isr.ISRCache::get(cache, cache_key)
  let now = @isr.now_ms()
  let status = @isr.check_status(entry, now)
  // Set X-Sol-ISR-Status header for diagnostics
  let isr_status_str = match status {
    @isr.Fresh => "HIT"
    @isr.Stale => "STALE"
    @isr.Miss => "MISS"
  }
  c.set_header("X-Sol-ISR-Status", isr_status_str)
  // ISR render props: mark as ISR to warn against ctx header access
  let isr_props = { ..props, is_isr_render: true }
  match status {
    @isr.Fresh =>
      // Return cached HTML directly
      match entry {
        Some(e) => c.html(e.html)
        None => c.text("Cache error", status=500)
      }
    @isr.Stale =>
      // Return cached HTML and schedule background revalidation
      match entry {
        Some(e) => {
          // Deduplicate in-flight revalidations for same cache key
          if !(isr_in_flight.val.contains(cache_key)) {
            isr_in_flight.val.add(cache_key)
            @isr.ISRCache::schedule_revalidation(cache, async fn() {
              let html = render_page_to_html(
                c,
                isr_props,
                handler,
                layouts,
                false,
                page_title,
                page_head,
                root_template,
                cache_strategy,
              ) catch {
                _ => {
                  isr_in_flight.val.remove(cache_key)
                  return
                }
              }
              let new_entry = @isr.CacheEntry::new(
                html,
                @isr.now_ms(),
                revalidate,
              )
              @isr.ISRCache::put(cache, cache_key, new_entry)
              isr_in_flight.val.remove(cache_key)
            })
          }
          c.html(e.html)
        }
        None => c.text("Cache error", status=500)
      }
    @isr.Miss => {
      // Render, cache, and return
      let html = render_page_to_html(
        c,
        isr_props,
        handler,
        layouts,
        false,
        page_title,
        page_head,
        root_template,
        cache_strategy,
      ) catch {
        e => {
          log_server_error("ISR revalidation error", e)
          return c.text("Internal Server Error", status=500)
        }
      }
      let new_entry = @isr.CacheEntry::new(html, now, revalidate)
      @isr.ISRCache::put(cache, cache_key, new_entry)
      c.html(html)
    }
  }
}

///|
fn register_compiled_sol_routes(
  app : App,
  routes : Array[CompiledSolRoute],
  config : RouterConfig,
) -> Unit {
  // Register server island defer endpoint if registry is configured
  match config.server_islands {
    Some(registry) =>
      if !(registry.is_empty()) {
        register_defer_endpoint(app, registry)
      }
    None => ()
  }
  for route in routes {
    // Convert catch-all pattern to Hono wildcard pattern
    let hono_pattern = convert_to_hono_pattern(route.pattern, route.catch_all)
    let middleware_chain = @middleware.pipeline(route.middlewares)
    match route.kind {
      SolRouteKind::Page =>
        app.get(hono_pattern, async fn(c) {
          // Run middleware chain first
          let halted = @middleware.run_or_500(middleware_chain, c)
          if halted {
            return
          }
          let is_fragment = has_fragment_header(c)
          let params = extract_route_params(
            c,
            is_fragment~,
            param_names=route.param_names,
          )
          // Add catch-all parameter if present

          let props = PageProps::from_ctx_with_params(c, params)
          let page_title = format_title(
            route.title,
            title_prefix=config.title_prefix,
            default_title=config.default_title,
          )
          let page_head = config.default_head + route.rendered_meta
          c.set_header(
            "X-Sol-Cache-Strategy",
            cache_strategy_to_header_value(route.cache_strategy),
          )
          match route.page_handler {
            Some(PageHandler(handler)) =>
              // Check if ISR is enabled for this route (only for full page requests)
              match (route.revalidate, config.isr_cache, is_fragment) {
                (Some(revalidate), Some(cache), false) =>
                  handle_isr_page_request(
                    c,
                    props,
                    handler,
                    route.layouts,
                    page_title,
                    page_head,
                    config.root_template,
                    route.cache_strategy,
                    route.revalidate,
                    revalidate,
                    cache,
                    config,
                  )
                _ =>
                  send_non_isr_page_response(
                    c,
                    props,
                    handler,
                    route.layouts,
                    is_fragment,
                    page_title,
                    page_head,
                    config.root_template,
                    route.cache_strategy,
                    route.revalidate,
                    config,
                  )
              }
            None => c.text("No handler for route", status=404)
          }
        })
      kind =>
        if is_api_route_kind(kind) {
          register_compiled_api_route(
            app, kind, hono_pattern, route, middleware_chain,
          )
        }
    }
  }
}

///|
/// Render ServerNode to fragment HTML (for CSR navigation)
async fn render_server_fragment(
  node : @server_dom.ServerNode,
  title : String,
  cache_strategy : CacheStrategy,
) -> String {
  // Resolve the ServerNode to get the actual Node
  let resolved_node = node.resolve()
  let body_html = @render.render_to_string(resolved_node).html

  // Wrap in templates like fragment.mbt does
  let buf = StringBuilder::new()
  buf.write_string("")
  buf.write_string("")
  buf.write_string("")
  buf.to_string()
}

///|
/// Render ServerNode to full page HTML
async fn render_server_page_html(
  node : @server_dom.ServerNode,
  title : String,
  head : String,
  template : String,
) -> String {
  // Resolve the ServerNode to get the actual Node
  let resolved_node = node.resolve()
  let body_html = @render.render_to_string(resolved_node).html
  // Add HMR script in dev mode
  let full_head = @hot_reload.with_dev_head_script(head)
  @page_shell.render_template(template, title, full_head, "", body_html)
}