<template>
  <div class="p-tab p-tab-photo-people">
    <div class="pa-2 p-faces">
      <v-alert v-if="markers.length === 0" color="surface-variant" icon="mdi-lightbulb-outline" class="no-results ma-2 opacity-70" variant="outlined">
        <div class="font-weight-bold">
          {{ $gettext(`No people found`) }}
        </div>
        <div class="mt-2">
          {{ $gettext(`You may rescan your library to find additional faces.`) }}
          {{ $gettext(`Recognition starts after indexing has been completed.`) }}
        </div>
      </v-alert>
      <div v-else class="v-row search-results face-results cards-view d-flex">
        <div v-for="m in markers" :key="m.UID" class="v-col-12 v-col-sm-6 v-col-md-4 v-col-lg-3 d-flex">
          <v-card :data-id="m.UID" :class="m.classes()" class="result not-selectable flex-grow-1" tabindex="0">
            <v-img :src="m.thumbnailUrl('tile_320')" aspect-ratio="1" class="card">
              <v-btn
                v-if="!m.SubjUID && !m.Invalid"
                :ripple="false"
                class="input-reject"
                icon
                variant="text"
                density="comfortable"
                position="absolute"
                :title="$gettext('Remove')"
                @click.stop.prevent="onReject(m)"
              >
                <v-icon class="action-reject">mdi-close</v-icon>
              </v-btn>
              <div v-else-if="hasFaceMenu(m)" class="face-actions" data-testid="face-actions">
                <p-action-menu :items="() => getFaceActions(m)" button-class="input-menu" list-class="opacity-85"></p-action-menu>
              </div>
            </v-img>
            <v-card-actions class="meta pa-0">
              <v-btn
                v-if="m.Invalid"
                :disabled="busy"
                size="large"
                variant="flat"
                block
                :rounded="false"
                class="action-undo text-center"
                :title="$gettext('Undo')"
                @click.stop="onApprove(m)"
              >
                <v-icon>mdi-undo</v-icon>
              </v-btn>
              <v-text-field
                v-else-if="m.SubjUID"
                v-model="m.Name"
                :rules="[textRule]"
                :disabled="busy"
                :readonly="true"
                autocomplete="off"
                autocorrect="off"
                hide-details
                single-line
                clearable
                persistent-clear
                clear-icon="mdi-eject"
                density="comfortable"
                class="input-name pa-0 ma-0"
                @click:clear="onClearSubject(m)"
              ></v-text-field>
              <v-combobox
                v-else
                v-model:search="m.Name"
                :items="$config.values.people"
                item-title="Name"
                item-value="Name"
                :disabled="busy"
                :menu-props="menuProps"
                return-object
                hide-no-data
                hide-details
                single-line
                open-on-clear
                append-icon=""
                prepend-inner-icon="mdi-account-plus"
                density="comfortable"
                class="input-name pa-0 ma-0 text-selectable"
                @update:model-value="(person) => onSetPerson(m, person)"
                @blur="(ev) => onSetName(m, ev)"
                @keyup.enter="(ev) => onSetName(m, ev)"
              >
              </v-combobox>
            </v-card-actions>
          </v-card>
        </div>
      </div>
    </div>
    <p-confirm-dialog
      :visible="confirm.visible"
      icon="mdi-account-plus"
      :icon-size="42"
      :text="confirm?.model?.Name ? $gettext('Add %{s}?', { s: confirm.model.Name }) : $gettext('Add person?')"
      @close="onCancelSetName"
      @confirm="onConfirmSetName"
    ></p-confirm-dialog>
  </div>
</template>

<script>
import Marker from "model/marker";
import Subject from "model/subject";
import PConfirmDialog from "component/confirm/dialog.vue";
import PActionMenu from "component/action/menu.vue";

const SUBJECT_NOT_FOUND = "subject-not-found";

export default {
  name: "PTabPhotoPeople",
  components: { PConfirmDialog, PActionMenu },
  props: {
    uid: {
      type: String,
      default: "",
    },
  },
  data() {
    const view = this.$view.getData();
    return {
      view,
      markers: view.model.getMarkers(true),
      busy: false,
      disabled: !this.$config.feature("edit"),
      config: this.$config.values,
      readonly: this.$config.get("readonly"),
      confirm: {
        visible: false,
        model: new Marker(),
        text: this.$gettext("Add person?"),
      },
      menuProps: {
        openOnClick: false,
        openOnFocus: true,
        closeOnBack: true,
        closeOnContentClick: true,
        disableInitialFocus: true,
        persistent: false,
        scrim: true,
        openDelay: 0,
        closeDelay: 0,
        opacity: 0,
        density: "compact",
        maxHeight: 300,
        locationStrategy: "connected",
        scrollStrategy: "reposition",
        origin: "auto",
      },
      textRule: (v) => {
        if (!v || !v.length) {
          return this.$gettext("Name");
        }

        return v.length <= this.$config.get("clip") || this.$gettext("Name too long");
      },
    };
  },
  watch: {
    uid: function () {
      this.refresh();
    },
  },
  methods: {
    refresh() {
      if (this.view.model) {
        this.markers = this.view.model.getMarkers(true);
      }
    },
    onReject(model) {
      if (this.busy || !model) return;

      this.busy = true;
      this.$notify.blockUI("busy");

      model.reject().finally(() => {
        this.$notify.unblockUI();
        this.busy = false;
      });
    },
    findPerson(uid) {
      const people = this.$config?.values?.people;

      if (!uid || !Array.isArray(people)) {
        return null;
      }

      return people.find((person) => person.UID === uid) || null;
    },
    updatePersonList(subject) {
      if (!subject) {
        return;
      }

      const people = this.$config?.values?.people;

      if (!Array.isArray(people)) {
        return;
      }

      const data = subject.getValues();
      const index = people.findIndex((person) => person.UID === subject.UID);
      if (index >= 0) {
        people[index] = Object.assign({}, people[index], data);
      } else {
        people.push(data);
      }
    },
    hasFaceMenu(marker) {
      return this.getFaceActions(marker).some((action) => action.visible);
    },
    getFaceActions(marker) {
      const assigned = !!marker?.SubjUID;
      const invalid = !!marker?.Invalid;
      const disabled = this.busy || this.disabled;

      return [
        {
          name: "go-to-person",
          /* icon: "mdi-account-search", */
          text: this.$gettext("Browse Pictures"),
          visible: assigned && !invalid,
          disabled,
          click: () => this.onGoToPerson(marker),
        },
        {
          name: "set-person-cover",
          /* icon: "mdi-account-check", */
          text: this.$gettext("Set as Cover Image"),
          visible: assigned && !invalid && !!marker?.Thumb,
          disabled,
          click: () => this.onSetPersonCover(marker),
        },
      ];
    },
    loadSubject(uid) {
      return new Subject({ UID: uid }).find(uid).catch((err) => {
        console.error("faces: failed loading subject", err);
        return null;
      });
    },
    onGoToPerson(marker) {
      if (!marker?.SubjUID) {
        return Promise.resolve();
      }

      const cached = this.findPerson(marker.SubjUID);
      const subjectPromise = cached
        ? Promise.resolve(new Subject(cached))
        : this.loadSubject(marker.SubjUID).then((subject) => {
            if (!subject) {
              this.$notify.error(this.$gettext("Person not found"));
              return null;
            }
            this.updatePersonList(subject);
            return subject;
          });

      return subjectPromise
        .then((subject) => {
          if (!subject) {
            return;
          }
          const route = subject.route("all");
          const resolved = this.$router.resolve(route);
          this.$util.openUrl(resolved.href);
        })
        .catch((err) => {
          if (!err || err.message !== SUBJECT_NOT_FOUND) {
            console.error("faces: failed opening person", err);
          }
        });
    },
    onSetPersonCover(marker) {
      if (this.busy || !marker?.SubjUID || !marker?.Thumb) {
        return Promise.resolve();
      }

      this.busy = true;
      this.$notify.blockUI("busy");

      const cached = this.findPerson(marker.SubjUID);
      const subjectPromise = cached
        ? Promise.resolve(new Subject(cached))
        : this.loadSubject(marker.SubjUID).then((subject) => {
            if (!subject) {
              this.$notify.error(this.$gettext("Person not found"));
              return null;
            }
            return subject;
          });

      return subjectPromise
        .then((subject) => {
          if (!subject) {
            return null;
          }
          return subject.setCover(marker.Thumb);
        })
        .then((updated) => {
          this.updatePersonList(updated);
          this.$notify.success(this.$gettext("Person cover updated"));
        })
        .catch((err) => {
          if (err) {
            console.error("faces: failed setting person cover", err);
            this.$notify.error(this.$gettext("Could not update person cover"));
          }
        })
        .finally(() => {
          this.$notify.unblockUI();
          this.busy = false;
        });
    },
    onApprove(model) {
      if (this.busy || !model) return;

      this.busy = true;

      model.approve().finally(() => (this.busy = false));
    },
    onClearSubject(model) {
      if (this.busy || !model) return;

      this.busy = true;
      this.$notify.blockUI("busy");

      model.clearSubject(model).finally(() => {
        this.$notify.unblockUI();
        this.busy = false;
      });
    },
    onSetPerson(model, person) {
      if (typeof person === "object" && model?.UID && person?.UID && person?.Name) {
        model.Name = person.Name;
        model.SubjUID = person.UID;
        this.setName(model);
      }

      return true;
    },
    onSetName(model, ev) {
      if (this.busy || !model) {
        return;
      }

      // If there's a pending confirmation for a different face, don't process new input
      if (this.confirm.visible && this.confirm.model && this.confirm.model.UID !== model.UID) {
        return;
      }

      const name = model?.Name;

      if (!name) {
        this.onCancelSetName();
        return;
      }

      this.confirm.model = model;

      const people = this.$config.values?.people;

      if (people) {
        const found = people.find((person) => person.Name.localeCompare(name, "en", { sensitivity: "base" }) === 0);
        if (found) {
          model.Name = found.Name;
          model.SubjUID = found.UID;
          if (model.wasChanged()) {
            this.setName(model);
          }
          return;
        }
      }

      model.Name = name;
      model.SubjUID = "";

      if (ev && ev.key === "Enter" && !ev.isComposing && !ev.repeat) {
        this.setName(model);
      } else {
        this.confirm.visible = true;
      }
    },
    onConfirmSetName() {
      if (!this.confirm?.model?.Name) {
        return;
      }

      this.setName(this.confirm.model);
    },
    onCancelSetName() {
      if (this.confirm && this.confirm.model) {
        this.confirm.model.Name = "";
        this.confirm.model.SubjUID = "";
      }
      this.confirm.visible = false;
    },
    setName(model) {
      if (this.busy || !model) {
        return;
      }

      this.busy = true;
      this.$notify.blockUI("busy");

      return model.setName().finally(() => {
        this.$notify.unblockUI();
        this.busy = false;
        this.confirm.model = null;
        this.confirm.visible = false;
      });
    },
  },
};
</script>
