;; 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.copying-and-duplicating-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.types.component :as ctk]
   [app.common.uuid :as uuid]
   [app.main.data.workspace :as dw]
   [app.main.data.workspace.colors :as dc]
   [app.main.data.workspace.libraries :as dwl]
   [app.main.data.workspace.pages :as dwp]
   [app.main.data.workspace.selection :as dws]
   [cljs.test :as t :include-macros true]
   [frontend-tests.helpers.mock :as mock]
   [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/copying-and-duplicating.penpot
(defn- setup-file []
  (-> (cthf/sample-file :file1 :page-label :page-1)
      (ctho/add-simple-component :simple-1 :frame-simple-1 :rect-simple-1 :child-params {:type :rect :fills (cths/sample-fills-color :fill-color "#2152e5") :name "rect-simple-1"})

      (ctho/add-frame :frame-composed-1 :name "frame-composed-1")
      (cthc/instantiate-component :simple-1 :copy-simple-1 :parent-label :frame-composed-1 :children-labels [:composed-1-simple-1])
      (cths/add-sample-shape :rect-composed-1 :parent-label :frame-composed-1 :fills (cths/sample-fills-color :fill-color "#B1B2B5"))
      (cthc/make-component :composed-1 :frame-composed-1)

      (ctho/add-frame :frame-composed-2 :name "frame-composed-2")
      (cthc/instantiate-component :composed-1 :copy-composed-1-composed-2 :parent-label :frame-composed-2 :children-labels [:composed-1-composed-2])
      (cthc/make-component :composed-2 :frame-composed-2)

      (cthc/instantiate-component :composed-2 :copy-composed-2)

      (ctho/add-frame :frame-composed-3 :name "frame-composed-3")
      (ctho/add-group :group-3 :parent-label :frame-composed-3)
      (cthc/instantiate-component :composed-2 :copy-composed-1-composed-3 :parent-label :group-3 :children-labels [:composed-1-composed-2])
      (cths/add-sample-shape :circle-composed-3 :parent-label :group-3 :fills (cths/sample-fills-color :fill-color "#B1B2B5"))
      (cthc/make-component :composed-3 :frame-composed-3)

      (cthc/instantiate-component :composed-3 :copy-composed-3 :children-labels [:composed-2-composed-3])
      (cthf/add-sample-page :page-2)
      (cthf/switch-to-page :page-1)))


(defn- copy-paste-shape
  [id file & {:keys [target-page-label target-container-id]}]
  (let [features            #{"components/v2"}
        version             46
        page                (cthf/current-page file)
        target-page-id      (cthi/id target-page-label)
        shape               (if (keyword? id)
                              (cths/get-shape file id)
                              (cths/get-shape-by-id file id))
        pdata               (thp/simulate-copy-shape #{(:id shape)} (:objects page) {(:id  file) file} page file features version)
        target-container-id (or target-container-id (:parent-id shape))]

    (filter some?
            [(when target-page-id (dw/initialize-page (:id file) target-page-id))
             (dws/select-shape target-container-id)
             (dw/paste-shapes pdata)
             (when target-page-id (dw/initialize-page (:id file) (:id page)))])))

(defn- sync-file [file]
  (map (fn [component-tag]
         (->> component-tag
              (cthc/get-component file)
              :component-id
              (dwl/sync-file (:id file) (:id file) :components)))
       [:simple-1 :composed-1 :composed-2 :composed-3]))

(defn- set-color-bottom-shape [label file color]
  (let [shape (ctho/bottom-shape file label)]
    (concat
     [(dws/select-shape (:id shape))
      (dc/apply-color-from-palette color false)]
     (sync-file file))))

(defn- count-shapes [file name color]
  (let [page (cthf/current-page file)]
    (->> (vals (:objects page))
         (filter #(and
                   (= (:name %) name)
                   (-> (cths/get-shape-by-id file (:id %))
                       :fills
                       first
                       :fill-color
                       (= color))))
         (count))))

(defn- duplicate-each-main-and-first-level-copy [file]
  (concat (copy-paste-shape :frame-simple-1 file)
          (copy-paste-shape :frame-simple-1 file)
          (copy-paste-shape :frame-composed-1 file)
          (copy-paste-shape :frame-composed-1 file)
          (copy-paste-shape :frame-composed-2 file)
          (copy-paste-shape :frame-composed-2 file)
          (copy-paste-shape :frame-composed-3 file)
          (copy-paste-shape :frame-composed-3 file)
          (copy-paste-shape :copy-composed-2 file)
          (copy-paste-shape :copy-composed-2 file)
          (copy-paste-shape :copy-composed-3 file)
          (copy-paste-shape :copy-composed-3 file)))

(defn- duplicate-simple-nested-in-main-and-group [file]
  (concat (copy-paste-shape :copy-simple-1 file)
          (copy-paste-shape :copy-simple-1 file)
          (copy-paste-shape :group-3 file)
          (copy-paste-shape :group-3 file)))

(defn- duplicate-copy-nested-and-group-out-of-the-main
  [file & {:keys [target-page-label]}]
  (let [page (cthf/current-page file)
        frame-1-instance-ids (->> (vals (:objects page))
                                  (filter #(and
                                            (or
                                             (= (:name %) "Frame1")
                                             (= (:name %) "Group1"))
                                            (not (:component-root %))))
                                  (map :id))]
    (concat
     (apply concat
            (mapv #(copy-paste-shape % file :target-page-label target-page-label :target-container-id uuid/zero) frame-1-instance-ids))
     (apply concat
            (mapv #(copy-paste-shape % file :target-page-label target-page-label :target-container-id uuid/zero) frame-1-instance-ids)))))

(t/deftest main-and-first-level-copy-1
  (t/async done
    (mock/with-mocks {uuid/next cthi/next-uuid}
      (fn [done']
        (let [;; ==== Setup
              file     (setup-file)
              store    (ths/setup-store file)
              ;; ==== Action


              ;; For each main and first level copy:
              ;; - Duplicate it two times with copy-paste.
              events
              (concat
               (duplicate-each-main-and-first-level-copy file)
               ;; - Change color of Simple1
               (set-color-bottom-shape :frame-simple-1 file {:color "#111111"}))]

          (ths/run-store
           store done' events
           (fn [new-state]
             (let [file' (ths/get-file-from-state new-state)]
               (cthf/validate-file! file')
               (t/is (= (count-shapes file' "rect-simple-1" "#111111") 18)))))))
      done)))

(t/deftest main-and-first-level-copy-2
  (t/async done
    (mock/with-mocks {uuid/next cthi/next-uuid}
      (fn [done']
        (let [;; ==== Setup
              file     (setup-file)
              store    (ths/setup-store file)
              ;; ==== Action


              ;; For each main and first level copy:
              ;; - Duplicate it two times with copy-paste.
              events
              (concat
               (duplicate-each-main-and-first-level-copy file)
               ;; - Change color of Simple1
               (set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
               ;; - Change color of the nearest main and check propagation to duplicated.
               (set-color-bottom-shape :frame-composed-1 file {:color "#222222"}))]

          (ths/run-store
           store done' events
           (fn [new-state]
             (let [file' (ths/get-file-from-state new-state)]
               (cthf/validate-file! file')
               (t/is (= (count-shapes file' "rect-simple-1" "#222222") 15)))))))
      done)))

(t/deftest main-and-first-level-copy-3
  (t/async done
    (mock/with-mocks {uuid/next cthi/next-uuid}
      (fn [done']
        (let [;; ==== Setup
              file     (setup-file)
              store    (ths/setup-store file)
              ;; ==== Action


              ;; For each main and first level copy:
              ;; - Duplicate it two times with copy-paste.
              events
              (concat
               (duplicate-each-main-and-first-level-copy file)
               ;; - Change color of Simple1
               (set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
               ;; - Change color of the nearest main and check propagation to duplicated.
               (set-color-bottom-shape :frame-composed-1 file {:color "#222222"})
               (set-color-bottom-shape :frame-composed-2 file {:color "#333333"}))]

          (ths/run-store
           store done' events
           (fn [new-state]
             (let [file' (ths/get-file-from-state new-state)]
               (cthf/validate-file! file')
               (t/is (= (count-shapes file' "rect-simple-1" "#333333") 12)))))))
      done)))

(t/deftest main-and-first-level-copy-4
  (t/async done
    (mock/with-mocks {uuid/next cthi/next-uuid}
      (fn [done']
        (let [;; ==== Setup
              file     (setup-file)
              store    (ths/setup-store file)
              ;; ==== Action


              ;; For each main and first level copy:
              ;; - Duplicate it two times with copy-paste.
              events
              (concat
               (duplicate-each-main-and-first-level-copy file)
               ;; - Change color of Simple1
               (set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
               ;; - Change color of the nearest main and check propagation to duplicated.
               (set-color-bottom-shape :frame-composed-1 file {:color "#222222"})
               (set-color-bottom-shape :frame-composed-2 file {:color "#333333"})
               (set-color-bottom-shape :frame-composed-3 file {:color "#444444"}))]

          (ths/run-store
           store done' events
           (fn [new-state]
             (let [file' (ths/get-file-from-state new-state)]
               (cthf/validate-file! file')
               (t/is (= (count-shapes file' "rect-simple-1" "#444444") 6)))))))
      done)))

(t/deftest copy-nested-in-main-1
  (t/async done
    (mock/with-mocks {uuid/next cthi/next-uuid}
      (fn [done']
        (let [;; ==== Setup
              file     (setup-file)
              store    (ths/setup-store file)

              ;; ==== Action
              ;; For each copy of Simple1 nested in a main, and the group inside Composed3 main:
              ;; - Duplicate it two times, keeping the duplicated inside the same main.
              events
              (concat
               (duplicate-simple-nested-in-main-and-group file)
               ;; - Change color of Simple1
               (set-color-bottom-shape :frame-simple-1 file {:color "#111111"}))]

          (ths/run-store
           store done' events
           (fn [new-state]
             (let [file' (ths/get-file-from-state new-state)]
               (cthf/validate-file! file')
               ;; Check propagation to all copies.
               (t/is (= (count-shapes file' "rect-simple-1" "#111111") 28)))))))
      done)))

(t/deftest copy-nested-in-main-2
  (t/async done
    (mock/with-mocks {uuid/next cthi/next-uuid}
      (fn [done']
        (let [;; ==== Setup
              file     (setup-file)
              store    (ths/setup-store file)

              ;; ==== Action
              ;; For each copy of Simple1 nested in a main, and the group inside Composed3 main:
              ;; - Duplicate it two times, keeping the duplicated inside the same main.
              events
              (concat
               (duplicate-simple-nested-in-main-and-group file)
               ;; - Change color of the nearest main
               (set-color-bottom-shape :frame-composed-1 file {:color "#222222"}))]

          (ths/run-store
           store done' events
           (fn [new-state]
             (let [file' (ths/get-file-from-state new-state)]
               (cthf/validate-file! file')
               ;; Check propagation to duplicated.
               (t/is (= (count-shapes file' "rect-simple-1" "#222222") 9)))))))
      done)))

(t/deftest copy-nested-in-main-3
  (t/async done
    (mock/with-mocks {uuid/next cthi/next-uuid}
      (fn [done']
        (let [;; ==== Setup
              file     (setup-file)
              store    (ths/setup-store file)

              ;; ==== Action
              ;; For each copy of Simple1 nested in a main, and the group inside Composed3 main:
              ;; - Duplicate it two times, keeping the duplicated inside the same main.
              events
              (concat
               (duplicate-simple-nested-in-main-and-group file)
               ;; - Change color of the copy you duplicated from.
               (set-color-bottom-shape :group-3 file {:color "#333333"}))]

          (ths/run-store
           store done' events
           (fn [new-state]
             (let [file' (ths/get-file-from-state new-state)]
               (cthf/validate-file! file')
               ;; Check that it's NOT PROPAGATED.
               (t/is (= (count-shapes file' "rect-simple-1" "#333333") 2)))))))
      done)))

(t/deftest copy-nested-1
  (t/async done
    (mock/with-mocks {uuid/next cthi/next-uuid}
      (fn [done']
        (let [;; ==== Setup
              file     (setup-file)
              store    (ths/setup-store file)

              ;; ==== Action
              ;; For each copy of Simple1 nested in a main or other copy, and the group inside Composed3
              ;; main and copy:
              ;;   - Duplicate it two times, moving the duplicates out of the main.
              events
              (concat
               (duplicate-copy-nested-and-group-out-of-the-main file)
               ;; - Change color of Simple1
               (set-color-bottom-shape :frame-simple-1 file {:color "#111111"}))]

          (ths/run-store
           store done' events
           (fn [new-state]
             (let [file' (ths/get-file-from-state new-state)]
               (cthf/validate-file! file')
               ;; Check propagation to all copies.
               (t/is (= (count-shapes file' "rect-simple-1" "#111111") 20)))))))
      done)))

(t/deftest copy-nested-2
  (t/async done
    (mock/with-mocks {uuid/next cthi/next-uuid}
      (fn [done']
        (let [;; ==== Setup
              file     (setup-file)
              store    (ths/setup-store file)

              ;; ==== Action
              ;; For each copy of Simple1 nested in a main or other copy, and the group inside Composed3
              ;; main and copy:
              ;;   - Duplicate it two times, moving the duplicates out of the main.
              events
              (concat
               (duplicate-copy-nested-and-group-out-of-the-main file)
               ;; - Change color of Simple1
               (set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
               ;; - Change color of the previous main
               (set-color-bottom-shape :frame-composed-1 file {:color "#222222"})
               (set-color-bottom-shape :group-3 file {:color "#333333"}))]

          (ths/run-store
           store done' events
           (fn [new-state]
             (let [file' (ths/get-file-from-state new-state)]
               (cthf/validate-file! file')
               ;; Check that it's NOT PROPAGATED.
               (t/is (= (count-shapes file' "rect-simple-1" "#111111") 11))
               (t/is (= (count-shapes file' "rect-simple-1" "#222222") 7))
               (t/is (= (count-shapes file' "rect-simple-1" "#333333") 2)))))))
      done)))

;; FIXME: this test does not calls done consistently, so it break all the test suite
#_(t/deftest copy-nested-3
    (t/async done
      (mock/with-mocks {uuid/next cthi/next-uuid}
        (fn [done']
          (let [;; ==== Setup
                file     (setup-file)
                store    (ths/setup-store file)

                ;; ==== Action
                ;; For each copy of Simple1 nested in a main or other copy, and the group inside Composed3
                ;; main and copy:
                ;;   - Duplicate it two times, moving the duplicates to another page
                events
                (concat
                 (duplicate-copy-nested-and-group-out-of-the-main file :target-page-label :page-2)
                 ;; - Change color of Simple1
                 (set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
                 ;; - Change color of the previous main
                 (set-color-bottom-shape :frame-composed-1 file {:color "#222222"})
                 (set-color-bottom-shape :group-3 file {:color "#333333"}))]
            `(ths/run-store
              store done' events
              (fn [new-state]
                (let [file' (-> (ths/get-file-from-state new-state)
                                (cthf/switch-to-page :page-2))]
                  (cthf/validate-file! file')
                  ;; Check that it's NOT PROPAGATED.
                  (t/is (= (count-shapes file' "rect-simple-1" "#111111") 10))
                  (t/is (= (count-shapes file' "rect-simple-1" "#222222") 4))
                  (t/is (= (count-shapes file' "rect-simple-1" "#333333") 0)))))))
        done)))

(t/deftest duplicate-page-integrity-frame-group-component
  ;; This test covers the bug fixed in 2.9.0: duplicating a page with a mainInstance inside a group
  ;; must preserve parent/child referential integrity (parent-id and :shapes).
  ;;
  ;; Structure created:
  ;;   Page
  ;;     └─ Group ("group-1")
  ;;         └─ Main component ("frame-1")
  ;;             └─ Shape ("shape-1")
  ;;   The frame is also promoted to a component (main).
  ;;
  ;; The test checks:
  ;;   - The group, frame, and shape exist in the duplicated page.
  ;;   - The parent/child relationships are correct (group:shapes contains frame, frame:shapes contains shape, etc).
  ;;   - The duplicated page contains an instance of the component whose main is in the original page.
  (t/async done
    (mock/with-mocks {uuid/next cthi/next-uuid}
      (fn [done']
        (let [file (-> (cthf/sample-file :file1 :page-label :page-1)
                       (ctho/add-group :group-1 {:name "group-1"})
                       (ctho/add-frame :frame-1 :parent-label :group-1 {:name "frame-1"})
                       (cths/add-sample-shape :shape-1 :parent-label :frame-1 {:name "shape-1"})
                       (cthc/make-component :component-1 :frame-1))
              page-id (cthf/current-page-id file)
              store (ths/setup-store file)
              events [(dwp/duplicate-page page-id)]]
          (ths/run-store
           store done' events
           (fn [new-state]
             (let [file' (ths/get-file-from-state new-state)
                   pages-vec (get-in file' [:data :pages])
                   pages-index (get-in file' [:data :pages-index])
                   new-page-id (first (remove #(= page-id %) pages-vec))
                   new-page (get pages-index new-page-id)
                   new-objects (:objects new-page)
                   group (some #(when (= (:name %) "group-1") %) (vals new-objects))
                   frame (some #(when (= (:name %) "frame-1") %) (vals new-objects))
                   shape (some #(when (= (:name %) "shape-1") %) (vals new-objects))]

               (t/is group "Group exists in duplicated page")
               (t/is frame "Frame exists in duplicated page")
               (t/is shape "Shape exists in duplicated page")
               (t/is (some #(= (:id frame) %) (:shapes group)) "Group's :shapes contains frame's id")
               (t/is (some #(= (:id shape) %) (:shapes frame)) "Frame's :shapes contains shape's id")
               (t/is (= (:parent-id frame) (:id group)) "Frame's parent is group")
               (t/is (= (:parent-id shape) (:id frame)) "Shape's parent is frame")

               ;; Check the duplicated page must contain an instance of the component whose main is in the original page
               (let [original-page (get pages-index page-id)
                     original-main (some #(when (:component-root %) %) (vals (:objects original-page)))
                     instance (some #(when (:component-root %) %) (vals (:objects new-page)))
                     component-id (:component-id original-main)]
                 (t/is (ctk/instance-of? instance (:id file) component-id)
                       (str "Duplicated page contains an instance of the original main component (component-id: " component-id ")"))))))))
      done)))


(defn- setup-swapped-copies-file
  "Creates a file with a component with two levels of nested copies inside. The component
   has one copy, and inside it, the topmost nested copy is swapped with a second component,
   also with one nested copy inside.

   {:frame-simple-1} [:name Frame1]                   # [Component :simple-1]
       :rect-simple-1 [:name Rect1]

   {:frame-composed-1} [:name frame-composed-1]       # [Component :composed-1]
       :copy-simple-1-in-composed-1 [:name Frame1]    @--> frame-simple-1
           <no-label #000894> [:name Rect1]           ---> rect-simple-1

   {:frame-composed-2} [:name frame-composed-2]       # [Component :composed-2]
       :copy-composed-1-in-composed-2 [:name frame-composed-1] @--> frame-composed-1
           <no-label #000899> [:name Frame1]          @--> copy-simple-1-in-composed-1
               <no-label #00089a> [:name Rect1]       ---> <no-label #000894>

   {:frame-simple-2} [:name Frame1]                   # [Component :simple-2]
       :rect-simple-2 [:name Rect1]

   {:frame-composed-3} [:name frame-composed-3]       # [Component :composed-3]
       :copy-simple-2-in-composed-3 [:name Frame1]    @--> frame-simple-2
           <no-label #0008a4> [:name Rect1]           ---> rect-simple-2

   :copy-composed-2 [:name frame-composed-2]          #--> [Component :composed-2] frame-composed-2
       :swapped-composed-3 [:name frame-composed-3, :swap-slot-label :copy-composed-1-in-composed-2] @--> frame-composed-3
           :swapped-simple-2-frame [:name Frame1]          @--> copy-simple-2-in-composed-3
               :swapped-simple-2-rect [:name Rect1]       ---> <no-label #0008a4>
   "
  []
  (-> (cthf/sample-file :file1 :page-label :page-1)
      ;; 1. Simple component :simple-1
      (ctho/add-simple-component :simple-1 :frame-simple-1 :rect-simple-1)

      ;; 2. Composed component :composed-1 containing a copy of :simple-1
      (ctho/add-frame :frame-composed-1 :name "frame-composed-1")
      (cthc/instantiate-component :simple-1 :copy-simple-1-in-composed-1
                                  :parent-label :frame-composed-1)
      (cthc/make-component :composed-1 :frame-composed-1)

      ;; 3. Composed component :composed-2 containing a copy of :composed-1
      (ctho/add-frame :frame-composed-2 :name "frame-composed-2")
      (cthc/instantiate-component :composed-1 :copy-composed-1-in-composed-2
                                  :parent-label :frame-composed-2)
      (cthc/make-component :composed-2 :frame-composed-2)

      ;; 4. Simple component :simple-2
      (ctho/add-simple-component :simple-2 :frame-simple-2 :rect-simple-2)

      ;; 5. Composed component :composed-3 containing a copy of :simple-2
      (ctho/add-frame :frame-composed-3 :name "frame-composed-3")
      (cthc/instantiate-component :simple-2 :copy-simple-2-in-composed-3
                                  :parent-label :frame-composed-3)
      (cthc/make-component :composed-3 :frame-composed-3)

      ;; 6. A copy of :composed-2
      (cthc/instantiate-component :composed-2 :copy-composed-2
                                  :children-labels [:nested-copy-composed-1])

      ;; 7. Swap the nested copy of :composed-1 in the copy of :composed-2 with :composed-3
      (cthc/component-swap :nested-copy-composed-1 :composed-3 :swapped-composed-3
                           :children-labels [:swapped-simple-2-frame :swapped-simple-2-rect])))

(t/deftest duplicate-swapped-copies
  (t/async done
    (mock/with-mocks {uuid/next cthi/next-uuid}
      (fn [done']
        (let [;; ==== Setup
              file     (setup-swapped-copies-file)
              store    (ths/setup-store file)

              ;; ==== Action
              ;; Copy to the clipboard all the shapes in the swapped copy one by one,
              ;; and paste them outside the copy, under uuid/zero
              events   (concat (copy-paste-shape :copy-composed-2 file :target-container-id uuid/zero)
                               (copy-paste-shape :swapped-composed-3 file :target-container-id uuid/zero)
                               (copy-paste-shape :swapped-simple-2-frame file :target-container-id uuid/zero)
                               (copy-paste-shape :swapped-simple-2-rect file :target-container-id uuid/zero))]

          (ths/run-store
           store done' events
           (fn [new-state]
             (let [file' (ths/get-file-from-state new-state)
                   page' (cthf/current-page file')]

               (cthf/validate-file! file')

               ;; ==== Check
               ;; Shape count breakdown (including page root):
               ;;  page root: 1
               ;;  :simple-1 main: 2 shapes (frame + rect)
               ;;  :composed-1 main: 3 shapes (frame + instance-of-simple-1 + its child)
               ;;  :composed-2 main: 4 shapes (frame + instance-of-composed-1 + its descendants)
               ;;  :simple-2 main: 2 shapes (frame + rect)
               ;;  :composed-3 main: 3 shapes (frame + instance-of-simple-2 + its child)
               ;;  copy of :composed-2 (with swapped child): 4 shapes
               ;;  pasted copy of :copy-composed-2: 4 shapes
               ;;  pasted copy of :composed-3: 3 shapes
               ;;  pasted copy of :swapped-simple-2-frame: 2 shapes
               ;;  pasted copy of :swapped-simple-2-rect: 1 shapes
               ;;  Total = 1 + 2 + 3 + 4 + 2 + 3 + 4 + 4 + 3 + 2 + 1 = 29
               (t/is (= (count (:objects page')) 29)))))))
      done)))

;; ---------------------------------------------------------------------------
;; Tests for issue-14302
;; Pasting a board with text shapes must clear stale position-data so the
;; workspace can remeasure the text and avoid incorrect line breaks.
;; ---------------------------------------------------------------------------

(defn- setup-board-with-texts
  "Two-page file. Page 1 has a board with two text shapes that carry stale
   position-data (simulating a shape that was already laid out on a
   different page). Page 2 is empty. Current page is page-1."
  []
  (let [stale-pd [{:x 999 :y 999 :width 999 :height 20 :fills [] :text "stale"}]]
    (-> (cthf/sample-file :file1 :page-label :page-1)
        (ctho/add-frame :frame-1 :name "board-with-texts")
        (cths/add-sample-shape :text-a :type :text :parent-label :frame-1 :name "text-a")
        (cths/update-shape :text-a :position-data stale-pd)
        (cths/add-sample-shape :text-b :type :text :parent-label :frame-1 :name "text-b")
        (cths/update-shape :text-b :position-data stale-pd)
        (cthf/add-sample-page :page-2)
        (cthf/switch-to-page :page-1))))

(t/deftest paste-to-empty-page-clears-text-position-data
  "Regression for issue-14302: copying a board with text shapes and pasting
   onto an empty page produced incorrect line breaks because stale
   position-data from the source page was preserved."
  (t/async done
    (mock/with-mocks {uuid/next cthi/next-uuid}
      (fn [done']
        (let [file   (setup-board-with-texts)
              store  (ths/setup-store file)
              ;; copy-paste-shape handles initialize-page + select-shape (needed
              ;; for calculate-paste-position) + paste-shapes + return to page-1
              events (copy-paste-shape :frame-1 file
                                       :target-page-label :page-2
                                       :target-container-id uuid/zero)]

          (ths/run-store
           store done' events
           (fn [new-state]
             (let [file'        (ths/get-file-from-state new-state)
                   page-2       (cthf/get-page file' :page-2)
                   pasted-texts (->> (vals (:objects page-2))
                                     (filter #(= :text (:type %))))]
               (t/is (= 2 (count pasted-texts))
                     "Both text shapes are pasted onto the empty page")
               (t/is (every? #(nil? (:position-data %)) pasted-texts)
                     "Pasted text shapes have nil position-data so they get remeasured"))))))
      done)))

(t/deftest paste-to-same-page-clears-text-position-data
  "Position-data is stripped on paste regardless of destination page."
  (t/async done
    (mock/with-mocks {uuid/next cthi/next-uuid}
      (fn [done']
        (let [file   (setup-board-with-texts)
              store  (ths/setup-store file)
              events (copy-paste-shape :frame-1 file
                                       :target-container-id uuid/zero)]

          (ths/run-store
           store done' events
           (fn [new-state]
             (let [file'        (ths/get-file-from-state new-state)
                   page-1'      (cthf/get-page file' :page-1)
                   ;; Pasted copies have IDs not registered in idmap — they show
                   ;; up as "<no-label …>" strings from cthi/label.
                   pasted-texts (->> (vals (:objects page-1'))
                                     (filter #(= :text (:type %)))
                                     (remove #(keyword? (cthi/label (:id %)))))]
               (t/is (= 2 (count pasted-texts))
                     "Two new text shapes are pasted onto the same page")
               (t/is (every? #(nil? (:position-data %)) pasted-texts)
                     "Pasted text shapes have nil position-data"))))))
      done)))

