<template>
  <section class="section container">
    <h1 class="title">{{ $t("Create a new group") }}</h1>

    <b-message type="is-danger" v-for="(value, index) in errors" :key="index">
      {{ value }}
    </b-message>

    <form @submit.prevent="createGroup">
      <b-field :label="$t('Group display name')" label-for="group-display-name">
        <b-input
          aria-required="true"
          required
          v-model="group.name"
          id="group-display-name"
        />
      </b-field>

      <div class="field">
        <label class="label" for="group-preferred-username">{{
          $t("Federated Group Name")
        }}</label>
        <div class="field-body">
          <b-field
            :message="preferredUsernameErrors[0]"
            :type="preferredUsernameErrors[1]"
          >
            <b-input
              ref="preferredUsernameInput"
              aria-required="true"
              required
              expanded
              v-model="group.preferredUsername"
              pattern="[a-z0-9_]+"
              id="group-preferred-username"
              :useHtml5Validation="true"
              :validation-message="
                group.preferredUsername
                  ? $t(
                      'Only alphanumeric lowercased characters and underscores are supported.'
                    )
                  : null
              "
            />
            <p class="control">
              <span class="button is-static">@{{ host }}</span>
            </p>
          </b-field>
        </div>
        <p
          v-html="
            $t(
              'This is like your federated username (<code>{username}</code>) for groups. It will allow the group to be found on the federation, and is guaranteed to be unique.',
              { username: usernameWithDomain(currentActor, true) }
            )
          "
        />
      </div>

      <b-field
        :label="$t('Description')"
        label-for="group-summary"
        :message="summaryErrors[0]"
        :type="summaryErrors[1]"
      >
        <b-input v-model="group.summary" type="textarea" id="group-summary" />
      </b-field>

      <div>
        <b>{{ $t("Avatar") }}</b>
        <picture-upload
          :textFallback="$t('Avatar')"
          v-model="avatarFile"
          :maxSize="avatarMaxSize"
        />
      </div>

      <div>
        <b>{{ $t("Banner") }}</b>
        <picture-upload
          :textFallback="$t('Banner')"
          v-model="bannerFile"
          :maxSize="bannerMaxSize"
        />
      </div>

      <button class="button is-primary" native-type="submit">
        {{ $t("Create my group") }}
      </button>
    </form>
  </section>
</template>

<script lang="ts">
import { Component, Watch } from "vue-property-decorator";
import { Group, IPerson, usernameWithDomain } from "@/types/actor";
import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "@/graphql/actor";
import { CREATE_GROUP } from "@/graphql/group";
import { mixins } from "vue-class-component";
import IdentityEditionMixin from "@/mixins/identityEdition";
import { MemberRole } from "@/types/enums";
import RouteName from "../../router/name";
import { convertToUsername } from "../../utils/username";
import PictureUpload from "../../components/PictureUpload.vue";
import { CONFIG } from "@/graphql/config";
import { IConfig } from "@/types/config.model";
import { ErrorResponse } from "@/types/errors.model";
import { ServerParseError } from "@apollo/client/link/http";
import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core";

@Component({
  components: {
    PictureUpload,
  },
  apollo: {
    currentActor: {
      query: CURRENT_ACTOR_CLIENT,
    },
    config: CONFIG,
  },
  metaInfo() {
    return {
      title: this.$t("Create a new group") as string,
    };
  },
})
export default class CreateGroup extends mixins(IdentityEditionMixin) {
  currentActor!: IPerson;

  group = new Group();

  config!: IConfig;

  avatarFile: File | null = null;

  bannerFile: File | null = null;

  errors: string[] = [];

  fieldErrors: Record<string, string | undefined> = {
    preferred_username: undefined,
    summary: undefined,
  };

  usernameWithDomain = usernameWithDomain;

  async createGroup(): Promise<void> {
    try {
      this.errors = [];
      this.fieldErrors = { preferred_username: undefined, summary: undefined };
      await this.$apollo.mutate({
        mutation: CREATE_GROUP,
        variables: this.buildVariables(),
        update: (store: ApolloCache<InMemoryCache>, { data }: FetchResult) => {
          const query = {
            query: PERSON_MEMBERSHIPS,
            variables: {
              id: this.currentActor.id,
            },
          };
          const membershipData = store.readQuery<{ person: IPerson }>(query);
          if (!membershipData) return;
          const { person } = membershipData;
          person.memberships.elements.push({
            parent: data?.createGroup,
            role: MemberRole.ADMINISTRATOR,
            actor: this.currentActor,
            insertedAt: new Date().toString(),
            updatedAt: new Date().toString(),
          });
          store.writeQuery({ ...query, data: { person } });
        },
      });

      await this.$router.push({
        name: RouteName.GROUP,
        params: { preferredUsername: usernameWithDomain(this.group) },
      });

      this.$notifier.success(
        this.$t("Group {displayName} created", {
          displayName: this.group.displayName(),
        }) as string
      );
    } catch (err: any) {
      this.handleError(err);
    }
  }

  // eslint-disable-next-line class-methods-use-this
  get host(): string {
    return window.location.hostname;
  }

  get avatarMaxSize(): number | undefined {
    return this?.config?.uploadLimits?.avatar;
  }

  get bannerMaxSize(): number | undefined {
    return this?.config?.uploadLimits?.banner;
  }

  @Watch("group.name")
  updateUsername(groupName: string): void {
    this.group.preferredUsername = convertToUsername(groupName);
  }

  private buildVariables() {
    let avatarObj = {};
    let bannerObj = {};

    if (this.avatarFile) {
      avatarObj = {
        avatar: {
          media: {
            name: this.avatarFile.name,
            alt: `${this.group.preferredUsername}'s avatar`,
            file: this.avatarFile,
          },
        },
      };
    }

    if (this.bannerFile) {
      bannerObj = {
        banner: {
          media: {
            name: this.bannerFile.name,
            alt: `${this.group.preferredUsername}'s banner`,
            file: this.bannerFile,
          },
        },
      };
    }

    return {
      ...this.group,
      ...avatarObj,
      ...bannerObj,
    };
  }

  private handleError(err: ErrorResponse) {
    if (err?.networkError?.name === "ServerParseError") {
      const error = err?.networkError as ServerParseError;

      if (error?.response?.status === 413) {
        this.errors.push(
          this.$t(
            "Unable to create the group. One of the pictures may be too heavy."
          ) as string
        );
      }
    }
    err.graphQLErrors?.forEach((error) => {
      if (error.field) {
        if (Array.isArray(error.message)) {
          this.fieldErrors[error.field] = error.message[0];
        } else {
          this.fieldErrors[error.field] = error.message;
        }
      } else {
        this.errors.push(error.message);
      }
    });
  }

  get summaryErrors() {
    const message = this.fieldErrors.summary
      ? this.fieldErrors.summary
      : undefined;
    const type = this.fieldErrors.summary ? "is-danger" : undefined;
    return [message, type];
  }

  get preferredUsernameErrors() {
    const message = this.fieldErrors.preferred_username
      ? this.fieldErrors.preferred_username
      : this.$t(
          "Only alphanumeric lowercased characters and underscores are supported."
        );
    const type = this.fieldErrors.preferred_username ? "is-danger" : undefined;
    return [message, type];
  }
}
</script>

<style>
.markdown-render h1 {
  font-size: 2em;
}
</style>
