{-# LANGUAGE CPP        #-}
{-# LANGUAGE Rank2Types #-}

{- |
Module                  : Summoner.Tui.Field
Copyright               : (c) 2018-2026 Kowainik
SPDX-License-Identifier : MPL-2.0
Maintainer              : Kowainik <xrom.xkov@gmail.com>
Stability               : Stable
Portability             : Portable

This modules adds necessary functions for Forms and Form fields
that are not covered in @brick@ library.
-}

module Summoner.Tui.Field
       ( strField
       , checkboxField
       , activeCheckboxField
       , radioField
       , disabledAttr
       ) where

import Brick (BrickEvent (..), EventM, Location (..), Widget, attrName, clickable, showCursor, str,
              vBox, withAttr, withDefAttr, (<+>))
import Brick.AttrMap (AttrName)
#if MIN_VERSION_brick(2,2,0)
import Brick.Forms (FormFieldVisibilityMode (ShowCompositeField))
#endif
import Brick.Forms (FormField (..), FormFieldState (..), checkboxCustomField, focusedFormInputAttr,
                    radioCustomField)
import Lens.Micro (Lens', lens, (^.))

import qualified Graphics.Vty as V


-- | A form field with a given text value which can not be modified or changed
-- via any events. It is always valid.
strField :: forall s e n . String -> s -> FormFieldState s e n
strField t _ = FormFieldState
    { formFieldState = ()
    , formFieldLens = fakeLens
    , formFields = []
    , formFieldRenderHelper = renderString
    , formFieldConcat = vBox
    , formFieldUpdate = flip const
#if MIN_VERSION_brick(2,2,0)
    , formFieldVisibilityMode = ShowCompositeField
#endif
    }
  where
    -- looool
    fakeLens :: Lens' s ()
    fakeLens = lens (const ()) (\s () -> s)

    renderString :: Widget n -> Widget n
    renderString w = str t <+> w

{- | Custom checkbox with unique fancy style.

__Example:__

@
⟦✔⟧ Library
⟦ ⟧ Executable
@
-}
checkboxField
    :: (Ord n, Show n)
    => Lens' s Bool -- ^ The state lens for this value.
    -> n            -- ^ The resource name for the input field.
    -> Text         -- ^ The label for the check box, to appear at its right.
    -> s            -- ^ The initial form state.
    -> FormFieldState s e n
checkboxField = checkboxCustomField '⟦' '✔' '⟧'

{- | Custom radio button with unique fancy style.

__Example:__

@
❮◆❯ Enable  ❮ ❯ Disable
@
-}
radioField
    :: (Ord n, Show n, Eq a)
    => Lens' s a       -- ^ The state lens for this value.
    -> [(a, n, Text)]  -- ^ The available choices, in order.
    -> s               -- ^ The initial form state.
    -> FormFieldState s e n
radioField = radioCustomField '❮' '◆' '❯'

-- | Checkbox that can be disabled.
activeCheckboxField
    :: forall n s e . Ord n
    => Lens' s Bool
    -> (s -> n -> Bool)  -- ^ Function should return 'False' if checkbox should be disabled.
    -> n
    -> String  -- ^ The label for the check box, to appear at its right.
    -> s       -- ^ The initial form state.
    -> FormFieldState s e n
activeCheckboxField stLens isActive name label initialState = FormFieldState
    { formFieldState        = initVal
    , formFields            = [checkboxFormField]
    , formFieldLens         = stLens
    , formFieldRenderHelper = id
    , formFieldConcat       = vBox
    , formFieldUpdate       = flip const
#if MIN_VERSION_brick(2,2,0)
    , formFieldVisibilityMode = ShowCompositeField
#endif
    }
  where
    initVal, isEnabled :: Bool
    initVal   = initialState ^. stLens
    isEnabled = isActive initialState name

    handleEvent :: BrickEvent n e -> EventM n Bool ()
    handleEvent (MouseDown n _ _ _)
        | isEnabled && n == name = get >>= put . not
    handleEvent (VtyEvent (V.EvKey (V.KChar ' ') [])) = get >>= put . not
    handleEvent _ = pass

    checkboxFormField :: FormField Bool Bool e n
    checkboxFormField = FormField
        { formFieldName = name
        , formFieldValidate = Just
        , formFieldExternallyValid = True
        , formFieldRender = renderCheckbox isEnabled label name
        , formFieldHandleEvent = handleEvent
        }

-- | Renders checkbox depending on its state.
renderCheckbox :: (Ord n) => Bool -> String -> n -> Bool -> Bool -> Widget n
renderCheckbox isEnabled label n foc val =
    let addAttr = if foc then withDefAttr focusedFormInputAttr else id
        csr = if foc then showCursor n (Location (1,0)) else id
    in if isEnabled
           then clickable n $ addAttr $ csr $ str $
               "⟦" <> (if val then "✔" else " ") <> "⟧" <> " " <> label
           else withAttr disabledAttr $ str $ "⟦ ⟧ " <> label

-- | Attribute for disabled checkboxes.
disabledAttr :: AttrName
disabledAttr = attrName "disabled"
