(** Walk a positioned node tree and issue Cairo draw commands.

    Handles backgrounds with rounded corners, borders, shadows,
    and text rendering with proper font face/size/weight. *)

open Sx_native_types
open Sx_native_style

(* -- Rounded rectangle path -- *)

let rounded_rect (cr : Cairo.context) (x : float) (y : float) (w : float) (h : float) (r : float) =
  let r = Float.min r (Float.min (w /. 2.) (h /. 2.)) in
  if r <= 0. then
    Cairo.rectangle cr x y ~w ~h
  else begin
    let pi = Float.pi in
    Cairo.Path.sub cr;
    Cairo.arc cr (x +. w -. r) (y +. r) ~r ~a1:(-.pi /. 2.) ~a2:0.;
    Cairo.arc cr (x +. w -. r) (y +. h -. r) ~r ~a1:0. ~a2:(pi /. 2.);
    Cairo.arc cr (x +. r) (y +. h -. r) ~r ~a1:(pi /. 2.) ~a2:pi;
    Cairo.arc cr (x +. r) (y +. r) ~r ~a1:pi ~a2:(-.pi /. 2.);
    Cairo.Path.close cr
  end

(* -- Shadow painting -- *)

let paint_shadow (cr : Cairo.context) (b : box) (radius : float) (level : [`Sm | `Md]) =
  let (offset, blur_passes, alpha) = match level with
    | `Sm -> (1., 2, 0.04)
    | `Md -> (2., 3, 0.05)
  in
  for i = 1 to blur_passes do
    let spread = float_of_int i *. 2. in
    Cairo.save cr;
    Cairo.set_source_rgba cr 0. 0. 0. (alpha /. float_of_int i);
    rounded_rect cr
      (b.x -. spread)
      (b.y +. offset -. spread +. float_of_int i)
      (b.w +. spread *. 2.)
      (b.h +. spread *. 2.)
      (radius +. spread);
    Cairo.fill cr;
    Cairo.restore cr
  done

(* -- Main paint function -- *)

(** Paint a positioned node tree to a Cairo context. *)
let rec paint (cr : Cairo.context) (node : node) : unit =
  let s = node.style in
  let b = node.box in

  if s.display = `None then ()
  else begin
    (* Save state for potential clip *)
    Cairo.save cr;

    (* Shadow *)
    (match s.shadow with
     | `None -> ()
     | `Sm -> paint_shadow cr b s.border_radius `Sm
     | `Md -> paint_shadow cr b s.border_radius `Md);

    (* Background *)
    (match s.bg_color with
     | Some c ->
       Cairo.set_source_rgba cr c.r c.g c.b c.a;
       rounded_rect cr b.x b.y b.w b.h s.border_radius;
       Cairo.fill cr
     | None -> ());

    (* Border *)
    if s.border_width > 0. then begin
      let bc = match s.border_color with Some c -> c | None -> stone_800 in
      Cairo.set_source_rgba cr bc.r bc.g bc.b bc.a;
      Cairo.set_line_width cr s.border_width;
      rounded_rect cr
        (b.x +. s.border_width /. 2.)
        (b.y +. s.border_width /. 2.)
        (b.w -. s.border_width)
        (b.h -. s.border_width)
        (Float.max 0. (s.border_radius -. s.border_width /. 2.));
      Cairo.stroke cr
    end;

    (* Clip for overflow *)
    if s.overflow_hidden then begin
      rounded_rect cr b.x b.y b.w b.h s.border_radius;
      Cairo.clip cr
    end;

    (* Text *)
    (match node.text with
     | Some txt when String.length txt > 0 ->
       let font_name = match s.font_family with `Sans -> "sans-serif" | `Mono -> "monospace" in
       let weight = match s.font_weight with `Normal -> Cairo.Normal | `Bold -> Cairo.Bold in
       let slant = match s.font_style with `Normal -> Cairo.Upright | `Italic -> Cairo.Italic in
       Cairo.select_font_face cr ~slant ~weight font_name;
       Cairo.set_font_size cr s.font_size;
       let fe = Cairo.font_extents cr in
       Cairo.set_source_rgba cr s.text_color.r s.text_color.g s.text_color.b s.text_color.a;
       Cairo.move_to cr (b.x +. s.padding.left) (b.y +. s.padding.top +. fe.ascent);
       Cairo.show_text cr txt
     | _ -> ());

    (* Children *)
    List.iter (paint cr) node.children;

    Cairo.restore cr
  end

(** Paint a horizontal URL bar at the top of the window. *)
let paint_url_bar (cr : Cairo.context) (url : string) (width : float) : float =
  let bar_height = 36. in
  (* Bar background *)
  Cairo.set_source_rgba cr stone_100.r stone_100.g stone_100.b 1.0;
  Cairo.rectangle cr 0. 0. ~w:width ~h:bar_height;
  Cairo.fill cr;

  (* Bottom border *)
  Cairo.set_source_rgba cr stone_200.r stone_200.g stone_200.b 1.0;
  Cairo.set_line_width cr 1.;
  Cairo.move_to cr 0. bar_height;
  Cairo.line_to cr width bar_height;
  Cairo.stroke cr;

  (* URL text *)
  Cairo.select_font_face cr ~slant:Cairo.Upright ~weight:Cairo.Normal "monospace";
  Cairo.set_font_size cr 13.;
  Cairo.set_source_rgba cr stone_600.r stone_600.g stone_600.b 1.0;
  Cairo.move_to cr 12. 23.;
  Cairo.show_text cr url;

  bar_height

(** Paint the entire scene: clear, URL bar, then content. *)
let paint_scene (cr : Cairo.context) (root : node) (url : string) (width : float) (height : float) : unit =
  (* Clear to white *)
  Cairo.set_source_rgba cr 1. 1. 1. 1.;
  Cairo.rectangle cr 0. 0. ~w:width ~h:height;
  Cairo.fill cr;

  (* URL bar *)
  let bar_h = paint_url_bar cr url width in

  (* Content area *)
  Cairo.save cr;
  Cairo.rectangle cr 0. bar_h ~w:width ~h:(height -. bar_h);
  Cairo.clip cr;

  (* Offset layout by bar height *)
  root.box.y <- root.box.y +. bar_h;
  paint cr root;
  root.box.y <- root.box.y -. bar_h;  (* restore for hit testing *)

  Cairo.restore cr
