module Entry exposing (main)

import Browser
import Http

import Json.Decode as D
import Json.Decode.Pipeline as P

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)

main = Browser.element
  { init = \globals ->
    ( Context globals <| ItemSearch { searchTerm = "", searchResults = [] }
    , Cmd.none
    )
  , subscriptions = \_ -> Sub.none
  , update = update
  , view = view
  }

-- Data types

type alias SearchResult =
  { barcode : String
  , name : String
  , netUnitPrice : Float
  , bought : String
  , salesUnits : Int
  , available : Bool
  , locationName : String
  , locationId : Int
  , groupName : String
  , groupId : Int
  , taxGroupId : Int
  }

searchResultDecoder =
  D.succeed SearchResult
    |> P.required "item_barcode" D.string
    |> P.required "name" D.string
    |> P.required "unit_price" D.float
    |> P.required "bought" D.string
    |> P.required "sales_units" D.int
    |> P.required "available" D.bool
    |> P.required "location_name" D.string
    |> P.required "location_id" D.int
    |> P.required "group_name" D.string
    |> P.required "group_id" D.int
    |> P.required "tax_group_id" D.int

type alias Location =
  { id : Int
  , name : String
  }

type alias Group =
  { id : Int
  , name : String
  }

type alias TaxGroup =
  { id : Int
  , description : String
  , percentage : Float
  }

type alias Context =
  { globals : Globals
  , state : State
  }

type alias Globals =
  { locations : List Location
  , groups : List Group
  , taxGroups : List TaxGroup
  }

type State
  = ItemSearch
    { searchTerm : String
    , searchResults : List SearchResult
    }
  | ItemEditor
    { barcode : String
    , name : String
    , salesUnits : Int
    , netUnitPrice : Float
    , groupId : Int
    , locationId : Int
    , taxGroupId : Int
    }

type Msg
  = SetSearchTerm String
  | SubmitSearch
  | ReceiveSearchResults (Result Http.Error (List SearchResult))
  | GotoItemEditor SearchResult
  | SetNetUnitPrice String
  | SetGroupId String
  | SetLocationId String
  | SetTaxGroupId String
  | SetBarcode String
  | SetName String
  | SetSalesUnits String

-- Update logic: State machine etc.

update msg { globals, state } =
  let
    (state_, cmd) = updateState msg globals state
  in
    ({ globals = globals, state = state_ }, cmd)

updateState msg globals state = case state of
  ItemSearch model -> case msg of
    SetSearchTerm searchTerm ->
      (ItemSearch { model  | searchTerm = searchTerm }, Cmd.none)
    SubmitSearch ->
      ( state
      , Http.get
        { url = "/entry/api/search-items?search-term=" ++ model.searchTerm
        , expect = Http.expectJson ReceiveSearchResults <| D.list searchResultDecoder
        }
      )
    ReceiveSearchResults (Ok searchResults) ->
      (ItemSearch { model | searchResults = searchResults }, Cmd.none)
    GotoItemEditor searchResult ->
      ( ItemEditor
        { barcode = searchResult.barcode
        , groupId = searchResult.groupId
        , locationId = searchResult.locationId
        , name = searchResult.name
        , netUnitPrice = searchResult.netUnitPrice
        , salesUnits = searchResult.salesUnits
        , taxGroupId = searchResult.taxGroupId
        }
      , Cmd.none
      )
    _ ->
      (state, Cmd.none)
  ItemEditor model -> case msg of
    SetNetUnitPrice netUnitPriceStr -> case String.toFloat netUnitPriceStr of
      Nothing -> (state, Cmd.none)
      Just netUnitPrice -> (ItemEditor { model | netUnitPrice = netUnitPrice }, Cmd.none)
    SetGroupId groupIdStr -> case String.toInt groupIdStr of
      Nothing -> (state, Cmd.none)
      Just groupId -> (ItemEditor { model | groupId = groupId }, Cmd.none)
    SetLocationId locationIdStr -> case String.toInt locationIdStr of
      Nothing -> (state, Cmd.none)
      Just locationId -> (ItemEditor { model | locationId = locationId }, Cmd.none)
    SetTaxGroupId taxGroupIdStr -> case String.toInt taxGroupIdStr of
      Nothing -> (state, Cmd.none)
      Just taxGroupId -> (ItemEditor { model | taxGroupId = taxGroupId }, Cmd.none)
    SetBarcode barcode ->
      (ItemEditor { model | barcode = barcode }, Cmd.none)
    SetName name ->
      (ItemEditor { model | name = name }, Cmd.none)
    SetSalesUnits salesUnitsStr -> case String.toInt salesUnitsStr of
      Nothing -> (state, Cmd.none)
      Just salesUnits -> (ItemEditor { model | salesUnits = salesUnits }, Cmd.none)
    _ ->
      (state, Cmd.none)

-- View stuff

view { globals, state } = case state of
  ItemSearch model ->
    fieldset []
      [ legend [] [ text "Vorlage für neuen Inventareintrag" ]
      , Html.form [ onSubmit SubmitSearch ]
        [ div [ class "form-input" ]
          [ label [ for "search-term", title "Barcode oder Name" ] [ text "Suchbegriff" ]
          , input [ onInput SetSearchTerm, value model.searchTerm, id "search-term" ] []
          ]
        , table [] <| searchResultHeaders :: List.map viewSearchResult model.searchResults
        ]
      ]
  ItemEditor model -> case find (\tg -> tg.id == model.taxGroupId) globals.taxGroups of
    Nothing -> div [] [ text "index error, this should never happen" ]
    Just selectedTaxGroup ->
      Html.form []
        [ fieldset []
          [ legend [] [ text "Neuer Inventareintrag" ]
          , div [ class "form-input" ]
            [ label [ for "barcode" ] [ text "Barcode" ]
            , input [ onInput SetBarcode, value model.barcode, disabled True, id "barcode" ] []
            ]
          , div [ class "form-input" ]
            [ label [ for "name" ] [ text "Name" ]
            , input [ onInput SetName, value model.name, id "name" ] []
            ]
          , div [ class "form-input" ]
            [ label [ for "sales-units" ] [ text "Stückzahl" ]
            , input [ onInput SetSalesUnits, value <| String.fromInt model.salesUnits, id "sales-units", type_ "number" ] []
            ]
          , div [ class "form-input" ]
            [ label [ for "group" ] [ text "Gruppe" ]
            , viewSelect
              [ onInput SetGroupId, id "group" ]
              String.fromInt .id .name model.groupId globals.groups
            ]
          , div [ class "form-input" ]
            [ label [ for "location" ] [ text "Raum" ]
            , viewSelect
              [ onInput SetLocationId, id "location" ]
              String.fromInt .id .name model.locationId globals.locations
            ]
          , div [ class "form-input" ]
            [ label [ for "net-unit-price" ] [ text "Stückpreis (Netto)" ]
            , input
              [ value <| String.fromFloat model.netUnitPrice
              , onInput SetNetUnitPrice
              , type_ "number"
              , id "net-unit-price"
              , step "0.01"
              ]
              []
            ]
          , div [ class "form-input" ]
            [ label [ for "tax-group" ] [ text "Steuergruppe" ]
            , viewSelect
              [ onInput SetTaxGroupId, id "tax-group" ]
              String.fromInt .id .description model.taxGroupId globals.taxGroups
            ]
          ]
        , fieldset []
          [ legend [] [ text "Neuer Snackeintrag" ]
          , div [ class "form-input" ]
            [ label [ for "snack-name" ] [ text "Name" ]
            , input [ value model.name, disabled True, id "snack-name" ] []
            ]
          , div [ class "form-input" ]
            [ label [ for "gross-unit-price" ]
              [ text <| "Stückpreis (Brutto), Vorschlag: " ++ String.fromFloat (calculateGarfieldPrice model.netUnitPrice selectedTaxGroup.percentage)
              ]
            , input [ value <| String.fromFloat <| Maybe.withDefault (calculateGarfieldPrice model.netUnitPrice selectedTaxGroup.percentage) Nothing, id "gross-unit-price", type_ "number", step "0.01" ] []
            ]
          ]
        ]

viewSelect selectAttributes showValue getValue getLabel selectedValue xs =
  let
    viewOption x =
      option
        [ value <| showValue <| getValue x
        , selected <| getValue x == selectedValue
        ]
        [ text <| getLabel x
        ]
  in
    select selectAttributes <| List.map viewOption xs

searchResultHeaders =
  tr []
    [ th [] [ text "Barcode" ]
    , th [] [ text "Name" ]
    , th [] [ text "Gruppe" ]
    , th [] [ text "Stückpreis (Netto)" ]
    , th [] [ text "Eingekauft" ]
    , th [] [ text "Kaufdatum" ]
    , th [] [ text "Raum" ]
    , th [] [ text "Aktiv?" ]
    , th [] []
    ]

viewSearchResult model =
  tr []
    [ td [] [ code [] [ text model.barcode ] ]
    , td [] [ text model.name ]
    , td [] [ text model.groupName ]
    , td [] [ text <| String.fromFloat model.netUnitPrice ]
    , td [] [ text <| String.fromInt model.salesUnits ]
    , td [] [ text model.bought ]
    , td [] [ text model.locationName ]
    , td [] [ text <| showBool model.available ]
    , td []
      [ button [ onClick <| GotoItemEditor model ] [ text "Als Vorlage verwenden" ]
      ]
    ]

calculateGarfieldPrice netUnitPrice taxPercentage =
  roundTo 2 <| netUnitPrice * (1 + taxPercentage) + 0.01

roundTo places x = toFloat (round <| x * 10 ^ places) / 10 ^ places

showBool b = case b of
  True -> "✅"
  False -> "❌"

find pred xs = List.head <| List.filter pred xs