module Panes.Person.Files exposing (..)

import Data exposing (Id, File)
import Html exposing (Html, div, text)
import Html.Attributes as HA exposing (class)
import Html.Events as HE
import Panes exposing (Event)
import Triple exposing (Triple, triple)
import Network.Messages
import Panes.Modal.FileInfo
import Tools
import PaneMeta exposing (PaneMeta)
import ContextMenu exposing (ContextMenu, Item)
import Browser.Dom
import Task
import Http
import Url
import DnD
import Json.Decode as JD

type FileTree
    = Node File Bool Tree
    | Load File

type alias Tree = List FileTree

hasSubNodes : FileTree -> Maybe Bool
hasSubNodes node =
    case node of
        Node _ _ [] -> Just False
        Node _ _ _ -> Just True
        Load _ -> Nothing

getOpen : FileTree -> Bool
getOpen node =
    case node of
        Node _ open _ -> open
        Load _ -> False

getFile : FileTree -> File
getFile node =
    case node of
        Node file _ _  -> file
        Load file -> file

get : Id -> Tree -> Maybe File
get id tree =
    Maybe.map getFile 
    <| getNode id tree

getNode : Id -> Tree -> Maybe FileTree
getNode id tree =
    case tree of
        [] -> Nothing
        Node file open list :: other->
            if file.id == id
            then Just <| Node file open list
            else case getNode id list of
                Just found -> Just found
                Nothing -> getNode id other
        Load file :: other->
            if file.id == id
            then Just <| Load file
            else getNode id other

transform : (FileTree -> FileTree) -> Id -> Tree -> Tree
transform mapper id =
    let
        walk : Tree -> (Tree, Bool)
        walk tree =
            List.foldr
                (\node (result, continue) ->
                    if continue
                    then case node of
                        Node file open items ->
                            if file.id == id
                            then (mapper node :: result, False)
                            else walk items
                                |> Tuple.mapFirst
                                    (\x -> Node file open x :: result)
                        Load file ->
                            if file.id == id
                            then (mapper node :: result, False)
                            else (node :: result, True)
                    else (node :: result, False)
                )
                ([], True)
                tree
    in walk >> Tuple.first

remove : Id -> Tree -> Tree
remove id =
    let
        walk : Tree -> (Tree, Bool)
        walk tree =
            List.foldr
                (\node (result, continue) ->
                    if continue
                    then case node of
                        Node file open items ->
                            if file.id == id
                            then (result, False)
                            else walk items
                                |> Tuple.mapFirst
                                    (\x -> Node file open x :: result)
                        Load file ->
                            if file.id == id
                            then (result, False)
                            else (node :: result, True)
                    else (node :: result, False)
                )
                ([], True)
                tree
    in walk >> Tuple.first

transformOrAppend : (FileTree -> FileTree) -> (() -> FileTree) -> Maybe Id ->  Id -> Tree -> Tree
transformOrAppend mapper creator parent id =
    let
        walk : Tree -> (Tree, Bool)
        walk tree =
            List.foldr
                (\node (result, continue) ->
                    if continue
                    then case node of
                        Node file open items ->
                            if file.id == id
                            then (mapper node :: result, False)
                            else case walk items of
                                (x, False) -> (Node file open x :: result, False)
                                (_, True) ->
                                    if Just file.id == parent
                                    then (Node file open (items ++ [ creator () ]) :: result, False)
                                    else (node :: result, True)
                        Load file ->
                            if file.id == id
                            then (mapper node :: result, False)
                            else 
                                if Just file.id == parent
                                then (Node file False [ creator () ] :: result, False)
                                else (node :: result, True)
                    else (node :: result, False)
                )
                ([], True)
                tree

        addIfNotDone : (Tree, Bool) -> Tree
        addIfNotDone (tree, continue) =
            if continue
            then tree ++ [ creator () ]
            else tree

    in walk >> addIfNotDone

append : List File -> Id -> Tree -> Tree
append files =
    transform
        (\node ->
            case node of
                Node file open items ->
                    Node file open
                        <| (++) items
                        <| List.map create files
                Load file ->
                    Node file False
                        <| List.map create files
        )

create : File -> FileTree
create file =
    if file.mime == "folder"
    then Load file
    else Node file False []

openNode : Bool -> Id -> Tree -> Tree
openNode open =
    transform
    <| \node ->
        case node of
            Node file _ items -> Node file open items
            Load file -> Load file

type alias Model =
    { tree: Tree
    , menu: ContextMenu File
    , edit: Edit
    , database: String
    , person: Id
    , draggable: DnD.Draggable File File
    , moving: Bool
    , stopClick: Bool
    }

type NodeMsg
    = Open Id Bool
    | None
    | SendEvent Panes.Event
    | SetEdit Edit
    | PerformRename Id String
    | PerformAddDir (Maybe Id) String
    | WrapContextMenu (ContextMenu.Msg File)
    | WrapDragDrop (DnD.Msg File File)

type Edit
    = NoEdit
    | AddDir (Maybe Id) String
    | Rename Id String

dnd : DnD.DraggableInit File File NodeMsg
dnd = DnD.init
    WrapDragDrop
    (\_ _ -> None)

init : String -> Id -> (Model, Cmd NodeMsg)
init database person =
    ContextMenu.init
    |> Tuple.mapBoth
        (\menu ->
            { tree = []
            , menu = menu
            , edit = NoEdit
            , database = database
            , person = person
            , draggable = dnd.model
            , moving = False
            , stopClick = False
            }
        )
        (Cmd.map WrapContextMenu)

viewContextMenu : PaneMeta -> Model -> File -> List (List (Item, NodeMsg))
viewContextMenu meta model file =
    List.filterMap identity
    [ Just
        [ Tuple.pair
            (ContextMenu.item "Upload")
            <| SendEvent
            <| Panes.Upload model.person 
            <| if file.mime == "folder"
                then Just file.id
                else file.parent
        ]
    , Just
        [ Tuple.pair
            (ContextMenu.item "Add Folder inside"
                |> ContextMenu.disabled (file.mime /= "folder")
            )
            <| SetEdit <| AddDir (Just file.id)
            <| Tools.viewDate meta.zone meta.now ++ " New Folder"
        , Tuple.pair
            (ContextMenu.item "Add Folder next to it"
            )
            <| SetEdit <| AddDir file.parent
            <| Tools.viewDate meta.zone meta.now ++ " New Folder"
        ]
    -- , if file.parent /= Nothing || not (PaneMeta.hasConfidential model.database meta)
    , if not (PaneMeta.hasConfidential model.database meta)
        then Nothing 
        else Just
            [ Tuple.pair
                (ContextMenu.item "Mark as confidential"
                    |> ContextMenu.disabled file.confidential
                )
                <| SendEvent
                <| Panes.Send
                <| Network.Messages.FileChangeConfidential file.id True
            , Tuple.pair
                (ContextMenu.item "Mark as non-confidential"
                    |> ContextMenu.disabled (not file.confidential)
                )
                <| SendEvent
                <| Panes.Send
                <| Network.Messages.FileChangeConfidential file.id False
            ]
    , Just
        [ Tuple.pair
            (ContextMenu.item "Rename"
            )
            <| SetEdit <| Rename file.id file.name
        , Tuple.pair
            (ContextMenu.item "Delete"
            )
            <| SendEvent
            <| Panes.Send
            <| Network.Messages.FileDelete file.id
        ]
    , Just
        [ Tuple.pair
            (ContextMenu.item "Properties")
            <| SendEvent
            <| Panes.OpenFile file.id
        ]
    ]

contextMenuConfig : ContextMenu.Config
contextMenuConfig = ContextMenu.defaultConfig |> \conf ->
    { conf 
    | fontFamily = "inherit"
    , rounded = True
    }

view : PaneMeta -> Model -> Html NodeMsg
view meta ({ tree, edit, menu, database, person } as model) =
    div []
        [ viewTree meta (if model.moving then Just model.draggable else Nothing)
            edit Nothing True model.stopClick tree
        , div [ class "pane-person-file-links", class "utils-links" ]
            <| List.filterMap identity
            [ Just <| Html.a
                [ class "utils-link"
                , HE.onClick 
                    <| SetEdit <| AddDir Nothing
                    <| Tools.viewDate meta.zone meta.now ++ " New Folder"
                ]
                [ text "Add folder to root" ]
            -- , if PaneMeta.hasConfidential database meta
            --     then Just
            --         <| Html.a
            --             [ class "utils-link" ]
            --             [ text "Add confidential folder to root" ]
            --     else Nothing
            , Just <| Html.a
                [ class "utils-link"
                , HE.onClick <| SendEvent <| Panes.Upload person Nothing
                ]
                [ text "Upload file to root" ]
            ]
        , if model.moving
            then
                div [ class "pane-person-file-draged-item" ]
                [ DnD.dragged model.draggable
                    <| viewNode meta (Just model.draggable) [] NoEdit False False True
                ]
            else text ""
        , ContextMenu.view
            contextMenuConfig
            WrapContextMenu
            (viewContextMenu meta model)
            menu
        ]

isValidName : Tree -> String -> Bool
isValidName tree name =
    if name == ""
    then False
    else List.all
        (\node ->
            (/=) name <| .name <| getFile node
        )
        tree

viewAddDir : Maybe Id -> Tree -> String -> Html NodeMsg
viewAddDir parent neighbours value =
    isValidName neighbours value
    |> \valid -> div 
        [ class "pane-person-files-node", class "pane-person-files-node-add" ]
        [ div [ class "pane-person-files-node-opener" ] []
        , div [ class "pane-person-files-node-container" ]
            <| List.singleton
            <| div [ class "pane-person-files-node-cell" ]
            <| List.singleton
            <| div [ class "pane-person-files-node-info" ]
                [ div [ class "pane-person-files-node-icon" ]
                    [ Html.img
                        [ HA.src "/img/svgrepo/interaction-set/folder-24770.svg"
                        ] []
                    ]
                , div [ class "pane-person-files-node-name" ]
                    <| List.singleton
                    <| Html.input
                        [ HA.value value
                        , HA.classList
                            [ ("pane-person-files-node-invalid", not valid ) ]
                        , HA.id "Panes-Person-Files-editor"
                        , HE.onInput <| SetEdit << AddDir parent
                        , Tools.onNotShiftEnter <|
                            if valid
                            then PerformAddDir parent value
                            else None
                        ] []
                , div [ class "pane-person-files-node-ok" ]
                    [ Html.img
                        [ HA.src "/img/svgrepo/essential-set-2/success-13679.svg"
                        , HE.onClick  <|
                            if valid
                            then PerformAddDir parent value
                            else None
                        ] []
                    ]
                , div [ class "pane-person-files-node-cancel" ]
                    [ Html.img
                        [ HA.src "/img/svgrepo/essential-set-2/error-138890.svg"
                        , HE.onClick <| SetEdit NoEdit
                        ] []
                    ]
                ]
        ]

viewTree : PaneMeta -> Maybe (DnD.Draggable File File) -> Edit -> Maybe Id -> Bool -> Bool -> Tree
    -> Html NodeMsg
viewTree meta drag edit parent validDropTarget stopClick tree = 
    div [ class "pane-person-files-tree" ]
        <| (\list ->
                case edit of
                    AddDir id value ->
                        if id == parent
                        then list ++ [ viewAddDir id tree value ]
                        else list
                    _ -> list
            )
        <| List.map
            (\node ->
                div [ class "pane-person-files-node" ]
                    [ div [ class "pane-person-files-node-opener" ]
                        [ if hasSubNodes node /= Just False
                            then
                                if getOpen node
                                then Html.img 
                                    [ HA.src "/img/svgrepo/essential-set-2/substract-51833.svg"
                                    , HE.onClick <| Open (getFile node |> .id) False
                                    ] []
                                else Html.img 
                                    [ HA.src "/img/svgrepo/essential-set-2/add-83989.svg"
                                    , HE.onClick <| Open (getFile node |> .id) True
                                    ] []
                            else text ""
                        ]
                    , div [ class "pane-person-files-node-container" ]
                        [ div [ class "pane-person-files-node-cell" ]
                            [ viewNode meta drag tree edit (getOpen node)
                                (validDropTarget
                                    && Maybe.andThen DnD.getDragMeta drag /= Just (getFile node)
                                )
                                stopClick
                                <| getFile node
                            ]
                        , case node of
                            Node { id } True list -> viewTree meta drag edit (Just id) 
                                (validDropTarget
                                    && Maybe.andThen DnD.getDragMeta drag /= Just (getFile node)
                                )
                                stopClick
                                list
                            Node _ False _ -> text ""
                            Load _ -> text ""
                        ]
                    ]
            )
        <| List.filter (getFile >> .deleted >> not)
        <| tree

viewNode : PaneMeta -> Maybe (DnD.Draggable File File) -> Tree -> Edit -> Bool -> Bool -> Bool -> File 
    -> Html NodeMsg
viewNode meta drag neighbours edit open validDropTarget stopClick file =
    (case Maybe.andThen DnD.getDragMeta drag of
        Nothing -> dnd.draggable file
        Just currentDrag ->
            if currentDrag /= file && validDropTarget
            then dnd.droppable file
            else div
    )
    -- div
        [ HA.classList
            [ ("pane-person-files-node-info", True)
            , Tuple.pair "pane-person-files-node-dragged"
                <| (==) (Just file.id)
                <| Maybe.map .id
                <| Maybe.andThen DnD.getDragMeta drag
            , Tuple.pair "pane-person-files-node-target"
                <| (==) (Just file.id)
                <| Maybe.map .id
                <| Maybe.andThen DnD.getDropMeta drag
            ]
        , HE.onClick
            <| (if stopClick then always None else identity)
            <| Maybe.withDefault
                ( if file.mime == "folder"
                    then Open file.id <| not open
                    else SendEvent <| Panes.OpenFile file.id
                )
            <| case edit of
                Rename id _ ->
                    if id == file.id
                    then Just None
                    else Nothing
                _ -> Nothing
        , ContextMenu.open
            WrapContextMenu
            file
        ]
    <| (::)
        (div [ class "pane-person-files-node-icon" ]
                [ Html.img
                    [ HA.src
                        <| Panes.Modal.FileInfo.getFileIcon file.mime
                    ] []
                ]
        )
    <| Maybe.withDefault
        [ div [ class "pane-person-files-node-name" ]
            [ text file.name 
            , if file.confidential
                then Html.img
                    [ HA.src "/img/svgrepo/essential-set-2/locked-22140.svg"
                    , HA.title
                        <| "This entry is in the confidential database stored."
                    ] []
                else text ""
            , if file.deleted
                then  Html.img
                    [ HA.src "/img/svgrepo/essential-set-2/garbage-51844.svg"
                    , HA.title
                        <| "This entry was deleted."
                    ] []
                else text ""
            ]
        , div [ class "pane-person-files-node-changed" ]
            [ Tools.viewDateElement meta.zone meta.now file.modified
            ]
        , div 
            [ class "pane-person-files-node-menu"
            , HA.title "more options"
            , ContextMenu.openClick True WrapContextMenu file
            ] []
        ]
    <| Maybe.map
        (\(id, value, valid) ->
            [ div [ class "pane-person-files-node-name" ]
                <| List.singleton
                <| Html.input
                    [ HA.value value
                    , HA.id "Panes-Person-Files-editor"
                    , HA.classList
                        [ ("pane-person-files-node-invalid", not valid) ]
                    , HE.onInput <| SetEdit << Rename id
                    , Tools.onNotShiftEnter <|
                        if valid
                        then PerformRename id value
                        else None
                    , HE.custom "click" 
                        <| JD.succeed
                            { message = None
                            , stopPropagation = True
                            , preventDefault = False
                            }
                    , HE.custom "mousedown" 
                        <| JD.succeed
                            { message = None
                            , stopPropagation = True
                            , preventDefault = False
                            }
                    ] []
            , div [ class "pane-person-files-node-ok" ]
                [ Html.img
                    [ HA.src "/img/svgrepo/essential-set-2/success-13679.svg"
                    , HE.onClick  <|
                        if valid
                        then PerformRename id value
                        else None
                    ] []
                ]
            , div [ class "pane-person-files-node-cancel" ]
                [ Html.img
                    [ HA.src "/img/svgrepo/essential-set-2/error-138890.svg"
                    , HE.onClick <| SetEdit NoEdit
                    ] []
                ]
            ]
        )
    <| Maybe.map
        (\(id, value) ->
            (id, value, isValidName neighbours value || value == file.name)
        )
    <| case edit of
        Rename id value ->
            if id == file.id then Just (id, value) else Nothing
        _ -> Nothing

loadSingle : Id -> FileTree -> Maybe Panes.Event
loadSingle person node =
    case node of
        Node _ _ _ -> Nothing
        Load n -> 
            if n.mime == "folder"
            then Just
                <| Panes.Send
                <| Network.Messages.FilePersonFetch
                    { person = person
                    , root = Just n.id
                    }
            else Nothing

loadSubNodes : Id -> FileTree -> List Panes.Event
loadSubNodes person node =
    case node of
        Node _ _ list ->
            List.filterMap (loadSingle person) list
        Load n -> 
            if n.mime == "folder"
            then List.singleton
                <| Panes.Send
                <| Network.Messages.FilePersonFetch
                    { person = person
                    , root = Just n.id
                    }
            else []

update : Id -> PaneMeta -> NodeMsg -> Model -> Triple Model (Cmd NodeMsg) (List Event)
update person meta msg model =
    case msg of
        None -> triple model Cmd.none []
        Open id open ->
            triple
                { model
                | tree = openNode open id model.tree
                }
                Cmd.none
            <| Maybe.withDefault []
            <| Maybe.map
                (loadSubNodes person)
            <| getNode id model.tree
        SendEvent event -> triple model Cmd.none [ event ]
        SetEdit edit -> triple
            { model | edit = edit }
            ( if edit /= NoEdit
                then Browser.Dom.focus "Panes-Person-Files-editor"
                    |> Task.attempt (always None)
                else Cmd.none
            )
            []
        PerformRename id name -> triple
            { model
            | tree = transform
                (\node ->
                    case node of
                        Node file open list ->
                            Node { file | name = name, modified = meta.now } open list
                        Load file ->
                            Load { file | name = name, modified = meta.now }
                )
                id
                model.tree
            , edit = NoEdit
            }
            Cmd.none
            [ Panes.Send <| Network.Messages.FileRename id name ]
        PerformAddDir parent name -> triple
            { model | edit = NoEdit }
            ( Http.request
                { method = "PUT"
                , headers = []
                , url = (++) "http://localhost:8015/upload?"
                    <| String.join "&"
                    <| List.map 
                        (\(k, v) -> Url.percentEncode k ++ "=" ++ Url.percentEncode v)
                    <| List.filterMap identity
                        [ Just ("person", person)
                        , Maybe.map (Tuple.pair "parent") parent
                        , Just ("name", name)
                        , Just ("mime", "folder")
                        , Maybe.map (Tuple.pair "token")
                            <| Maybe.map .uploadToken meta.info
                        ]
                , body = Http.emptyBody
                , expect = Http.expectWhatever (always None)
                , timeout = Nothing
                , tracker = Nothing
                }
            )
            []
        WrapContextMenu sub ->
            ContextMenu.update sub model.menu
            |> Tuple.mapBoth
                (\x -> { model | menu = x })
                (Cmd.map WrapContextMenu)
            |> Triple.insertThird []
        WrapDragDrop sub -> 
            let
                newDrag = DnD.update sub model.draggable

                moving = DnD.getDragMeta newDrag /= Nothing
                    && DnD.getDragMeta model.draggable /= Nothing
                
                stopClick = moving && (model.stopClick || DnD.getDropMeta model.draggable /= Nothing)

                drop : Maybe (Id, Maybe Id)
                drop = Debug.log "drag"
                    <| Maybe.map
                        (Tuple.mapBoth
                            .id
                        <| \file -> if file.mime == "folder" then Just file.id else file.parent
                        )
                    <| if DnD.getDragMeta newDrag == Nothing
                        then Maybe.map2 Tuple.pair
                            (DnD.getDragMeta model.draggable)
                            (DnD.getDropMeta model.draggable)
                        else Nothing
            
            in triple
                { model | draggable = newDrag, moving = moving, stopClick = stopClick }
                Cmd.none
                (case drop of
                    Nothing -> []
                    Just (id, parent) -> [ Panes.Send <| Network.Messages.FileMove id parent ]
                )

apply : Id -> Network.Messages.ReceiveMessage -> Model -> Triple Model (Cmd NodeMsg) (List Event)
apply person msg model =
    case msg of
        Network.Messages.FileSend _ (Just file) ->
            triple
                { model
                | tree = transformOrAppend
                    (\node ->
                        case node of
                            Node _ open list ->
                                Node file open list
                            Load _ -> Load file
                    )
                    (\() -> Node file False [])
                    file.parent
                    file.id
                    model.tree
                }
                Cmd.none
                []
        Network.Messages.FileUpdated file ->
            triple
                { model
                | tree = transformOrAppend
                    (\node ->
                        case node of
                            Node _ open list ->
                                Node file open list
                            Load _ -> Load file
                    )
                    (\() -> Node file False [])
                    file.parent
                    file.id
                    model.tree
                }
                Cmd.none
                []
        Network.Messages.FileMoved file ->
            if file.person == person
            then case getNode file.id model.tree of
                Nothing -> triple
                    { model
                    | tree = transformOrAppend
                        (\node ->
                            case node of
                                Node _ open list ->
                                    Node file open list
                                Load _ -> Load file
                        )
                        (\() -> Node file False [])
                        file.parent
                        file.id
                        model.tree
                    }
                    Cmd.none
                    []
                Just node -> triple
                    { model
                    | tree = transformOrAppend
                        (always node)
                        (always node)
                        file.parent
                        file.id
                        <| remove file.id model.tree
                    }
                    Cmd.none
                    []
            else triple model Cmd.none []
        _ -> triple model Cmd.none []

subscription : Model -> Sub NodeMsg
subscription model =
    Sub.batch
        [ Sub.map WrapContextMenu
            <| ContextMenu.subscriptions model.menu
        , dnd.subscriptions model.draggable
        ]
