;; 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.plugins.page-test
  (:require
   [app.common.test-helpers.files :as cthf]
   [app.common.test-helpers.ids-map :as thi]
   [app.common.test-helpers.shapes :as cths]
   [app.main.data.workspace.pages :as dwpg]
   [app.main.store :as st]
   [app.plugins.api :as api]
   [app.plugins.shape :as shape]
   [app.util.object :as obj]
   [cljs.test :as t :include-macros true]
   [frontend-tests.helpers.state :as ths]
   [frontend-tests.helpers.wasm :as thw]
   [potok.v2.core :as ptk]))

(t/use-fixtures :each
  {:before (fn [] (thw/setup-wasm-mocks!))
   :after  thw/teardown-wasm-mocks!})

(defn- setup
  "Creates a file with two pages (page1 as current) and a plugin context."
  []
  (let [file    (-> (cthf/sample-file :file1 :page-label :page1)
                    (cthf/add-sample-page :page2)
                    (cthf/switch-to-page :page1))
        store   (ths/setup-store file)
        _       (set! st/state store)
        _       (set! st/stream (ptk/input-stream store))
        _       (ptk/emit! store #(assoc-in % [:plugins :flags "00000000-0000-0000-0000-000000000000" :throw-validation-errors] true))
        context (api/create-context "00000000-0000-0000-0000-000000000000")]
    {:file file :store store :context context}))

(def ^:private plugin-id "00000000-0000-0000-0000-000000000000")

(defn- setup-with-board
  "Creates a file with a board on the current page and a plugin context."
  []
  (let [file    (-> (cthf/sample-file :file1 :page-label :page1)
                    (cths/add-sample-shape :board1 :type :frame))
        store   (ths/setup-store file)
        _       (set! st/state store)
        _       (set! st/stream (ptk/input-stream store))
        context (api/create-context plugin-id)]
    {:file file :store store :context context :board-id (thi/id :board1)}))

(t/deftest test-create-flow-starting-board-is-valid
  ;; Regression: the flow proxy returned by createFlow (and obtained later via
  ;; page.flows) must expose a valid board proxy for `startingBoard`, carrying
  ;; the plugin id rather than a corrupted handle. See issue #10203.
  (let [result          (setup-with-board)
        ^js context     (:context result)
        board-id        (:board-id result)
        ^js page        (.-currentPage context)
        ^js board       (.getShapeById page (str board-id))
        ^js flow        (.createFlow page "flow1" board)
        ^js sb          (.-startingBoard flow)]
    (t/is (shape/shape-proxy? sb))
    (t/is (= (str board-id) (.-id sb)))
    (t/is (= plugin-id (obj/get sb "$plugin")))

    ;; Re-fetching the flow through page.flows must yield the same valid board
    (let [^js flow2 (aget (.-flows page) 0)
          ^js sb2   (.-startingBoard flow2)]
      (t/is (shape/shape-proxy? sb2))
      (t/is (= (str board-id) (.-id sb2)))
      (t/is (= plugin-id (obj/get sb2 "$plugin"))))))

(defn- mock-page-initialized
  "Simulates the two effects of initialize-page* without routing:
  updates current-page-id in state, then emits the public ::dwpg/initialized event."
  [store page-id]
  (ptk/emit! store #(assoc % :current-page-id page-id))
  (ptk/emit! store (ptk/data-event ::dwpg/initialized page-id)))

(t/deftest test-open-page-returns-promise
  (let [^js context (:context (setup))
        ^js pages   (.. context -currentFile -pages)
        ^js page2   (aget pages 1)]
    (t/is (instance? js/Promise (.openPage context page2)))))

(t/deftest test-open-page-new-window-returns-promise
  (let [^js context (:context (setup))
        ^js pages   (.. context -currentFile -pages)
        ^js page2   (aget pages 1)]
    (t/is (instance? js/Promise (.openPage context page2 true)))))

(t/deftest test-open-page-invalid-arg-throws
  ;; With throwValidationErrors enabled an invalid argument surfaces as an
  ;; exception instead of being silently logged.
  (let [^js context (:context (setup))]
    (t/is (thrown? js/Error (.openPage context "not-a-page")))))

(t/deftest test-open-page-resolves-when-page-changes
  (t/async done
    (let [result                  (setup)
          store                   (:store result)
          ^js context             (:context result)
          ^js pages               (.. context -currentFile -pages)
          ^js page2               (aget pages 1)
          page2-id                (obj/get page2 "$id")]

      (-> (.openPage context page2)
          (.then (fn [_]
                   (t/is (= (:current-page-id @store) page2-id))
                   (done))))

      (mock-page-initialized store page2-id))))

(t/deftest test-flows-returns-empty-array-when-no-flows
  ;; page.flows must always return an array, even when the page has no flows
  (let [^js context (:context (setup))
        ^js pages   (.. context -currentFile -pages)
        ^js page1   (aget pages 0)
        ^js flows   (.-flows page1)]
    (t/is (array? flows))
    (t/is (= 0 (.-length flows)))))

(t/deftest test-open-page-does-not-resolve-for-wrong-page
  ;; Promise should not resolve when a different page is initialized
  (t/async done
    (let [result                  (setup)
          store                   (:store result)
          ^js context             (:context result)
          ^js pages               (.. context -currentFile -pages)
          ^js page1               (aget pages 0)
          ^js page2               (aget pages 1)
          page1-id                (obj/get page1 "$id")
          page2-id                (obj/get page2 "$id")
          resolved?               (atom false)]

      (-> (.openPage context page2)
          (.then (fn [_] (reset! resolved? true))))

      ;; Initialize page1 (wrong page) — promise should not resolve
      (mock-page-initialized store page1-id)

      ;; Give microtasks a chance to run, then verify promise is still pending
      (js/setTimeout
       (fn []
         (t/is (not @resolved?))
         ;; Now initialize the correct page and confirm it resolves
         (-> (.openPage context page2)
             (.then (fn [_]
                      (t/is (= (:current-page-id @store) page2-id))
                      (done))))
         (mock-page-initialized store page2-id))
       0))))
