;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC

(ns frontend-tests.helpers.pages
  (:require
   [app.common.data :as d]
   [app.common.files.changes :as cp]
   [app.common.files.changes-builder :as pcb]
   [app.common.files.helpers :as cfh]
   [app.common.files.shapes-helpers :as cfsh]
   [app.common.geom.point :as gpt]
   [app.common.geom.shapes :as gsh]
   [app.common.logic.libraries :as cll]
   [app.common.types.component :as ctk]
   [app.common.types.container :as ctn]
   [app.common.types.file :as ctf]
   [app.common.types.shape :as cts]
   [app.common.types.shape-tree :as ctst]
   [app.common.uuid :as uuid]
   [app.main.data.workspace.groups :as dwg]
   [app.main.data.workspace.layout :as layout]
   [app.main.data.workspace.state-helpers :as wsh]))

;; ---- Helpers to manage pages and objects

(def current-file-id (uuid/next))

(def initial-state
  {:current-file-id current-file-id
   :current-page-id nil
   :workspace-layout layout/default-layout
   :workspace-global layout/default-global
   :workspace-data {:id current-file-id
                    :options {:components-v2 true}
                    :components {}
                    :pages []
                    :pages-index {}}
   :workspace-libraries {}
   :features/team #{"components/v2"}})

(def ^:private idmap (atom {}))

(defn reset-idmap! []
  (reset! idmap {}))

(defn current-page
  [state]
  (let [page-id (:current-page-id state)]
    (get-in state [:workspace-data :pages-index page-id])))

(defn id
  [label]
  (get @idmap label))

(defn get-shape
  [state label]
  (let [page (current-page state)]
    (get-in page [:objects (id label)])))

(defn get-children
  [state label]
  (let [page (current-page state)]
    (cfh/get-children (:objects page) (id label))))

(defn sample-page
  ([state] (sample-page state {}))
  ([state {:keys [id name] :as props
           :or {id (uuid/next)
                name "page1"}}]

   (swap! idmap assoc :page id)
   (-> state
       (assoc :current-page-id id)
       (update :workspace-data
               cp/process-changes
               [{:type :add-page
                 :id id
                 :name name}]))))

(defn sample-shape
  ([state label type] (sample-shape state type {}))
  ([state label type props]
   (let [page  (current-page state)
         frame (cfh/get-frame (:objects page))
         shape (cts/setup-shape (merge {:type type :x 0 :y 0 :width 1 :height 1} props))]
     (swap! idmap assoc label (:id shape))
     (update state :workspace-data
             cp/process-changes
             [{:type :add-obj
               :id (:id shape)
               :page-id (:id page)
               :frame-id (:id frame)
               :obj shape}]))))

(defn group-shapes
  ([state label ids] (group-shapes state label ids "Group"))
  ([state label ids prefix]
   (let [page  (current-page state)
         shapes (dwg/shapes-for-grouping (:objects page) ids)]
     (if (empty? shapes)
       state
       (let [[group changes]
             (dwg/prepare-create-group (pcb/empty-changes) nil (:objects page) (:id page) shapes prefix true)]

         (swap! idmap assoc label (:id group))
         (update state :workspace-data
                 cp/process-changes (:redo-changes changes)))))))

(defn frame-shapes
  ([state label ids] (frame-shapes state label ids "Board"))
  ([state label ids frame-name]
   (let [page    (current-page state)
         shapes  (dwg/shapes-for-grouping (:objects page) ids)
         changes (pcb/empty-changes nil (:id page))]
     (if (empty? shapes)
       state
       (let [[frame changes]
             (cfsh/prepare-create-artboard-from-selection changes
                                                          nil
                                                          nil
                                                          (:objects page)
                                                          (map :id shapes)
                                                          nil
                                                          frame-name
                                                          true)]

         (swap! idmap assoc label (:id frame))
         (update state :workspace-data
                 cp/process-changes (:redo-changes changes)))))))

(defn make-component
  [state instance-label component-label shape-ids]
  (let [page    (current-page state)
        objects (wsh/lookup-page-objects state (:id page))
        shapes  (dwg/shapes-for-grouping objects shape-ids)

        [group component-id changes]
        (cll/generate-add-component (pcb/empty-changes nil)
                                    shapes
                                    (:objects page)
                                    (:id page)
                                    current-file-id
                                    true
                                    dwg/prepare-create-group
                                    cfsh/prepare-create-artboard-from-selection)]

    (swap! idmap assoc instance-label (:id group)
           component-label component-id)
    (update state :workspace-data
            cp/process-changes (:redo-changes changes))))

(defn instantiate-component
  ([state label component-id]
   (instantiate-component state label component-id current-file-id))
  ([state label component-id file-id]
   (let [page      (current-page state)
         libraries (wsh/get-libraries state)
         objects (:objects page)

         changes   (-> (pcb/empty-changes nil (:id page))
                       (pcb/with-objects objects))

         [new-shape changes]
         (cll/generate-instantiate-component changes
                                             objects
                                             file-id
                                             component-id
                                             (gpt/point 100 100)
                                             page
                                             libraries)]

     (swap! idmap assoc label (:id new-shape))
     (update state :workspace-data
             cp/process-changes (:redo-changes changes)))))

(defn move-to-library
  [state label name]
  (let [library-id (uuid/next)
        data       (get state :workspace-data)]
    (swap! idmap assoc label library-id)
    (-> state
        (update :workspace-libraries
                assoc library-id {:id library-id
                                  :name name
                                  :data {:id library-id
                                         :options (:options data)
                                         :pages (:pages data)
                                         :pages-index (:pages-index data)
                                         :components (:components data)}})
        (update :workspace-data
                assoc :components {} :pages [] :pages-index {}))))


(defn simulate-copy-shape
  [selected objects libraries page file features version]
  (letfn [(sort-selected [data]
            (let [;; Narrow the objects map so it contains only relevant data for
                  ;; selected and its parents
                  objects  (cfh/selected-subtree objects selected)
                  selected (->> (ctst/sort-z-index objects selected)
                                (reverse)
                                (into (d/ordered-set)))]

              (assoc data :selected selected)))

          ;; Prepare the shape object.
          (prepare-object [objects parent-frame-id obj]
            (maybe-translate obj objects parent-frame-id))

          ;; Collects all the items together and split images into a
          ;; separated data structure for a more easy paste process.
          (collect-data [result {:keys [id ::images] :as item}]
            (cond-> result
              :always
              (update :objects assoc id (dissoc item ::images))

              (some? images)
              (update :images into images)))

          (maybe-translate [shape objects parent-frame-id]
            (if (= parent-frame-id uuid/zero)
              shape
              (let [frame (get objects parent-frame-id)]
                (gsh/translate-to-frame shape frame))))

          ;; When copying an instance that is nested inside another one, we need to
          ;; advance the shape refs to one or more levels of remote mains.
          (advance-copies [data]
            (let [heads     (mapcat #(ctn/get-child-heads (:objects data) %) selected)]
              (update data :objects
                      #(reduce (partial advance-copy file libraries page)
                               %
                               heads))))

          (advance-copy [file libraries page objects shape]
            (if (and (ctk/instance-head? shape) (not (ctk/main-instance? shape)))
              (let [level-delta (ctn/get-nesting-level-delta (:objects page) shape uuid/zero)]
                (if (pos? level-delta)
                  (reduce (partial advance-shape file libraries page level-delta)
                          objects
                          (cfh/get-children-with-self objects (:id shape)))
                  objects))
              objects))

          (advance-shape [file libraries page level-delta objects shape]
            (let [new-shape-ref (ctf/advance-shape-ref file page libraries shape level-delta {:include-deleted? true})]
              (cond-> objects
                (and (some? new-shape-ref) (not= new-shape-ref (:shape-ref shape)))
                (assoc-in [(:id shape) :shape-ref] new-shape-ref))))]

    (let [file-id  (:id file)
          frame-id (cfh/common-parent-frame objects selected)

          initial  {:type :copied-shapes
                    :features features
                    :version version
                    :file-id file-id
                    :selected selected
                    :objects {}
                    :images #{}
                    :in-viewport false}

          shapes   (->> (cfh/selected-with-children objects selected)
                        (keep (d/getf objects)))]

      (->> shapes
           (map (partial prepare-object objects frame-id))
           (reduce collect-data initial)
           sort-selected
           advance-copies))))
