(* file: working.ml *)
open Fmlib_browser
open Svg
open Data

(* Goal *)
type goal = Light | Colour | Durable

(*
  Declared in data.ml:
  type fabric_data = { fabric : string; colour : string; in_stock : bool }
*)

let string_of_goal (g : goal) : string =
  match g with
  | Light -> "Less Weight"
  | Colour -> "More Colour"
  | Durable -> "More Durable"

(* Pack and volume*)
type pack = C65 | C55 | C45 | A65 | A55 | A45
type volume = V65 | V55 | V45

(* Need this type to transfer properly between pack volumes while maintaining style, colour info *)
type style = Classic | Alpine

(* Torso length *)
type torso = T56 | T53 | T50 | T48 | T45 | T43

let string_of_torso (t : torso) : string =
  match t with
  | T56 -> "56 XL"
  | T53 -> "53 L"
  | T50 -> "50 L"
  | T48 -> "48 M"
  | T45 -> "45 M"
  | T43 -> "43 S"

(* Waist belt size *)
type waist = W_Med | W_Large

let string_of_waist (w : waist) : string =
  match w with W_Med -> "75 M" | W_Large -> "80 L"

(* Messages *)
type msg =
  | Query of string
  | Light
  | Colour
  | Durable
  | V65
  | V55
  | V45
  | Classic
  | Alpine
  | C65
  | C55
  | C45
  | A65
  | A55
  | A45
  | Side_panels of fabric_data
  | Side_pockets of fabric_data
  | Back_panel of fabric_data
  | Rolltop of fabric_data
  | T56
  | T53
  | T50
  | T48
  | T45
  | T43
  | W_Med
  | W_Large
  | Email of string
  | Sold
  | Not_sold

(* Pack data structures *)
type pack_data = {
  pack : pack;
  message : msg;
  volume : volume;
  style : style;
  str : string;
  price : int;
  ultra_price : int;
  featues : string list;
}

let record_of_pack (p : pack) : pack_data =
  match p with
  | C65 ->
      {
        pack = C65;
        message = C65;
        volume = V65;
        style = Classic;
        str = "65l Classic";
        price = c65_price;
        ultra_price = v65_ultra_price;
        featues = c65_featues;
      }
  | C55 ->
      {
        pack = C55;
        message = C55;
        volume = V55;
        style = Classic;
        str = "55l Classic";
        price = c55_price;
        ultra_price = v55_ultra_price;
        featues = c65_featues;
      }
  | C45 ->
      {
        pack = C45;
        message = C45;
        volume = V45;
        style = Classic;
        str = "45l Classic";
        price = c45_price;
        ultra_price = v45_ultra_price;
        featues = c45_featues;
      }
  | A65 ->
      {
        pack = A65;
        message = A65;
        volume = V65;
        style = Alpine;
        str = "65l Alpine";
        price = a65_price;
        ultra_price = v65_ultra_price;
        featues = a65_featues;
      }
  | A55 ->
      {
        pack = A55;
        message = A55;
        volume = V55;
        style = Alpine;
        str = "55l Alpine";
        price = a55_price;
        ultra_price = v55_ultra_price;
        featues = a55_featues;
      }
  | A45 ->
      {
        pack = A45;
        message = A45;
        volume = V45;
        style = Alpine;
        str = "45l Alpine";
        price = a45_price;
        ultra_price = v45_ultra_price;
        featues = a45_featues;
      }

let list_of_volume (v : volume) : pack_data list =
  match v with
  | V65 -> [ record_of_pack A65; record_of_pack C65 ]
  | V55 -> [ record_of_pack A55; record_of_pack C55 ]
  | V45 -> [ record_of_pack A45; record_of_pack C45 ]

let string_of_pack p = match record_of_pack p with { str } -> str
let ft_lst_of_pack p = match record_of_pack p with { featues } -> featues

let string_of_volume (v : volume) : string =
  match v with V65 -> "65l" | V55 -> "55l" | V45 -> "45l"

let string_of_style (s : style) : string =
  match s with Classic -> "Classic" | Alpine -> "Alpine"

(* Fabric data structures *)
type fabric_by_goal = {
  light : fabric_data list;
  colour : fabric_data list;
  durable : fabric_data list;
}

let get_fabric_by_goal (g : goal) (f : fabric_by_goal) =
  match g with Light -> f.light | Colour -> f.colour | Durable -> f.durable

(* TO DO: need to filter panel, pocket, rolltop match for in_stock, but at present all fabric in stock*)
let panel_match (p : pack) : fabric_by_goal =
  match p with
  | C65 ->
      { light = vx21; colour = List.concat [ epx200; epx400 ]; durable = ultra }
  | C55 ->
      { light = vx21; colour = List.concat [ epx200; epx400 ]; durable = ultra }
  | C45 ->
      { light = List.concat [ vx07; vx21 ]; colour = epx200; durable = ultra }
  | A65 ->
      { light = vx21; colour = List.concat [ epx200; epx400 ]; durable = ultra }
  | A55 ->
      { light = vx21; colour = List.concat [ epx200; epx400 ]; durable = ultra }
  | A45 ->
      { light = vx21; colour = List.concat [ epx200; epx400 ]; durable = ultra }

let pocket_match (p : pack) : fabric_by_goal =
  match p with
  | C65 ->
      {
        light = List.concat [ vx07; gridstop ];
        colour = epx200;
        durable = List.concat [ vx07; epx200 ];
      }
  | C55 ->
      {
        light = List.concat [ vx07; gridstop ];
        colour = epx200;
        durable = List.concat [ vx07; epx200 ];
      }
  | C45 ->
      {
        light = List.concat [ liteskin; vx07; gridstop ];
        colour = List.concat [ epx200; gridstop ];
        durable = List.concat [ vx07; epx200 ];
      }
  | A65 ->
      {
        light = List.concat [ vx21; vx42 ];
        colour = List.concat [ epx400; epx200 ];
        durable = ultra;
      }
  | A55 ->
      {
        light = List.concat [ vx21; vx42 ];
        colour = List.concat [ epx400; epx200 ];
        durable = ultra;
      }
  | A45 ->
      {
        light = List.concat [ vx21; vx42 ];
        colour = List.concat [ epx200; epx400 ];
        durable = ultra;
      }

let rolltop_match (p : pack) =
  match p with
  | C65 ->
      { light = vx07; colour = epx200; durable = List.concat [ vx07; epx200 ] }
  | C55 ->
      { light = vx07; colour = epx200; durable = List.concat [ vx07; epx200 ] }
  | C45 ->
      {
        light = List.concat [ liteskin; vx07 ];
        colour = epx200;
        durable = List.concat [ vx07; epx200 ];
      }
  | A65 ->
      { light = vx07; colour = epx200; durable = List.concat [ vx07; epx200 ] }
  | A55 ->
      { light = vx07; colour = epx200; durable = List.concat [ vx07; epx200 ] }
  | A45 ->
      { light = vx07; colour = epx200; durable = List.concat [ vx07; epx200 ] }

type buy_now = Sold | Not_sold

let string_of_buy_now s = match s with Sold -> "Sold" | Not_sold -> "Not sold"

(* Model *)
type state = {
  history : state list;
  query : string;
  volume : volume;
  style : style;
  pack : pack;
  goal : goal;
  side_panels : fabric_data;
  side_pockets : fabric_data;
  back_panel : fabric_data;
  rolltop : fabric_data;
  torso : torso;
  waist : waist;
  email : string;
  buy_now : buy_now;
}

let init : state =
  {
    history = [];
    query = "";
    volume = V55;
    style = Classic;
    pack = C55;
    goal = Colour;
    (* golden dazy *)
    side_panels = List.nth epx200 4;
    (* brick red *)
    side_pockets = List.nth epx200 2;
    (* tropic teal *)
    back_panel = List.nth epx200 7;
    (* bright blue *)
    rolltop = List.nth epx200 8;
    torso = T50;
    waist = W_Large;
    email = "";
    buy_now = Not_sold;
  }

let string_of_state s =
  s.query ^ " | " ^ string_of_volume s.volume ^ " | " ^ string_of_style s.style
  ^ " | " ^ string_of_pack s.pack ^ " | " ^ string_of_goal s.goal ^ " | "
  ^ s.back_panel.fabric ^ " | " ^ s.rolltop.fabric ^ " | "
  ^ s.side_panels.fabric ^ " | " ^ s.side_pockets.fabric ^ " | "
  ^ string_of_torso s.torso ^ " | " ^ string_of_waist s.waist ^ " | " ^ s.email
  ^ " | "
  ^ string_of_buy_now s.buy_now

(* Takes first n items from a list, used to truncate length of state history *)
let rec take n lst =
  if n = 0 || lst = [] then [] else List.hd lst :: take (n - 1) (List.tl lst)

let price s =
  let p = record_of_pack s.pack in
  match s.goal with
  | Durable -> p.price + p.ultra_price
  | Light -> p.price
  | Colour -> p.price

(* Views*)
let test_view state =
  let open Html in
  let open Attribute in
  let section attrs nodes = node "section" attrs nodes in
  section []
    [
      ul []
        [
          li [] [ text "Query: "; text state.query ];
          li [] [ text "Volume: "; text (string_of_volume state.volume) ];
          li [] [ text "Style: "; text (string_of_style state.style) ];
          li [] [ text "Pack: "; text (string_of_pack state.pack) ];
          li [] [ text "Goal: "; text (string_of_goal state.goal) ];
          li [] [ text "Side Panels: "; text state.side_panels.fabric ];
          li [] [ text "Side Pockets: "; text state.side_pockets.fabric ];
          li [] [ text "Back Panel: "; text state.back_panel.fabric ];
          li [] [ text "Rolltop: "; text state.rolltop.fabric ];
          li [] [ text "Torso Length: "; text (string_of_torso state.torso) ];
          li [] [ text "Waist Belt: "; text (string_of_waist state.waist) ];
          li [] [ text "Email: "; text state.email ];
          li [] [ text "Buy Now: "; text (string_of_buy_now state.buy_now) ];
        ];
      ol []
        (List.map (fun x -> li [] [ text (string_of_state x) ]) state.history);
    ]

let header_view =
  let open Html in
  let open Attribute in
  let nav attrs nodes = node "nav" attrs nodes in
  let img attrs nodes = node "img" attrs nodes in
  let a attrs nodes = node "a" attrs nodes in
  nav []
    [
      ul [] [ li [] [ img [ src "logo.svg"; style "width" "20rem" ] [] ] ];
      ul []
        [
          li [] [ a [ href "https://fiordlandpacks.fly.dev" ] [ text "Blog" ] ];
          li [] [ a [ href "https://skraak.kiwi" ] [ text "Kiwi" ] ];
          li [] [ a [ href "/contact" ] [ text "Contact" ] ];
        ];
    ]

let query_view state =
  let open Html in
  let open Attribute in
  let form attrs nodes = node "form" attrs nodes in
  let query str = Query str in
  form []
    [
      input
        [
          attribute "type" "search";
          attribute "id" "search";
          attribute "name" "search";
          attribute "placeholder" "Ask a question";
          value state.query;
          on_input query;
        ]
        [];
    ]

let react_button f btn_type s btn_msg =
  let open Html in
  let open Attribute in
  button
    [ (if s = btn_type then class_ "contrast" else on_click btn_msg) ]
    [ text (f btn_type) ]

let goal_buttons s hd =
  let open Html in
  let open Attribute in
  let section attrs nodes = node "section" attrs nodes in
  section []
    [
      h2 [] [ text hd ];
      div
        [ attribute "role" "group" ]
        [
          react_button string_of_goal Light s Light;
          react_button string_of_goal Colour s Colour;
          react_button string_of_goal Durable s Durable;
        ];
    ]

let volume_buttons s hd =
  let open Html in
  let open Attribute in
  let section attrs nodes = node "section" attrs nodes in
  section []
    [
      h2 [] [ text hd ];
      div
        [ attribute "role" "group" ]
        [
          react_button string_of_volume V65 s V65;
          react_button string_of_volume V55 s V55;
          react_button string_of_volume V45 s V45;
        ];
    ]

let view_of_pack (p : pack) =
  match p with
  | A45 -> a45_view
  | A55 -> a55_view
  | A65 -> a65_view
  | C45 -> c45_view
  | C55 -> c55_view
  | C65 -> c65_view

let pack_card (state : state) (pack : pack) =
  let open Html in
  let open Attribute in
  let article attrs nodes = node "article" attrs nodes in
  let footer attrs nodes = node "footer" attrs nodes in
  div []
    [
      article []
        [
          view_of_pack pack state.back_panel.colour state.rolltop.colour
            state.side_panels.colour state.side_pockets.colour;
          footer [] [ text (string_of_pack pack) ];
        ];
    ]

let pack_view state hd =
  let open Html in
  let open Attribute in
  let section attrs nodes = node "section" attrs nodes in
  let details attrs nodes = node "details" attrs nodes in
  let summary attrs nodes = node "summary" attrs nodes in
  let hr attrs nodes = node "hr" attrs nodes in
  let packs = list_of_volume state.volume in
  let card = pack_card state in
  section
    [ id "packs" ]
    [
      h2 [] [ text hd ];
      div
        [ attribute "role" "group" ]
        (List.map
           (fun (p : pack_data) ->
             react_button string_of_pack p.pack state.pack p.message)
           packs);
      div [ padding "1rem" ] [];
      card state.pack;
      hr [] [];
      details []
        [
          summary [] [ text "Features: "; text (string_of_pack state.pack) ];
          ul []
            (List.map (fun x -> li [] [ text x ]) (ft_lst_of_pack state.pack));
        ];
      hr [] [];
    ]

(* fbfcfc *)
let picker s hd match_fun update_fun bc =
  let open Html in
  let open Attribute in
  let details attrs nodes = node "details" attrs nodes in
  let summary attrs nodes = node "summary" attrs nodes in
  details
    [ class_ "dropdown" ]
    [
      summary [ background_color bc; color "black" ] [ text hd ];
      ul []
        (List.map
           (fun (x : fabric_data) ->
             li
               [
                 on_click (update_fun x);
                 color "black";
                 background_color x.colour;
               ]
               [ text x.fabric ])
           (* can filter for in stock *)
           (s.pack |> match_fun |> get_fabric_by_goal s.goal));
    ]

let fabric_view s hd =
  let open Html in
  let open Attribute in
  let section attrs nodes = node "section" attrs nodes in
  let back_panel (f : fabric_data) = Back_panel f in
  let side_panels (f : fabric_data) = Side_panels f in
  let side_pockets (f : fabric_data) = Side_pockets f in
  let rolltop (f : fabric_data) = Rolltop f in
  section []
    [
      h2 [] [ text hd ];
      section
        [ class_ "grid" ]
        [
          picker s "Side panels" panel_match side_panels s.side_panels.colour;
          picker s "Side pockets" pocket_match side_pockets
            s.side_pockets.colour;
          picker s "Back panel" panel_match back_panel s.back_panel.colour;
          picker s "Rolltop" rolltop_match rolltop s.rolltop.colour;
        ];
    ]

let torso_view s hd =
  let open Html in
  let open Attribute in
  let section attrs nodes = node "section" attrs nodes in
  let details attrs nodes = node "details" attrs nodes in
  let summary attrs nodes = node "summary" attrs nodes in
  let hr attrs nodes = node "hr" attrs nodes in
  let img attrs nodes = node "img" attrs nodes in
  section []
    [
      h2 [] [ text hd ];
      div
        [ attribute "role" "group" ]
        [
          react_button string_of_torso T43 s.torso T43;
          react_button string_of_torso T45 s.torso T45;
          react_button string_of_torso T48 s.torso T48;
          react_button string_of_torso T50 s.torso T50;
          react_button string_of_torso T53 s.torso T53;
          react_button string_of_torso T56 s.torso T56;
        ];
      hr [] [];
      details []
        [
          summary [] [ text "How to measure your torso" ];
          div
            [ class_ "grid" ]
            [
              img [ src "torso.svg" ] [];
              div []
                [
                  p []
                    [
                      text
                        "Bowing your head forward, find the bump sticking out \
                         on your spine at the bottom of your neck, the C7 \
                         vertebra.";
                    ];
                  p []
                    [
                      text
                        "Find the top of your sacrum by placing your fore \
                         fingers on your hip bones, thumbs pointing in and \
                         resting in the small of your back.";
                    ];
                  p []
                    [
                      text
                        "Use a tape measure or piece of string to measure the \
                         length of your spine in between, conforming to the \
                         curves of your back.";
                    ];
                  p []
                    [
                      text
                        "Measure a few times at different times, compare your \
                         final result with the length of a known good pack.";
                    ];
                ];
            ];
        ];
      hr [] [];
    ]

let waist_view s hd =
  let open Html in
  let open Attribute in
  let section attrs nodes = node "section" attrs nodes in
  let details attrs nodes = node "details" attrs nodes in
  let summary attrs nodes = node "summary" attrs nodes in
  let hr attrs nodes = node "hr" attrs nodes in
  let img attrs nodes = node "img" attrs nodes in
  section []
    [
      h2 [] [ text hd ];
      div
        [ attribute "role" "group" ]
        [
          react_button string_of_waist W_Med s.waist W_Med;
          react_button string_of_waist W_Large s.waist W_Large;
        ];
      hr [] [];
      details []
        [
          summary [] [ text "How to measure your waist" ];
          div
            [ class_ "grid" ]
            [
              img [ src "waist.svg" ] [];
              div []
                [
                  p []
                    [
                      text
                        "A large waist belt is 81 cm tip to tip. A medium \
                         waist belt is 75 cm tip to tip.";
                    ];
                  p []
                    [
                      text
                        "Your waist belt, tip to tip, should be 80 to 90 \
                         percent of your waist circumference. Generally, \
                         either size will be fine for most average people, I \
                         use both sizes myself.";
                    ];
                ];
            ];
        ];
      hr [] [];
    ]

let buy_now s =
  let open Html in
  let open Attribute in
  let email str = Email str in
  let section attrs nodes = node "section" attrs nodes in
  let small attrs nodes = node "small" attrs nodes in
  section []
    [
      label []
        [
          text "Email";
          input
            [
              attribute "type" "email";
              attribute "autocomplete" "email";
              id "email";
              value s.email;
              on_input email;
            ]
            [];
          small [] [ text "Needed to match detailed order to payment"]
        ];
      div [ padding "1rem" ] [];
      p [] [ text "Lead time on your pack is "; text wait_time ];
      p []
        [
          text "Price: $"; text (string_of_int (price s)); text " plus shipping";
        ];
      button
        [ class_ "contrast"; padding "1rem"; on_click (Sold : msg) ]
        [ text "Buy Now" ];
    ]

let view state =
  let open Html in
  let open Attribute in
  div []
    [
      div [ id "header"; class_ "container" ] [ header_view ];
      div
        [ id "main"; class_ "container" ]
        [
          h1 [] [ text "Comfortable Kiwi made backpacks" ];
          query_view state;
          div [ padding "1rem" ] [];
          volume_buttons state.volume "Your pack volume";
          div [ padding "1rem" ] [];
          pack_view state "Choose your pack";
          div [ padding "1rem" ] [];
          goal_buttons state.goal "Your main priority";
          div [ padding "1rem" ] [];
          fabric_view state "Fabric and colour";
          div [ padding "1rem" ] [];
          torso_view state "Torso length (cm)";
          div [ padding "1rem" ] [];
          waist_view state "Waist belt (cm)";
          div [ padding "1rem" ] [];
          buy_now state;
          div [ padding "1rem" ] [];
          (* stock_view state;
                div [ padding "1rem" ] [];*)
          test_view state;
          div [ padding "1rem" ] [];
        ];
    ]

(* Helper for Update *)
let pack_of_vol_styl (vs_tup : volume * style) : pack_data =
  match vs_tup with
  | V65, Classic -> record_of_pack C65
  | V55, Classic -> record_of_pack C55
  | V45, Classic -> record_of_pack C45
  | V65, Alpine -> record_of_pack A65
  | V55, Alpine -> record_of_pack A55
  | V45, Alpine -> record_of_pack A45

(* Checks that the state of side_panels, side_pockets, back_panel, rolltop, is
   possible in the fabric_data list and returns plausible values.
   Run for any change in goal or pack *)
let check_back_panel state (g : goal) (p : pack) =
  let pan = panel_match p |> get_fabric_by_goal g in
  if List.memq state.back_panel pan then state.back_panel else List.hd pan

let check_side_panels state (g : goal) (p : pack) =
  let pan = panel_match p |> get_fabric_by_goal g in
  if List.memq state.side_panels pan then state.side_panels else List.hd pan

let check_pockets state (g : goal) (p : pack) =
  let pan = pocket_match p |> get_fabric_by_goal g in
  if List.memq state.side_pockets pan then state.side_pockets else List.hd pan

let check_rolltop state (g : goal) (p : pack) =
  let r = rolltop_match p |> get_fabric_by_goal g in
  if List.memq state.rolltop r then state.rolltop else List.hd r

(* Update *)
let update state msg =
  let new_state =
    match msg with
    | Query str -> { state with query = str }
    | Light ->
        {
          state with
          goal = Light;
          side_panels = check_side_panels state Light state.pack;
          side_pockets = check_pockets state Light state.pack;
          back_panel = check_back_panel state Light state.pack;
          rolltop = check_rolltop state Light state.pack;
        }
    | Colour ->
        {
          state with
          goal = Colour;
          side_panels = check_side_panels state Colour state.pack;
          side_pockets = check_pockets state Colour state.pack;
          back_panel = check_back_panel state Colour state.pack;
          rolltop = check_rolltop state Colour state.pack;
        }
    | Durable ->
        {
          state with
          goal = Durable;
          side_panels = check_side_panels state Durable state.pack;
          side_pockets = check_pockets state Durable state.pack;
          back_panel = check_back_panel state Durable state.pack;
          rolltop = check_rolltop state Durable state.pack;
        }
    | V65 ->
        {
          state with
          volume = V65;
          pack = (pack_of_vol_styl (V65, state.style)).pack;
        }
    | V55 ->
        {
          state with
          volume = V55;
          pack = (pack_of_vol_styl (V55, state.style)).pack;
        }
    | V45 ->
        {
          state with
          volume = V45;
          pack = (pack_of_vol_styl (V45, state.style)).pack;
        }
    (* never called *)
    | Classic ->
        {
          state with
          style = Classic;
          side_panels = check_side_panels state state.goal state.pack;
          side_pockets = check_pockets state state.goal state.pack;
          back_panel = check_back_panel state state.goal state.pack;
          rolltop = check_rolltop state state.goal state.pack;
        }
    (* never called *)
    | Alpine ->
        {
          state with
          style = Alpine;
          side_panels = check_side_panels state state.goal state.pack;
          side_pockets = check_pockets state state.goal state.pack;
          back_panel = check_back_panel state state.goal state.pack;
          rolltop = check_rolltop state state.goal state.pack;
        }
    | C65 ->
        {
          state with
          pack = C65;
          style = Classic;
          side_panels = check_side_panels state state.goal C65;
          side_pockets = check_pockets state state.goal C65;
          back_panel = check_back_panel state state.goal C65;
          rolltop = check_rolltop state state.goal C65;
        }
    | C55 ->
        {
          state with
          pack = C55;
          style = Classic;
          side_panels = check_side_panels state state.goal C55;
          side_pockets = check_pockets state state.goal C55;
          back_panel = check_back_panel state state.goal C55;
          rolltop = check_rolltop state state.goal C55;
        }
    | C45 ->
        {
          state with
          pack = C45;
          style = Classic;
          side_panels = check_side_panels state state.goal C45;
          side_pockets = check_pockets state state.goal C45;
          back_panel = check_back_panel state state.goal C45;
          rolltop = check_rolltop state state.goal C45;
        }
    | A65 ->
        {
          state with
          pack = A65;
          style = Alpine;
          side_panels = check_side_panels state state.goal A65;
          side_pockets = check_pockets state state.goal A65;
          back_panel = check_back_panel state state.goal A65;
          rolltop = check_rolltop state state.goal A65;
        }
    | A55 ->
        {
          state with
          pack = A55;
          style = Alpine;
          side_panels = check_side_panels state state.goal A55;
          side_pockets = check_pockets state state.goal A55;
          back_panel = check_back_panel state state.goal A55;
          rolltop = check_rolltop state state.goal A55;
        }
    | A45 ->
        {
          state with
          pack = A45;
          style = Alpine;
          side_panels = check_side_panels state state.goal A45;
          side_pockets = check_pockets state state.goal A45;
          back_panel = check_back_panel state state.goal A45;
          rolltop = check_rolltop state state.goal A45;
        }
    | Side_panels (f : fabric_data) -> { state with side_panels = f }
    | Side_pockets (f : fabric_data) -> { state with side_pockets = f }
    | Back_panel (f : fabric_data) -> { state with back_panel = f }
    | Rolltop (f : fabric_data) -> { state with rolltop = f }
    | T56 -> { state with torso = T56 }
    | T53 -> { state with torso = T53 }
    | T50 -> { state with torso = T50 }
    | T48 -> { state with torso = T48 }
    | T45 -> { state with torso = T45 }
    | T43 -> { state with torso = T43 }
    | W_Med -> { state with waist = W_Med }
    | W_Large -> { state with waist = W_Large }
    | Email str -> { state with email = str }
    | Sold -> { state with buy_now = Sold }
    | Not_sold -> { state with buy_now = Not_sold }
  in
  if List.length new_state.history < 20 then
    { new_state with history = state :: new_state.history }
  else { new_state with history = state :: take 19 new_state.history }

let _ = sandbox init view update