;; 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 app.plugins.grid
  (:require
   [app.common.data :as d]
   [app.common.schema :as sm]
   [app.common.types.shape.layout :as ctl]
   [app.main.data.workspace.shape-layout :as dwsl]
   [app.main.data.workspace.transforms :as dwt]
   [app.main.store :as st]
   [app.plugins.register :as r]
   [app.plugins.tracks :as tracks]
   [app.plugins.utils :as u]
   [app.util.object :as obj]
   [potok.v2.core :as ptk]))

;; Define in `app.plugins.shape` we do this way to prevent circular dependency
(def shape-proxy? nil)

(defn- update-padding
  "Patch the layout padding and re-derive its `:simple`/`:multiple` type."
  [this id patch]
  (let [padding (merge (-> this u/proxy->shape :layout-padding) patch)]
    (st/emit! (dwsl/update-layout #{id} {:layout-padding patch
                                         :layout-padding-type (ctl/padding-type-for padding)}))))

(defn grid-layout-proxy? [p]
  (obj/type-of? p "GridLayoutProxy"))

(defn grid-layout-proxy
  [plugin-id file-id page-id id]
  (obj/reify {:name "GridLayoutProxy"}
    :$plugin {:enumerable false :get (constantly plugin-id)}
    :$id {:enumerable false :get (constantly id)}
    :$file {:enumerable false :get (constantly file-id)}
    :$page {:enumerable false :get (constantly page-id)}

    :dir
    {:this true
     :get #(-> % u/proxy->shape :layout-grid-dir d/name)
     :set
     (fn [_ value]
       (let [value (keyword value)]
         (cond
           (not (contains? ctl/grid-direction-types value))
           (u/not-valid plugin-id :dir value)

           (not (r/check-permission plugin-id "content:write"))
           (u/not-valid plugin-id :dir "Plugin doesn't have 'content:write' permission")

           (not (u/page-active? page-id))
           (u/not-valid plugin-id :dir "Cannot modify a page that is not currently active")

           :else
           (st/emit! (dwsl/update-layout #{id} {:layout-grid-dir value})))))}

    :rows
    {:this true
     :get (fn [self]
            (tracks/format-tracks plugin-id file-id page-id id :row
                                  (-> self u/proxy->shape :layout-grid-rows)))}

    :columns
    {:this true
     :get (fn [self]
            (tracks/format-tracks plugin-id file-id page-id id :column
                                  (-> self u/proxy->shape :layout-grid-columns)))}

    :horizontalSizing
    {:this true
     :get #(-> % u/proxy->shape :layout-item-h-sizing (d/nilv :fix) d/name)
     :set
     (fn [_ value]
       (let [value (keyword value)]
         (cond
           (not (contains? ctl/item-h-sizing-types value))
           (u/not-valid plugin-id :horizontalSizing value)

           (not (r/check-permission plugin-id "content:write"))
           (u/not-valid plugin-id :horizontalSizing "Plugin doesn't have 'content:write' permission")

           (not (u/page-active? page-id))
           (u/not-valid plugin-id :horizontalSizing "Cannot modify a page that is not currently active")

           :else
           (st/emit! (dwsl/update-layout #{id} {:layout-item-h-sizing value})))))}

    :verticalSizing
    {:this true
     :get #(-> % u/proxy->shape :layout-item-v-sizing (d/nilv :fix) d/name)
     :set
     (fn [_ value]
       (let [value (keyword value)]
         (cond
           (not (contains? ctl/item-v-sizing-types value))
           (u/not-valid plugin-id :verticalSizing value)

           (not (r/check-permission plugin-id "content:write"))
           (u/not-valid plugin-id :verticalSizing "Plugin doesn't have 'content:write' permission")

           (not (u/page-active? page-id))
           (u/not-valid plugin-id :verticalSizing "Cannot modify a page that is not currently active")

           :else
           (st/emit! (dwsl/update-layout #{id} {:layout-item-v-sizing value})))))}

    :alignItems
    {:this true
     :get #(-> % u/proxy->shape :layout-align-items d/name)
     :set
     (fn [_ value]
       (let [value (keyword value)]
         (cond
           (not (contains? ctl/align-items-types value))
           (u/not-valid plugin-id :alignItems value)

           (not (r/check-permission plugin-id "content:write"))
           (u/not-valid plugin-id :alignItems "Plugin doesn't have 'content:write' permission")

           (not (u/page-active? page-id))
           (u/not-valid plugin-id :alignItems "Cannot modify a page that is not currently active")

           :else
           (st/emit! (dwsl/update-layout #{id} {:layout-align-items value})))))}

    :alignContent
    {:this true
     :get #(-> % u/proxy->shape :layout-align-content d/name)
     :set
     (fn [_ value]
       (let [value (keyword value)]
         (cond
           (not (contains? ctl/align-content-types value))
           (u/not-valid plugin-id :alignContent value)

           (not (r/check-permission plugin-id "content:write"))
           (u/not-valid plugin-id :alignContent "Plugin doesn't have 'content:write' permission")

           (not (u/page-active? page-id))
           (u/not-valid plugin-id :alignContent "Cannot modify a page that is not currently active")

           :else
           (st/emit! (dwsl/update-layout #{id} {:layout-align-content value})))))}

    :justifyItems
    {:this true
     :get #(-> % u/proxy->shape :layout-justify-items d/name)
     :set
     (fn [_ value]
       (let [value (keyword value)]
         (cond
           (not (contains? ctl/justify-items-types value))
           (u/not-valid plugin-id :justifyItems value)

           (not (r/check-permission plugin-id "content:write"))
           (u/not-valid plugin-id :justifyItems "Plugin doesn't have 'content:write' permission")

           (not (u/page-active? page-id))
           (u/not-valid plugin-id :justifyItems "Cannot modify a page that is not currently active")

           :else
           (st/emit! (dwsl/update-layout #{id} {:layout-justify-items value})))))}

    :justifyContent
    {:this true
     :get #(-> % u/proxy->shape :layout-justify-content d/name)
     :set
     (fn [_ value]
       (let [value (keyword value)]
         (cond
           (not (contains? ctl/justify-content-types value))
           (u/not-valid plugin-id :justifyContent value)

           (not (r/check-permission plugin-id "content:write"))
           (u/not-valid plugin-id :justifyContent "Plugin doesn't have 'content:write' permission")

           (not (u/page-active? page-id))
           (u/not-valid plugin-id :justifyContent "Cannot modify a page that is not currently active")

           :else
           (st/emit! (dwsl/update-layout #{id} {:layout-justify-content value})))))}

    :rowGap
    {:this true
     :get #(-> % u/proxy->shape :layout-gap :row-gap (d/nilv 0))
     :set
     (fn [_ value]
       (cond
         (not (sm/valid-safe-number? value))
         (u/not-valid plugin-id :rowGap value)

         (not (r/check-permission plugin-id "content:write"))
         (u/not-valid plugin-id :rowGap "Plugin doesn't have 'content:write' permission")

         (not (u/page-active? page-id))
         (u/not-valid plugin-id :rowGap "Cannot modify a page that is not currently active")

         :else
         (st/emit! (dwsl/update-layout #{id} {:layout-gap {:row-gap value}}))))}

    :columnGap
    {:this true
     :get #(-> % u/proxy->shape :layout-gap :column-gap (d/nilv 0))
     :set
     (fn [_ value]
       (cond
         (not (sm/valid-safe-number? value))
         (u/not-valid plugin-id :columnGap value)

         (not (r/check-permission plugin-id "content:write"))
         (u/not-valid plugin-id :columnGap "Plugin doesn't have 'content:write' permission")

         (not (u/page-active? page-id))
         (u/not-valid plugin-id :columnGap "Cannot modify a page that is not currently active")

         :else
         (st/emit! (dwsl/update-layout #{id} {:layout-gap {:column-gap value}}))))}

    :verticalPadding
    {:this true
     :get #(-> % u/proxy->shape :layout-padding :p1 (d/nilv 0))
     :set
     (fn [this value]
       (cond
         (not (sm/valid-safe-number? value))
         (u/not-valid plugin-id :verticalPadding value)

         (not (r/check-permission plugin-id "content:write"))
         (u/not-valid plugin-id :verticalPadding "Plugin doesn't have 'content:write' permission")

         (not (u/page-active? page-id))
         (u/not-valid plugin-id :verticalPadding "Cannot modify a page that is not currently active")

         :else
         (update-padding this id {:p1 value :p3 value})))}

    :horizontalPadding
    {:this true
     :get #(-> % u/proxy->shape :layout-padding :p2 (d/nilv 0))
     :set
     (fn [this value]
       (cond
         (not (sm/valid-safe-number? value))
         (u/not-valid plugin-id :horizontalPadding value)

         (not (r/check-permission plugin-id "content:write"))
         (u/not-valid plugin-id :horizontalPadding "Plugin doesn't have 'content:write' permission")

         (not (u/page-active? page-id))
         (u/not-valid plugin-id :horizontalPadding "Cannot modify a page that is not currently active")

         :else
         (update-padding this id {:p2 value :p4 value})))}

    :topPadding
    {:this true
     :get #(-> % u/proxy->shape :layout-padding :p1 (d/nilv 0))
     :set
     (fn [this value]
       (cond
         (not (sm/valid-safe-number? value))
         (u/not-valid plugin-id :topPadding value)

         (not (r/check-permission plugin-id "content:write"))
         (u/not-valid plugin-id :topPadding "Plugin doesn't have 'content:write' permission")

         (not (u/page-active? page-id))
         (u/not-valid plugin-id :topPadding "Cannot modify a page that is not currently active")

         :else
         (update-padding this id {:p1 value})))}

    :rightPadding
    {:this true
     :get #(-> % u/proxy->shape :layout-padding :p2 (d/nilv 0))
     :set
     (fn [this value]
       (cond
         (not (sm/valid-safe-number? value))
         (u/not-valid plugin-id :rightPadding value)

         (not (r/check-permission plugin-id "content:write"))
         (u/not-valid plugin-id :rightPadding "Plugin doesn't have 'content:write' permission")

         (not (u/page-active? page-id))
         (u/not-valid plugin-id :rightPadding "Cannot modify a page that is not currently active")

         :else
         (update-padding this id {:p2 value})))}

    :bottomPadding
    {:this true
     :get #(-> % u/proxy->shape :layout-padding :p3 (d/nilv 0))
     :set
     (fn [this value]
       (cond
         (not (sm/valid-safe-number? value))
         (u/not-valid plugin-id :bottomPadding value)

         (not (r/check-permission plugin-id "content:write"))
         (u/not-valid plugin-id :bottomPadding "Plugin doesn't have 'content:write' permission")

         (not (u/page-active? page-id))
         (u/not-valid plugin-id :bottomPadding "Cannot modify a page that is not currently active")

         :else
         (update-padding this id {:p3 value})))}

    :leftPadding
    {:this true
     :get #(-> % u/proxy->shape :layout-padding :p4 (d/nilv 0))
     :set
     (fn [this value]
       (cond
         (not (sm/valid-safe-number? value))
         (u/not-valid plugin-id :leftPadding value)

         (not (r/check-permission plugin-id "content:write"))
         (u/not-valid plugin-id :leftPadding "Plugin doesn't have 'content:write' permission")

         (not (u/page-active? page-id))
         (u/not-valid plugin-id :leftPadding "Cannot modify a page that is not currently active")

         :else
         (update-padding this id {:p4 value})))}

    ;; `:simple` mirrors vertical/horizontal padding; `:multiple` honours each side.
    :paddingType
    {:this true
     :get #(-> % u/proxy->shape :layout-padding-type (d/nilv :simple) d/name)
     :set
     (fn [_ value]
       (let [value (keyword value)]
         (cond
           (not (contains? ctl/padding-type value))
           (u/not-valid plugin-id :paddingType value)

           (not (r/check-permission plugin-id "content:write"))
           (u/not-valid plugin-id :paddingType "Plugin doesn't have 'content:write' permission")

           (not (u/page-active? page-id))
           (u/not-valid plugin-id :paddingType "Cannot modify a page that is not currently active")

           :else
           (st/emit! (dwsl/update-layout #{id} {:layout-padding-type value})))))}

    :addRow
    (fn [type value]
      (let [type (keyword type)]
        (cond
          (not (contains? ctl/grid-track-types type))
          (u/not-valid plugin-id :addRow-type type)

          (and (or (= :percent type) (= :flex type) (= :fixed type))
               (not (sm/valid-safe-number? value)))
          (u/not-valid plugin-id :addRow-value value)

          (not (r/check-permission plugin-id "content:write"))
          (u/not-valid plugin-id :addRow "Plugin doesn't have 'content:write' permission")

          (not (u/page-active? page-id))
          (u/not-valid plugin-id :addRow "Cannot modify a page that is not currently active")

          :else
          (st/emit! (dwsl/add-layout-track #{id} :row {:type type :value value})))))

    :addRowAtIndex
    (fn [index type value]
      (let [type (keyword type)
            num-rows (-> (u/locate-shape file-id page-id id) :layout-grid-rows count)]
        (cond
          (not (sm/valid-safe-int? index))
          (u/not-valid plugin-id :addRowAtIndex-index index)

          (or (< index 0) (> index num-rows))
          (u/not-valid plugin-id :addRowAtIndex-index index)

          (not (contains? ctl/grid-track-types type))
          (u/not-valid plugin-id :addRowAtIndex-type type)

          (and (or (= :percent type) (= :flex type) (= :fixed type))
               (not (sm/valid-safe-number? value)))
          (u/not-valid plugin-id :addRowAtIndex-value value)

          (not (r/check-permission plugin-id "content:write"))
          (u/not-valid plugin-id :addRowAtIndex "Plugin doesn't have 'content:write' permission")

          (not (u/page-active? page-id))
          (u/not-valid plugin-id :addRowAtIndex "Cannot modify a page that is not currently active")

          :else
          (st/emit! (dwsl/add-layout-track #{id} :row {:type type :value value} index)))))

    :addColumn
    (fn [type value]
      (let [type (keyword type)]
        (cond
          (not (contains? ctl/grid-track-types type))
          (u/not-valid plugin-id :addColumn-type type)

          (and (or (= :percent type) (= :flex type) (= :lex type))
               (not (sm/valid-safe-number? value)))
          (u/not-valid plugin-id :addColumn-value value)

          (not (r/check-permission plugin-id "content:write"))
          (u/not-valid plugin-id :addColumn "Plugin doesn't have 'content:write' permission")

          (not (u/page-active? page-id))
          (u/not-valid plugin-id :addColumn "Cannot modify a page that is not currently active")

          :else
          (st/emit! (dwsl/add-layout-track #{id} :column {:type type :value value})))))

    :addColumnAtIndex
    (fn [index type value]
      (let [type (keyword type)
            num-columns (-> (u/locate-shape file-id page-id id) :layout-grid-columns count)]
        (cond
          (not (sm/valid-safe-int? index))
          (u/not-valid plugin-id :addColumnAtIndex-index index)

          (or (< index 0) (> index num-columns))
          (u/not-valid plugin-id :addColumnAtIndex-index index)

          (not (contains? ctl/grid-track-types type))
          (u/not-valid plugin-id :addColumnAtIndex-type type)

          (and (or (= :percent type) (= :flex type) (= :fixed type))
               (not (sm/valid-safe-number? value)))
          (u/not-valid plugin-id :addColumnAtIndex-value value)

          (not (r/check-permission plugin-id "content:write"))
          (u/not-valid plugin-id :addColumnAtIndex "Plugin doesn't have 'content:write' permission")

          (not (u/page-active? page-id))
          (u/not-valid plugin-id :addColumnAtIndex "Cannot modify a page that is not currently active")

          :else
          (st/emit! (dwsl/add-layout-track #{id} :column {:type type :value value} index)))))

    :removeRow
    (fn [index]
      (let [num-rows (-> (u/locate-shape file-id page-id id) :layout-grid-rows count)]
        (cond
          (not (sm/valid-safe-int? index))
          (u/not-valid plugin-id :removeRow index)

          (or (< index 0) (>= index num-rows))
          (u/not-valid plugin-id :removeRow index)

          (not (r/check-permission plugin-id "content:write"))
          (u/not-valid plugin-id :removeRow "Plugin doesn't have 'content:write' permission")

          (not (u/page-active? page-id))
          (u/not-valid plugin-id :removeRow "Cannot modify a page that is not currently active")

          :else
          (st/emit! (dwsl/remove-layout-track #{id} :row index)))))

    :removeColumn
    (fn [index]
      (let [num-columns (-> (u/locate-shape file-id page-id id) :layout-grid-columns count)]
        (cond
          (not (sm/valid-safe-int? index))
          (u/not-valid plugin-id :removeColumn index)

          (or (< index 0) (>= index num-columns))
          (u/not-valid plugin-id :removeColumn index)

          (not (r/check-permission plugin-id "content:write"))
          (u/not-valid plugin-id :removeColumn "Plugin doesn't have 'content:write' permission")

          (not (u/page-active? page-id))
          (u/not-valid plugin-id :removeColumn "Cannot modify a page that is not currently active")

          :else
          (st/emit! (dwsl/remove-layout-track #{id} :column index)))))

    :setColumn
    (fn [index type value]
      (let [type (keyword type)
            num-columns (-> (u/locate-shape file-id page-id id) :layout-grid-columns count)]
        (cond
          (not (sm/valid-safe-int? index))
          (u/not-valid plugin-id :setColumn-index index)

          (or (< index 0) (>= index num-columns))
          (u/not-valid plugin-id :setColumn-index index)

          (not (contains? ctl/grid-track-types type))
          (u/not-valid plugin-id :setColumn-type type)

          (and (or (= :percent type) (= :flex type) (= :fixed type))
               (not (sm/valid-safe-number? value)))
          (u/not-valid plugin-id :setColumn-value value)

          (not (r/check-permission plugin-id "content:write"))
          (u/not-valid plugin-id :setColumn "Plugin doesn't have 'content:write' permission")

          (not (u/page-active? page-id))
          (u/not-valid plugin-id :setColumn "Cannot modify a page that is not currently active")

          :else
          (st/emit! (dwsl/change-layout-track #{id} :column index (d/without-nils {:type type :value value}))))))

    :setRow
    (fn [index type value]
      (let [type (keyword type)
            num-rows (-> (u/locate-shape file-id page-id id) :layout-grid-rows count)]
        (cond
          (not (sm/valid-safe-int? index))
          (u/not-valid plugin-id :setRow-index index)

          (or (< index 0) (>= index num-rows))
          (u/not-valid plugin-id :setRow-index index)

          (not (contains? ctl/grid-track-types type))
          (u/not-valid plugin-id :setRow-type type)

          (and (or (= :percent type) (= :flex type) (= :fixed type))
               (not (sm/valid-safe-number? value)))
          (u/not-valid plugin-id :setRow-value value)

          (not (r/check-permission plugin-id "content:write"))
          (u/not-valid plugin-id :setRow "Plugin doesn't have 'content:write' permission")

          (not (u/page-active? page-id))
          (u/not-valid plugin-id :setRow "Cannot modify a page that is not currently active")

          :else
          (st/emit! (dwsl/change-layout-track #{id} :row index (d/without-nils {:type type :value value}))))))

    :remove
    (fn []
      (cond
        (not (r/check-permission plugin-id "content:write"))
        (u/not-valid plugin-id :remove "Plugin doesn't have 'content:write' permission")

        (not (u/page-active? page-id))
        (u/not-valid plugin-id :remove "Cannot modify a page that is not currently active")

        :else
        (st/emit! (dwsl/remove-layout #{id}))))

    :appendChild
    (fn [child row column]
      (let [valid-child? (shape-proxy? child)
            child-page   (when valid-child? (obj/get child "$page"))
            child-id     (when valid-child? (obj/get child "$id"))
            objects      (when valid-child? (u/locate-objects file-id page-id))
            shape        (when valid-child? (get objects id))
            child-shape  (when valid-child? (get objects child-id))]
        (cond
          (not valid-child?)
          (u/not-valid plugin-id :appendChild-child child)

          (or (< row 0) (not (sm/valid-safe-int? row)))
          (u/not-valid plugin-id :appendChild-row row)

          (or (< column 0) (not (sm/valid-safe-int? column)))
          (u/not-valid plugin-id :appendChild-column column)

          (not (r/check-permission plugin-id "content:write"))
          (u/not-valid plugin-id :appendChild "Plugin doesn't have 'content:write' permission")

          (or (not (u/page-active? page-id))
              (not (u/page-active? child-page)))
          (u/not-valid plugin-id :appendChild "Cannot modify a page that is not currently active")

          (u/changes-component-copy-structure? objects shape child-shape)
          (u/not-valid plugin-id :appendChild "Cannot change the structure of a component copy")

          :else
          (st/emit! (dwt/move-shapes-to-frame #{child-id} id nil [row column])
                    (ptk/data-event :layout/update {:ids [id]})))))))

(defn layout-cell-proxy? [p]
  (obj/type-of? p "GridCellProxy"))

(defn layout-cell-proxy
  [plugin-id file-id page-id id]
  (letfn [(locate-cell [_]
            (let [shape (u/locate-shape file-id page-id id)
                  parent (u/locate-shape file-id page-id (:parent-id shape))]
              (ctl/get-cell-by-shape-id parent id)))]

    (obj/reify {:name "GridCellProxy"}
      :$plugin {:enumerable false :get (constantly plugin-id)}
      :$id {:enumerable false :get (constantly id)}
      :$file {:enumerable false :get (constantly file-id)}
      :$page {:enumerable false :get (constantly page-id)}

      :row
      {:this true
       :get #(-> % locate-cell :row)
       :set
       (fn [self value]
         (let [cell (locate-cell self)
               shape (u/proxy->shape self)]
           (cond
             (not (sm/valid-safe-int? value))
             (u/not-valid plugin-id :row-value value)

             (nil? cell)
             (u/not-valid plugin-id :row-cell "cell not found")

             (not (r/check-permission plugin-id "content:write"))
             (u/not-valid plugin-id :row "Plugin doesn't have 'content:write' permission")

             (not (u/page-active? page-id))
             (u/not-valid plugin-id :row "Cannot modify a page that is not currently active")

             :else
             (st/emit! (dwsl/update-grid-cell-position (:parent-id shape) (:id cell) {:row value})))))}

      :rowSpan
      {:this true
       :get #(-> % locate-cell :row-span)
       :set
       (fn [self value]
         (let [shape (u/proxy->shape self)
               cell (locate-cell self)]
           (cond
             (not (sm/valid-safe-int? value))
             (u/not-valid plugin-id :rowSpan-value value)

             (nil? cell)
             (u/not-valid plugin-id :rowSpan-cell "cell not found")

             (not (r/check-permission plugin-id "content:write"))
             (u/not-valid plugin-id :rowSpan "Plugin doesn't have 'content:write' permission")

             (not (u/page-active? page-id))
             (u/not-valid plugin-id :rowSpan "Cannot modify a page that is not currently active")

             :else
             (st/emit! (dwsl/update-grid-cell-position (:parent-id shape) (:id cell) {:row-span value})))))}

      :column
      {:this true
       :get #(-> % locate-cell :column)
       :set
       (fn [self value]
         (let [shape (u/proxy->shape self)
               cell (locate-cell self)]
           (cond
             (not (sm/valid-safe-int? value))
             (u/not-valid plugin-id :column-value value)

             (nil? cell)
             (u/not-valid plugin-id :column-cell "cell not found")

             (not (r/check-permission plugin-id "content:write"))
             (u/not-valid plugin-id :column "Plugin doesn't have 'content:write' permission")

             (not (u/page-active? page-id))
             (u/not-valid plugin-id :column "Cannot modify a page that is not currently active")

             :else
             (st/emit! (dwsl/update-grid-cell-position (:parent-id shape) (:id cell) {:column value})))))}

      :columnSpan
      {:this true
       :get #(-> % locate-cell :column-span)
       :set
       (fn [self value]
         (let [shape (u/proxy->shape self)
               cell (locate-cell self)]
           (cond
             (not (sm/valid-safe-int? value))
             (u/not-valid plugin-id :columnSpan-value value)

             (nil? cell)
             (u/not-valid plugin-id :columnSpan-cell "cell not found")

             (not (r/check-permission plugin-id "content:write"))
             (u/not-valid plugin-id :columnSpan "Plugin doesn't have 'content:write' permission")

             (not (u/page-active? page-id))
             (u/not-valid plugin-id :columnSpan "Cannot modify a page that is not currently active")

             :else
             (st/emit! (dwsl/update-grid-cell-position (:parent-id shape) (:id cell) {:column-span value})))))}

      :areaName
      {:this true
       :get #(-> % locate-cell :area-name)
       :set
       (fn [self value]
         (let [shape (u/proxy->shape self)
               cell (locate-cell self)]
           (cond
             (not (string? value))
             (u/not-valid plugin-id :areaName-value value)

             (nil? cell)
             (u/not-valid plugin-id :areaName-cell "cell not found")

             (not (r/check-permission plugin-id "content:write"))
             (u/not-valid plugin-id :areaName "Plugin doesn't have 'content:write' permission")

             (not (u/page-active? page-id))
             (u/not-valid plugin-id :areaName "Cannot modify a page that is not currently active")

             :else
             (st/emit! (dwsl/update-grid-cells (:parent-id shape) #{(:id cell)} {:area-name value})))))}

      :position
      {:this true
       :get #(-> % locate-cell :position d/name)
       :set
       (fn [self value]
         (let [shape (u/proxy->shape self)
               cell (locate-cell self)
               value (keyword value)]
           (cond
             (not (contains? ctl/grid-position-types value))
             (u/not-valid plugin-id :position-value value)

             (nil? cell)
             (u/not-valid plugin-id :position-cell "cell not found")

             (not (r/check-permission plugin-id "content:write"))
             (u/not-valid plugin-id :position "Plugin doesn't have 'content:write' permission")

             (not (u/page-active? page-id))
             (u/not-valid plugin-id :position "Cannot modify a page that is not currently active")

             :else
             (st/emit! (dwsl/change-cells-mode (:parent-id shape) #{(:id cell)} value)))))}

      :alignSelf
      {:this true
       :get #(-> % locate-cell :align-self d/name)
       :set
       (fn [self value]
         (let [shape (u/proxy->shape self)
               value (keyword value)
               cell (locate-cell self)]
           (cond
             (not (contains? ctl/grid-cell-align-self-types value))
             (u/not-valid plugin-id :alignSelf-value value)

             (nil? cell)
             (u/not-valid plugin-id :alignSelf-cell "cell not found")

             (not (r/check-permission plugin-id "content:write"))
             (u/not-valid plugin-id :alignSelf "Plugin doesn't have 'content:write' permission")

             (not (u/page-active? page-id))
             (u/not-valid plugin-id :alignSelf "Cannot modify a page that is not currently active")

             :else
             (st/emit! (dwsl/update-grid-cells (:parent-id shape) #{(:id cell)} {:align-self value})))))}

      :justifySelf
      {:this true
       :get #(-> % locate-cell :justify-self d/name)
       :set
       (fn [self value]
         (let [shape (u/proxy->shape self)
               value (keyword value)
               cell (locate-cell self)]
           (cond
             (not (contains? ctl/grid-cell-justify-self-types value))
             (u/not-valid plugin-id :justifySelf-value value)

             (nil? cell)
             (u/not-valid plugin-id :justifySelf-cell "cell not found")

             (not (r/check-permission plugin-id "content:write"))
             (u/not-valid plugin-id :justifySelf "Plugin doesn't have 'content:write' permission")

             (not (u/page-active? page-id))
             (u/not-valid plugin-id :justifySelf "Cannot modify a page that is not currently active")

             :else
             (st/emit! (dwsl/update-grid-cells (:parent-id shape) #{(:id cell)} {:justify-self value})))))})))
