(** Parse Tailwind CSS class strings into native style records.

    Supports ~50 common utility classes covering layout, spacing,
    sizing, typography, colors, borders, and effects. *)

open Sx_native_types

(* -- Color palette (Tailwind stone + violet) -- *)

let white       = { r = 1.0;   g = 1.0;   b = 1.0;   a = 1.0 }
let black       = { r = 0.0;   g = 0.0;   b = 0.0;   a = 1.0 }

let stone_50    = { r = 0.980; g = 0.976; b = 0.973; a = 1.0 }
let stone_100   = { r = 0.961; g = 0.953; b = 0.945; a = 1.0 }
let stone_200   = { r = 0.906; g = 0.890; b = 0.875; a = 1.0 }
let stone_300   = { r = 0.839; g = 0.812; b = 0.788; a = 1.0 }
let stone_400   = { r = 0.659; g = 0.616; b = 0.576; a = 1.0 }
let stone_500   = { r = 0.471; g = 0.431; b = 0.396; a = 1.0 }
let stone_600   = { r = 0.341; g = 0.306; b = 0.275; a = 1.0 }
let stone_700   = { r = 0.267; g = 0.231; b = 0.208; a = 1.0 }
(* stone_800 is already in sx_native_types *)
let stone_900   = { r = 0.106; g = 0.098; b = 0.090; a = 1.0 }

let violet_50   = { r = 0.961; g = 0.953; b = 1.0;   a = 1.0 }
let violet_100  = { r = 0.929; g = 0.906; b = 0.996; a = 1.0 }
let violet_200  = { r = 0.867; g = 0.820; b = 0.992; a = 1.0 }
let violet_300  = { r = 0.769; g = 0.686; b = 0.984; a = 1.0 }
let violet_400  = { r = 0.655; g = 0.525; b = 0.969; a = 1.0 }
let violet_500  = { r = 0.545; g = 0.361; b = 0.945; a = 1.0 }
let violet_600  = { r = 0.486; g = 0.227; b = 0.929; a = 1.0 }
let violet_700  = { r = 0.427; g = 0.176; b = 0.831; a = 1.0 }
let violet_800  = { r = 0.357; g = 0.153; b = 0.694; a = 1.0 }
let violet_900  = { r = 0.298; g = 0.133; b = 0.576; a = 1.0 }

let red_500     = { r = 0.937; g = 0.267; b = 0.267; a = 1.0 }
let red_600     = { r = 0.863; g = 0.145; b = 0.145; a = 1.0 }
let blue_500    = { r = 0.231; g = 0.510; b = 0.965; a = 1.0 }
let blue_600    = { r = 0.145; g = 0.388; b = 0.922; a = 1.0 }
let green_500   = { r = 0.133; g = 0.773; b = 0.369; a = 1.0 }
let green_600   = { r = 0.086; g = 0.635; b = 0.290; a = 1.0 }
let amber_500   = { r = 0.961; g = 0.718; b = 0.078; a = 1.0 }

(* -- Spacing scale (Tailwind: 1 unit = 4px) -- *)

let spacing n = float_of_int n *. 4.0

(* -- Font sizes (Tailwind) -- *)

let font_size_of = function
  | "text-xs"   -> 12.
  | "text-sm"   -> 14.
  | "text-base" -> 16.
  | "text-lg"   -> 18.
  | "text-xl"   -> 20.
  | "text-2xl"  -> 24.
  | "text-3xl"  -> 30.
  | "text-4xl"  -> 36.
  | "text-5xl"  -> 48.
  | _ -> 16.

(* -- Parse a single Tailwind class, mutating a style -- *)

let parse_spacing_value s =
  (* Extract numeric value from strings like "p-4", "gap-2" *)
  match int_of_string_opt s with
  | Some n -> spacing n
  | None -> 0.

let bg_color_of cls =
  match cls with
  | "bg-white"      -> Some white
  | "bg-black"      -> Some black
  | "bg-stone-50"   -> Some stone_50
  | "bg-stone-100"  -> Some stone_100
  | "bg-stone-200"  -> Some stone_200
  | "bg-stone-300"  -> Some stone_300
  | "bg-stone-400"  -> Some stone_400
  | "bg-stone-500"  -> Some stone_500
  | "bg-stone-600"  -> Some stone_600
  | "bg-stone-700"  -> Some stone_700
  | "bg-stone-800"  -> Some stone_800
  | "bg-stone-900"  -> Some stone_900
  | "bg-violet-50"  -> Some violet_50
  | "bg-violet-100" -> Some violet_100
  | "bg-violet-200" -> Some violet_200
  | "bg-violet-300" -> Some violet_300
  | "bg-violet-400" -> Some violet_400
  | "bg-violet-500" -> Some violet_500
  | "bg-violet-600" -> Some violet_600
  | "bg-violet-700" -> Some violet_700
  | "bg-violet-800" -> Some violet_800
  | "bg-violet-900" -> Some violet_900
  | "bg-red-500"    -> Some red_500
  | "bg-red-600"    -> Some red_600
  | "bg-blue-500"   -> Some blue_500
  | "bg-blue-600"   -> Some blue_600
  | "bg-green-500"  -> Some green_500
  | "bg-green-600"  -> Some green_600
  | "bg-amber-500"  -> Some amber_500
  | _ -> None

let text_color_of cls =
  match cls with
  | "text-white"      -> Some white
  | "text-black"      -> Some black
  | "text-stone-50"   -> Some stone_50
  | "text-stone-100"  -> Some stone_100
  | "text-stone-200"  -> Some stone_200
  | "text-stone-300"  -> Some stone_300
  | "text-stone-400"  -> Some stone_400
  | "text-stone-500"  -> Some stone_500
  | "text-stone-600"  -> Some stone_600
  | "text-stone-700"  -> Some stone_700
  | "text-stone-800"  -> Some stone_800
  | "text-stone-900"  -> Some stone_900
  | "text-violet-50"  -> Some violet_50
  | "text-violet-100" -> Some violet_100
  | "text-violet-200" -> Some violet_200
  | "text-violet-300" -> Some violet_300
  | "text-violet-400" -> Some violet_400
  | "text-violet-500" -> Some violet_500
  | "text-violet-600" -> Some violet_600
  | "text-violet-700" -> Some violet_700
  | "text-violet-800" -> Some violet_800
  | "text-violet-900" -> Some violet_900
  | "text-red-500"    -> Some red_500
  | "text-red-600"    -> Some red_600
  | "text-blue-500"   -> Some blue_500
  | "text-blue-600"   -> Some blue_600
  | "text-green-500"  -> Some green_500
  | "text-green-600"  -> Some green_600
  | "text-amber-500"  -> Some amber_500
  | _ -> None

let border_color_of cls =
  match cls with
  | "border-stone-100" -> Some stone_100
  | "border-stone-200" -> Some stone_200
  | "border-stone-300" -> Some stone_300
  | "border-violet-200" -> Some violet_200
  | "border-violet-300" -> Some violet_300
  | "border-white"      -> Some white
  | _ -> None

(** Apply a single Tailwind class to a style, returning the updated style. *)
let apply_class (s : style) (cls : string) : style =
  (* Layout *)
  if cls = "flex" then { s with display = `Flex; flex_direction = `Row }
  else if cls = "flex-col" then { s with display = `Flex; flex_direction = `Column }
  else if cls = "flex-row" then { s with display = `Flex; flex_direction = `Row }
  else if cls = "block" then { s with display = `Block }
  else if cls = "hidden" then { s with display = `None }
  else if cls = "items-center" then { s with align_items = `Center }
  else if cls = "items-start" then { s with align_items = `Start }
  else if cls = "items-end" then { s with align_items = `End }
  else if cls = "items-stretch" then { s with align_items = `Stretch }
  else if cls = "justify-center" then { s with justify_content = `Center }
  else if cls = "justify-between" then { s with justify_content = `Between }
  else if cls = "justify-start" then { s with justify_content = `Start }
  else if cls = "justify-end" then { s with justify_content = `End }
  else if cls = "flex-grow" || cls = "grow" then { s with flex_grow = 1. }
  (* Gap *)
  else if String.length cls > 4 && String.sub cls 0 4 = "gap-" then
    let n = String.sub cls 4 (String.length cls - 4) in
    { s with gap = parse_spacing_value n }
  (* Padding *)
  else if String.length cls > 2 && String.sub cls 0 2 = "p-" && cls.[2] <> 'x' && cls.[2] <> 'y' && cls.[2] <> 'l' && cls.[2] <> 'r' && cls.[2] <> 't' && cls.[2] <> 'b' then
    let n = String.sub cls 2 (String.length cls - 2) in
    let v = parse_spacing_value n in
    { s with padding = { top = v; right = v; bottom = v; left = v } }
  else if String.length cls > 3 && String.sub cls 0 3 = "px-" then
    let n = String.sub cls 3 (String.length cls - 3) in
    let v = parse_spacing_value n in
    { s with padding = { s.padding with left = v; right = v } }
  else if String.length cls > 3 && String.sub cls 0 3 = "py-" then
    let n = String.sub cls 3 (String.length cls - 3) in
    let v = parse_spacing_value n in
    { s with padding = { s.padding with top = v; bottom = v } }
  else if String.length cls > 3 && String.sub cls 0 3 = "pt-" then
    let n = String.sub cls 3 (String.length cls - 3) in
    { s with padding = { s.padding with top = parse_spacing_value n } }
  else if String.length cls > 3 && String.sub cls 0 3 = "pb-" then
    let n = String.sub cls 3 (String.length cls - 3) in
    { s with padding = { s.padding with bottom = parse_spacing_value n } }
  else if String.length cls > 3 && String.sub cls 0 3 = "pl-" then
    let n = String.sub cls 3 (String.length cls - 3) in
    { s with padding = { s.padding with left = parse_spacing_value n } }
  else if String.length cls > 3 && String.sub cls 0 3 = "pr-" then
    let n = String.sub cls 3 (String.length cls - 3) in
    { s with padding = { s.padding with right = parse_spacing_value n } }
  (* Margin *)
  else if String.length cls > 2 && String.sub cls 0 2 = "m-" && cls.[2] <> 'x' && cls.[2] <> 'y' && cls.[2] <> 'l' && cls.[2] <> 'r' && cls.[2] <> 't' && cls.[2] <> 'b' then
    let n = String.sub cls 2 (String.length cls - 2) in
    let v = parse_spacing_value n in
    { s with margin = { top = v; right = v; bottom = v; left = v } }
  else if String.length cls > 3 && String.sub cls 0 3 = "mx-" then
    let n = String.sub cls 3 (String.length cls - 3) in
    let v = parse_spacing_value n in
    { s with margin = { s.margin with left = v; right = v } }
  else if String.length cls > 3 && String.sub cls 0 3 = "my-" then
    let n = String.sub cls 3 (String.length cls - 3) in
    let v = parse_spacing_value n in
    { s with margin = { s.margin with top = v; bottom = v } }
  else if String.length cls > 3 && String.sub cls 0 3 = "mt-" then
    let n = String.sub cls 3 (String.length cls - 3) in
    { s with margin = { s.margin with top = parse_spacing_value n } }
  else if String.length cls > 3 && String.sub cls 0 3 = "mb-" then
    let n = String.sub cls 3 (String.length cls - 3) in
    { s with margin = { s.margin with bottom = parse_spacing_value n } }
  (* Sizing *)
  else if cls = "w-full" then { s with width = `Full }
  else if cls = "h-full" then { s with height = `Full }
  else if String.length cls > 2 && String.sub cls 0 2 = "w-" then
    let n = String.sub cls 2 (String.length cls - 2) in
    (match int_of_string_opt n with
     | Some v -> { s with width = `Px (float_of_int v *. 4.) }
     | None -> s)
  else if String.length cls > 2 && String.sub cls 0 2 = "h-" then
    let n = String.sub cls 2 (String.length cls - 2) in
    (match int_of_string_opt n with
     | Some v -> { s with height = `Px (float_of_int v *. 4.) }
     | None -> s)
  (* Typography *)
  else if cls = "font-bold" then { s with font_weight = `Bold }
  else if cls = "font-semibold" then { s with font_weight = `Bold }
  else if cls = "font-normal" then { s with font_weight = `Normal }
  else if cls = "italic" then { s with font_style = `Italic }
  else if cls = "font-mono" then { s with font_family = `Mono }
  else if String.length cls >= 5 && String.sub cls 0 5 = "text-" then
    (* Could be text color or text size *)
    let rest = String.sub cls 5 (String.length cls - 5) in
    if rest = "xs" || rest = "sm" || rest = "base" || rest = "lg"
       || rest = "xl" || rest = "2xl" || rest = "3xl" || rest = "4xl"
       || rest = "5xl" then
      { s with font_size = font_size_of cls }
    else
      (match text_color_of cls with
       | Some c -> { s with text_color = c }
       | None -> s)
  (* Background *)
  else if String.length cls >= 3 && String.sub cls 0 3 = "bg-" then
    (match bg_color_of cls with
     | Some c -> { s with bg_color = Some c }
     | None -> s)
  (* Borders *)
  else if cls = "rounded" then { s with border_radius = 4. }
  else if cls = "rounded-md" then { s with border_radius = 6. }
  else if cls = "rounded-lg" then { s with border_radius = 8. }
  else if cls = "rounded-xl" then { s with border_radius = 12. }
  else if cls = "rounded-2xl" then { s with border_radius = 16. }
  else if cls = "rounded-full" then { s with border_radius = 9999. }
  else if cls = "border" then
    { s with border_width = 1.;
             border_color = (if s.border_color = None then Some stone_200 else s.border_color) }
  else if cls = "border-2" then
    { s with border_width = 2.;
             border_color = (if s.border_color = None then Some stone_200 else s.border_color) }
  else if String.length cls >= 7 && String.sub cls 0 7 = "border-" then
    (match border_color_of cls with
     | Some c -> { s with border_color = Some c;
                          border_width = (if s.border_width = 0. then 1. else s.border_width) }
     | None -> s)
  (* Shadow *)
  else if cls = "shadow" then { s with shadow = `Sm }
  else if cls = "shadow-md" then { s with shadow = `Md }
  else if cls = "shadow-lg" then { s with shadow = `Md }
  (* Overflow *)
  else if cls = "overflow-hidden" then { s with overflow_hidden = true }
  else s  (* unknown class: ignore *)

(** Parse a space-separated Tailwind class string into a [style]. *)
let parse_classes ?(base = default_style) (classes : string) : style =
  let parts = String.split_on_char ' ' classes in
  List.fold_left (fun s cls ->
    let cls = String.trim cls in
    if cls = "" then s else apply_class s cls
  ) base parts
