;; 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 Sucursal en España SL
(ns frontend-tests.logic.pasting-in-containers-test
  (:require
   [app.common.test-helpers.components :as cthc]
   [app.common.test-helpers.compositions :as ctho]
   [app.common.test-helpers.files :as cthf]
   [app.common.test-helpers.ids-map :as cthi]
   [app.common.test-helpers.shapes :as cths]
   [app.common.test-helpers.variants :as thv]
   [app.common.types.component :as ctk]
   [app.common.uuid :as uuid]
   [app.main.data.workspace :as dw]
   [app.main.data.workspace.selection :as dws]
   [cljs.test :as t :include-macros true]
   [cuerdas.core :as str]
   [frontend-tests.helpers.pages :as thp]
   [frontend-tests.helpers.state :as ths]))

(t/use-fixtures :each
  {:before thp/reset-idmap!})

;; Related .penpot file: common/test/cases/remove-swap-slots.penpot
(defn- setup-file
  []
  ;; {:frame-red} [:name Frame1]                           # [Component :red]
  ;; {:frame-blue} [:name Frame1]                          # [Component :blue]
  ;; {:frame-green} [:name Frame1]                         # [Component :green]
  ;;     :red-copy-green [:name Frame1]                    @--> :frame-red
  ;; {:frame-b1} [:name Frame1]                            # [Component :b1]
  ;;     :blue1 [:name Frame1, :swap-slot-label :red-copy] @--> :frame-blue
  ;;     :frame-yellow [:name Frame1]
  ;;     :green-copy [:name Frame1]                        @--> :frame-green
  ;;         :blue-copy-in-green-copy [:name Frame1, :swap-slot-label :red-copy-green] @--> :frame-blue
  ;; {:frame-b2} [:name Frame1]                            # [Component :b2]
  (-> (cthf/sample-file :file1)
      (ctho/add-frame :frame-red {:name "frame-red"})
      (ctho/add-frame :frame-red {:name "frame-red"})
      (ctho/add-frame :frame-blue :name "frame-blue")
      (cthc/make-component :blue :frame-blue)
      (ctho/add-frame :frame-green :name "frame-green")
      (cthc/make-component :green :frame-green)
      (cthc/instantiate-component :red :red-copy-green :parent-label :frame-green)
      (ctho/add-frame :frame-b1)
      (cthc/make-component :b1 :frame-b1)
      (ctho/add-frame :frame-yellow :parent-label :frame-b1 :name "frame-yellow")
      (cthc/instantiate-component :red :red-copy :parent-label :frame-b1)
      (cthc/component-swap :red-copy :blue :blue1)
      (cthc/instantiate-component :green :green-copy :parent-label :frame-b1 :children-labels [:red-copy-in-green-copy])
      (cthc/component-swap :red-copy-in-green-copy :blue :blue-copy-in-green-copy)
      (ctho/add-frame :frame-b2)
      (cthc/make-component :b2 :frame-b2)))

(defn- find-copied-shape
  ([original-shape page parent-id]
   (find-copied-shape original-shape page parent-id false))
  ([original-shape page parent-id ignore-label?]
   ;; copied shape has the same name, is in the specified parent, and doesn't have a label
   ;; for restored components we can ignore the label part
   (->> (vals (:objects page))
        (filter #(and (= (:name %) (:name original-shape))
                      (= (:parent-id %) parent-id)
                      (or ignore-label?  (str/starts-with? (cthi/label (:id %)) "<no-label"))))
        first)))

(t/deftest copy-shape-to-frame
  "Coping a rect into a frame results in a copy of the rect inside the frame"
  (t/async
    done
    (let [;; ==== Setup
          file     (-> (cthf/sample-file :file1)
                       (ctho/add-frame :frame-red {:name "frame-red"})
                       (ctho/add-frame :frame-blue {:name "frame-blue"}))
          store    (ths/setup-store file)

          ;; ==== Action
          page       (cthf/current-page file)
          frame-red  (cths/get-shape file :frame-red)
          frame-blue (cths/get-shape file :frame-blue)
          features   #{}
          version    67

          pdata      (thp/simulate-copy-shape #{(:id frame-blue)} (:objects page) {(:id  file) file} page file features version)

          events
          [(dws/select-shape (:id frame-red))
           (dw/paste-shapes pdata)]]

      (ths/run-store
       store done events
       (fn [new-state]
         (let [;; ==== Get
               file'         (ths/get-file-from-state new-state)
               page'         (cthf/current-page file')
               frame-red'    (cths/get-shape file' :frame-red)
               frame-blue'   (cths/get-shape file' :frame-blue)
               copied-blue1' (find-copied-shape frame-blue' page' (:id frame-red'))]

           ;; ==== Check
           (t/is (= (:parent-id copied-blue1') (:id frame-red')))))))))

(t/deftest copy-shape-to-component
  "Coping a rect into a component results in a copy of the rect inside the component"
  (t/async
    done
    (let [;; ==== Setup
          file     (-> (cthf/sample-file :file1)
                       (ctho/add-frame :frame-red {:name "frame-red"})
                       (cthc/make-component :red :frame-red)
                       (ctho/add-frame :frame-blue {:name "frame-blue"}))
          store    (ths/setup-store file)

          ;; ==== Action
          page       (cthf/current-page file)
          frame-red  (cths/get-shape file :frame-red)
          frame-blue (cths/get-shape file :frame-blue)
          features   #{}
          version    67

          pdata      (thp/simulate-copy-shape #{(:id frame-blue)} (:objects page) {(:id  file) file} page file features version)

          events
          [(dws/select-shape (:id frame-red))
           (dw/paste-shapes pdata)]]

      (ths/run-store
       store done events
       (fn [new-state]
         (let [;; ==== Get
               file'         (ths/get-file-from-state new-state)
               page'         (cthf/current-page file')
               frame-red'    (cths/get-shape file' :frame-red)
               frame-blue'   (cths/get-shape file' :frame-blue)
               copied-blue1' (find-copied-shape frame-blue' page' (:id frame-red'))]

           ;; ==== Check
           (t/is (= (:parent-id copied-blue1') (:id frame-red')))))))))

(t/deftest copy-component-to-frame
  "Coping a component c1 into a frame results in a copy of c1 inside the frame"
  (t/async
    done
    (let [;; ==== Setup
          file     (-> (cthf/sample-file :file1)
                       (ctho/add-frame :frame-red {:name "frame-red"})
                       (ctho/add-frame :frame-blue {:name "frame-blue"})
                       (cthc/make-component :blue :frame-blue))
          store    (ths/setup-store file)

          ;; ==== Action
          page       (cthf/current-page file)
          frame-red  (cths/get-shape file :frame-red)
          frame-blue (cths/get-shape file :frame-blue)
          features   #{}
          version    67

          pdata      (thp/simulate-copy-shape #{(:id frame-blue)} (:objects page) {(:id  file) file} page file features version)

          events
          [(dws/select-shape (:id frame-red))
           (dw/paste-shapes pdata)]]

      (ths/run-store
       store done events
       (fn [new-state]
         (let [;; ==== Get
               file'         (ths/get-file-from-state new-state)
               page'         (cthf/current-page file')
               frame-red'    (cths/get-shape file' :frame-red)
               frame-blue'   (cths/get-shape file' :frame-blue)
               copied-blue1' (find-copied-shape frame-blue' page' (:id frame-red'))]

           ;; ==== Check
           (t/is (= (:parent-id copied-blue1') (:id frame-red')))))))))

(t/deftest copy-component-to-component
  "Coping a component c1 into the main of a component c2 results in a copy of c1 inside c2"
  (t/async
    done
    (let [;; ==== Setup
          file     (-> (cthf/sample-file :file1)
                       (ctho/add-frame :frame-red {:name "frame-red"})
                       (cthc/make-component :red :frame-red)
                       (ctho/add-frame :frame-blue {:name "frame-blue"})
                       (cthc/make-component :blue :frame-blue))
          store    (ths/setup-store file)

          ;; ==== Action
          page       (cthf/current-page file)
          frame-red  (cths/get-shape file :frame-red)
          frame-blue (cths/get-shape file :frame-blue)
          features   #{}
          version    67

          pdata      (thp/simulate-copy-shape #{(:id frame-blue)} (:objects page) {(:id  file) file} page file features version)

          events
          [(dws/select-shape (:id frame-red))
           (dw/paste-shapes pdata)]]

      (ths/run-store
       store done events
       (fn [new-state]
         (let [;; ==== Get
               file'         (ths/get-file-from-state new-state)
               page'         (cthf/current-page file')
               frame-red'    (cths/get-shape file' :frame-red)
               frame-blue'   (cths/get-shape file' :frame-blue)
               copied-blue1' (find-copied-shape frame-blue' page' (:id frame-red'))]

           ;; ==== Check
           (t/is (not (ctk/main-instance? copied-blue1')))
           (t/is (= (:parent-id copied-blue1') (:id frame-red')))))))))

(t/deftest cut-paste-component-to-component
  "Cutting a component c1 and pasting it into the main of a component c2 results in a restored main of C1 on root,
   because its not allowed to have a main inside a main"
  (t/async
    done
    (let [;; ==== Setup
          file     (-> (cthf/sample-file :file1)
                       (ctho/add-frame :frame-red {:name "frame-red"})
                       (cthc/make-component :red :frame-red)
                       (ctho/add-frame :frame-blue {:name "frame-blue"})
                       (cthc/make-component :blue :frame-blue))
          store    (ths/setup-store file)

          ;; ==== Action
          page         (cthf/current-page file)
          frame-red    (cths/get-shape file :frame-red)
          frame-blue   (cths/get-shape file :frame-blue)
          features     #{}
          version      67

          pdata        (thp/simulate-copy-shape #{(:id frame-blue)} (:objects page) {(:id  file) file} page file features version)

          events
          [(dws/select-shape (:id frame-blue))
           (dw/delete-selected)
           (dws/select-shape (:id frame-red))
           (dw/paste-shapes pdata)]]

      (ths/run-store
       store done events
       (fn [new-state]
         (let [;; ==== Get
               file'         (ths/get-file-from-state new-state)
               page'         (cthf/current-page file')
               frame-red'    (cths/get-shape file' :frame-red)
               frame-blue'   (cths/get-shape file' :frame-blue)
               copied-blue1' (find-copied-shape frame-blue' page' (:id frame-red'))
               copied-blue2' (find-copied-shape frame-blue' page' uuid/zero true)]

           ;; ==== Check
           (t/is (nil? copied-blue1'))
           (t/is (ctk/main-instance? copied-blue2'))
           (t/is (= (:parent-id copied-blue2') uuid/zero))))))))

(t/deftest copy-variant-container-into-component
  "Coping a variant container into the main of a component results in a new variant-container on root,
   because its not allowed to have a variant container inside a main"
  (t/async
    done
    (let [;; ==== Setup
          file     (-> (cthf/sample-file :file1)
                       (ctho/add-frame :frame-red {:name "frame-red"})
                       (cthc/make-component :red :frame-red)
                       (thv/add-variant :v01 :c01 :m01 :c02 :m02))

          store    (ths/setup-store file)

          ;; ==== Action
          page         (cthf/current-page file)
          root         (cths/get-shape-by-id file uuid/zero)
          frame-red    (cths/get-shape file :frame-red)
          v01          (cths/get-shape file :v01)

          features     #{}
          version      67

          pdata        (thp/simulate-copy-shape #{(:id v01)} (:objects page) {(:id  file) file} page file features version)

          events
          [(dws/select-shape (:id frame-red))
           (dw/paste-shapes pdata)]]

      (ths/run-store
       store done events
       (fn [new-state]
         (let [;; ==== Get
               file'          (ths/get-file-from-state new-state)
               root'          (cths/get-shape-by-id file' uuid/zero)
               frame-red'     (cths/get-shape file' :frame-red)
               root-children' (->> (:shapes root')
                                   (map #(cths/get-shape-by-id file' %)))]

           ;; ==== Check
           ;; The main shape of the component have no children
           (t/is (= 0 (count (:shapes frame-red'))))
           ;; Root had two children, now have 3
           (t/is (= 2 (count (:shapes root))))
           (t/is (= 3 (count (:shapes root'))))
           ;; Two of the children of root are variant-containers
           (t/is (= 2 (count (filter ctk/is-variant-container? root-children'))))))))))

(t/deftest cut-paste-variant-container-into-component
  "Cuting and pasting a variant container into the main of a component results in a new variant-container on root,
   because its not allowed to have a variant container inside a main"
  (t/async
    done
    (let [;; ==== Setup
          file     (-> (cthf/sample-file :file1)
                       (ctho/add-frame :frame-red {:name "frame-red"})
                       (cthc/make-component :red :frame-red)
                       (thv/add-variant :v01 :c01 :m01 :c02 :m02))

          store    (ths/setup-store file)

          ;; ==== Action
          page         (cthf/current-page file)
          root         (cths/get-shape-by-id file uuid/zero)
          frame-red    (cths/get-shape file :frame-red)
          v01          (cths/get-shape file :v01)

          features     #{}
          version      67

          pdata        (thp/simulate-copy-shape #{(:id v01)} (:objects page) {(:id  file) file} page file features version)

          events
          [(dws/select-shape (:id v01))
           (dw/delete-selected)
           (dws/select-shape (:id frame-red))
           (dw/paste-shapes pdata)]]

      (ths/run-store
       store done events
       (fn [new-state]
         (let [;; ==== Get
               file'          (ths/get-file-from-state new-state)
               root'          (cths/get-shape-by-id file' uuid/zero)
               frame-red'     (cths/get-shape file' :frame-red)
               root-children' (->> (:shapes root')
                                   (map #(cths/get-shape-by-id file' %)))]

           ;; ==== Check
           ;; The main shape of the component have no children
           (t/is (= 0 (count (:shapes frame-red'))))
           ;; Root had two children, now it still have two (because we have cutted one of them, and then created a new one)
           (t/is (= 2 (count (:shapes root))))
           (t/is (= 2 (count (:shapes root'))))
           ;; One of the children of root is a variant-container
           (t/is (= 1 (count (filter ctk/is-variant-container? root-children'))))))))))

(t/deftest copy-variant-into-different-variant-container
  "Coping a variant into a different variant-container creates a new variant inside that container"
  (t/async
    done
    (let [;; ==== Setup
          file     (-> (cthf/sample-file :file1)
                       (thv/add-variant :v01 :c01 :m01 :c02 :m02)
                       (thv/add-variant :v02 :c03 :m03 :c04 :m04))

          store    (ths/setup-store file)

          ;; ==== Action
          page         (cthf/current-page file)
          m01          (cths/get-shape file :m01)
          v02          (cths/get-shape file :v02)
          components   (cthc/get-components file)

          features     #{}
          version      67

          pdata        (thp/simulate-copy-shape #{(:id m01)} (:objects page) {(:id  file) file} page file features version)

          events
          [(dws/select-shape (:id v02))
           (dw/paste-shapes pdata)]]

      (ths/run-store
       store done events
       (fn [new-state]
         (let [;; ==== Get
               file'          (ths/get-file-from-state new-state)
               v02'           (cths/get-shape file' :v02)
               components'    (cthc/get-components file')]

           ;; ==== Check
           ;; v02 had two children, now it have 3
           (t/is (= 2 (count (:shapes v02))))
           (t/is (= 3 (count (:shapes v02'))))

           ;;There was 4 components, now there are 5
           (t/is (= 4 (count components)))
           (t/is (= 5 (count components')))))))))

(t/deftest copy-variant-into-variant-another-container
  "Coping a variant into a variant of a different variant-container creates a copy of the variant inside"
  (t/async
    done
    (let [;; ==== Setup
          file     (-> (cthf/sample-file :file1)
                       (thv/add-variant :v01 :c01 :m01 :c02 :m02)
                       (thv/add-variant :v02 :c03 :m03 :c04 :m04))

          store    (ths/setup-store file)

          ;; ==== Action
          page         (cthf/current-page file)
          m01          (cths/get-shape file :m01)
          v02          (cths/get-shape file :v02)
          m03          (cths/get-shape file :m03)
          components   (cthc/get-components file)

          features     #{}
          version      67

          pdata        (thp/simulate-copy-shape #{(:id m01)} (:objects page) {(:id  file) file} page file features version)

          events
          [(dws/select-shape (:id m03))
           (dw/paste-shapes pdata)]]

      (ths/run-store
       store done events
       (fn [new-state]
         (let [;; ==== Get
               file'          (ths/get-file-from-state new-state)
               v02'           (cths/get-shape file' :v02)
               m03'           (cths/get-shape file' :m03)
               components'    (cthc/get-components file')]

           ;; ==== Check
           ;; v02 had two children, now it have still 2
           (t/is (= 2 (count (:shapes v02))))
           (t/is (= 2 (count (:shapes v02'))))

           ;; m03 had no children, now it have 1
           (t/is (= 0 (count (:shapes m03))))
           (t/is (= 1 (count (:shapes m03'))))

           ;;There was 4 components, now there is still 4
           (t/is (= 4 (count components)))
           (t/is (= 4 (count components')))))))))

(t/deftest copy-variant-into-its-variant-container
  "Coping a variant into its own variant-container creates a new variant inside that container"
  (t/async
    done
    (let [;; ==== Setup
          file     (-> (cthf/sample-file :file1)
                       (thv/add-variant :v01 :c01 :m01 :c02 :m02))

          store    (ths/setup-store file)

          ;; ==== Action
          page         (cthf/current-page file)
          m01          (cths/get-shape file :m01)
          v01          (cths/get-shape file :v01)
          components   (cthc/get-components file)

          features     #{}
          version      67

          pdata        (thp/simulate-copy-shape #{(:id m01)} (:objects page) {(:id  file) file} page file features version)

          events
          [(dws/select-shape (:id v01))
           (dw/paste-shapes pdata)]]

      (ths/run-store
       store done events
       (fn [new-state]
         (let [;; ==== Get
               file'          (ths/get-file-from-state new-state)
               v01'           (cths/get-shape file' :v01)
               components'    (cthc/get-components file')]

           ;; ==== Check
           ;; v01 had two children, now it have 3
           (t/is (= 2 (count (:shapes v01))))
           (t/is (= 3 (count (:shapes v01'))))

           ;;There was 2 components, now there are 3
           (t/is (= 2 (count components)))
           (t/is (= 3 (count components')))))))))

(t/deftest copy-variant-into-itself
  "Coping a variant into itself creates a new variant inside its container"
  (t/async
    done
    (let [;; ==== Setup
          file     (-> (cthf/sample-file :file1)
                       (thv/add-variant :v01 :c01 :m01 :c02 :m02))

          store    (ths/setup-store file)

          ;; ==== Action
          page         (cthf/current-page file)
          m01          (cths/get-shape file :m01)
          v01          (cths/get-shape file :v01)
          components   (cthc/get-components file)

          features     #{}
          version      67

          pdata        (thp/simulate-copy-shape #{(:id m01)} (:objects page) {(:id  file) file} page file features version)

          events
          [(dws/select-shape (:id m01))
           (dw/paste-shapes pdata)]]

      (ths/run-store
       store done events
       (fn [new-state]
         (let [;; ==== Get
               file'          (ths/get-file-from-state new-state)
               v01'           (cths/get-shape file' :v01)
               components'    (cthc/get-components file')]

           ;; ==== Check
           ;; v01 had two children, now it have 3
           (t/is (= 2 (count (:shapes v01))))
           (t/is (= 3 (count (:shapes v01'))))

           ;;There was 2 components, now there are 3
           (t/is (= 2 (count components)))
           (t/is (= 3 (count components')))))))))

(t/deftest copy-variant-into-a-brother
  "Coping a variant into a brother variant creates a new variant inside its container"
  (t/async
    done
    (let [;; ==== Setup
          file     (-> (cthf/sample-file :file1)
                       (thv/add-variant :v01 :c01 :m01 :c02 :m02))

          store    (ths/setup-store file)

          ;; ==== Action
          page         (cthf/current-page file)
          m01          (cths/get-shape file :m01)
          m02          (cths/get-shape file :m02)
          v01          (cths/get-shape file :v01)
          components   (cthc/get-components file)

          features     #{}
          version      67

          pdata        (thp/simulate-copy-shape #{(:id m01)} (:objects page) {(:id  file) file} page file features version)

          events
          [(dws/select-shape (:id m02))
           (dw/paste-shapes pdata)]]

      (ths/run-store
       store done events
       (fn [new-state]
         (let [;; ==== Get
               file'          (ths/get-file-from-state new-state)
               v01'           (cths/get-shape file' :v01)
               components'    (cthc/get-components file')]

           ;; ==== Check
           ;; v01 had two children, now it have 3
           (t/is (= 2 (count (:shapes v01))))
           (t/is (= 3 (count (:shapes v01'))))

           ;;There was 2 components, now there are 3
           (t/is (= 2 (count components)))
           (t/is (= 3 (count components')))))))))

(t/deftest copy-component-into-a-variant-container
  "Coping a component into a variant-container creates a new variant inside that container"
  (t/async
    done
    (let [;; ==== Setup
          file     (-> (cthf/sample-file :file1)
                       (ctho/add-frame :frame-red {:name "frame-red"})
                       (cthc/make-component :red :frame-red)
                       (thv/add-variant :v01 :c01 :m01 :c02 :m02))

          store    (ths/setup-store file)

          ;; ==== Action
          page         (cthf/current-page file)
          frame-red    (cths/get-shape file :frame-red)
          v01          (cths/get-shape file :v01)
          components   (cthc/get-components file)

          features     #{}
          version      67

          pdata        (thp/simulate-copy-shape #{(:id frame-red)} (:objects page) {(:id  file) file} page file features version)

          events
          [(dws/select-shape (:id v01))
           (dw/paste-shapes pdata)]]

      (ths/run-store
       store done events
       (fn [new-state]
         (let [;; ==== Get
               file'          (ths/get-file-from-state new-state)
               v01'           (cths/get-shape file' :v01)
               components'    (cthc/get-components file')]

           ;; ==== Check
           ;; v01 had two children, now it have 3
           (t/is (= 2 (count (:shapes v01))))
           (t/is (= 3 (count (:shapes v01'))))

           ;;There was 3 components, now there are 4
           (t/is (= 3 (count components)))
           (t/is (= 4 (count components')))))))))

(t/deftest copy-component-into-a-variant
  "Coping a component into a variant creates a copy of the component inside the variant"
  (t/async
    done
    (let [;; ==== Setup
          file     (-> (cthf/sample-file :file1)
                       (ctho/add-frame :frame-red {:name "frame-red"})
                       (cthc/make-component :red :frame-red)
                       (thv/add-variant :v01 :c01 :m01 :c02 :m02))

          store    (ths/setup-store file)

          ;; ==== Action
          page         (cthf/current-page file)
          frame-red    (cths/get-shape file :frame-red)
          v01          (cths/get-shape file :v01)
          m01          (cths/get-shape file :m01)
          components   (cthc/get-components file)

          features     #{}
          version      67

          pdata        (thp/simulate-copy-shape #{(:id frame-red)} (:objects page) {(:id  file) file} page file features version)

          events
          [(dws/select-shape (:id m01))
           (dw/paste-shapes pdata)]]

      (ths/run-store
       store done events
       (fn [new-state]
         (let [;; ==== Get
               file'          (ths/get-file-from-state new-state)
               v01'           (cths/get-shape file' :v01)
               m01'           (cths/get-shape file' :m01)
               components'    (cthc/get-components file')]

           ;; ==== Check
           ;; v01 had two children, now it have still 2
           (t/is (= 2 (count (:shapes v01))))
           (t/is (= 2 (count (:shapes v01'))))

           ;; m01 had no children, now it have 1
           (t/is (= 0 (count (:shapes m01))))
           (t/is (= 1 (count (:shapes m01'))))

           ;;There was 3 components, now there are still 3
           (t/is (= 3 (count components)))
           (t/is (= 3 (count components')))))))))