///////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2002-2026, Open Design Alliance (the "Alliance").
// All rights reserved.
//
// This software and its documentation and related materials are owned by
// the Alliance. The software may only be incorporated into application
// programs owned by members of the Alliance, subject to a signed
// Membership Agreement and Supplemental Software License Agreement with the
// Alliance. The structure and organization of this software are the valuable
// trade secrets of the Alliance and its suppliers. The software is also
// protected by copyright law and international treaty provisions. Application
// programs incorporating this software must include the following statement
// with their copyright notices:
//
//   This application incorporates Open Design Alliance software pursuant to a
//   license agreement with Open Design Alliance.
//   Open Design Alliance Copyright (C) 2002-2026 by Open Design Alliance.
//   All rights reserved.
//
// By use of this software, its documentation or related materials, you
// acknowledge and accept the above terms.
///////////////////////////////////////////////////////////////////////////////

import { IHttpClient } from "./IHttpClient";
import { Endpoint } from "./Endpoint";
import { FetchError } from "./FetchError";
import { userFullName, userInitials } from "./Utils";

/**
 * Provides properties and methods for obtaining information about a Open Cloud Server user and manage
 * its data.
 */
export class User extends Endpoint {
  private _data: any;

  /**
   * @param data - Raw user data received from the server. For more information, see
   *   {@link https://cloud.opendesign.com/docs//pages/server/api.html#Users | Open Cloud Users API}.
   * @param httpClient - HTTP client instance used to send requests to the REST API server.
   */
  constructor(data: any, httpClient: IHttpClient) {
    super("", httpClient);
    this.data = data;
  }

  /**
   * User avatar image URL or empty string if the user does not have an avatar. Use
   * {@link setAvatar | setAvatar()} to change avatar image.
   *
   * @readonly
   */
  get avatarUrl(): string {
    return this._data.avatarUrl;
  }

  /**
   * `true` if user is allowed to create a projects.
   *
   * Only administrators can change create project permission.
   */
  get canCreateProject(): boolean {
    return this.data.canCreateProject;
  }

  set canCreateProject(value: boolean) {
    this._data.canCreateProject = value;
  }

  /**
   * Account registration time (UTC) in the format specified in
   * {@link https://www.wikipedia.org/wiki/ISO_8601 | ISO 8601}.
   *
   * @readonly
   */
  get createAt(): string {
    return this.data.createAt;
  }

  /**
   * User custom fields object, to store custom data.
   */
  get customFields(): any {
    return this.data.customFields;
  }

  set customFields(value: any) {
    this._data.customFields = value;
  }

  /**
   * Raw user data received from the server. For more information, see
   * {@link https://cloud.opendesign.com/docs//pages/server/api.html#Users | Open Cloud Users API}.
   *
   * @readonly
   */

  get data(): any {
    return this._data;
  }

  private set data(value: any) {
    this._data = value;
    this._data.avatarUrl = value.avatarImage
      ? `${this.httpClient.serverUrl}/users/${this._data.id}/avatar?updated=${value.lastModified}`
      : "";
    this._data.fullName = userFullName(this._data);
    this._data.initials = userInitials(this._data.fullName);
  }

  /**
   * User email.
   */
  get email(): string {
    return this.data.email;
  }

  set email(value: string) {
    this._data.email = value;
  }

  /**
   * The user's email confirmation code, or an empty string if the email has already been confirmed.
   *
   * To send the confirmation code to the server, use
   * {@link Client.confirmUserEmail | Client.confirmUserEmail()}.
   *
   * @readonly
   */
  get emailConfirmationId(): string {
    return this.data.emailConfirmationId;
  }

  /**
   * First name.
   */
  get firstName(): string {
    return this.data.firstName;
  }

  set firstName(value: string) {
    this._data.firstName = value;
  }

  /**
   * Full name. Returns the user's first and last name. If first name and last names are empty, returns
   * the user name.
   *
   * @readonly
   */
  get fullName(): string {
    return this.data.fullName;
  }

  /**
   * Unique user ID.
   *
   * @readonly
   */
  get id(): string {
    return this.data.id;
  }

  /**
   * User initials. Returns a first letters of the user's first and last names. If first name and last
   * names are empty, returns the first letter of the user name.
   *
   * @readonly
   */
  get initials(): string {
    return this.data.initials;
  }

  /**
   * `true` if user is an administrator.
   *
   * Only administrators can change user type.
   */
  get isAdmin(): boolean {
    return this.data.isAdmin;
  }

  set isAdmin(value: boolean) {
    this._data.isAdmin = value;
  }

  /**
   * `false` if the user has not yet confirmed his email address.
   *
   * @readonly
   */
  get isEmailConfirmed(): boolean {
    return this.data.isEmailConfirmed;
  }

  /**
   * User last update time (UTC) in the format specified in
   * {@link https://www.wikipedia.org/wiki/ISO_8601 | ISO 8601}.
   */
  get lastModified(): string {
    return this.data.lastModified;
  }

  /**
   * Last name.
   */
  get lastName(): string {
    return this.data.lastName;
  }

  set lastName(value: string) {
    this._data.lastName = value;
  }

  /**
   * User last sign in time (UTC) in the format specified in
   * {@link https://www.wikipedia.org/wiki/ISO_8601 | ISO 8601}.
   */
  get lastSignIn(): string {
    return this.data.lastSignIn;
  }

  /**
   * The maximum number of projects that a user can create.
   *
   * Only administrators can change projects limit.
   */
  get projectsLimit(): number {
    return this.data.projectsLimit;
  }

  set projectsLimit(value: number) {
    this._data.projectsLimit = value;
  }

  /**
   * The identity provider used to create the account. Can be `ldap`, `oauth`, `saml`, or empty for local
   * accounts.
   *
   * @readonly
   */
  get providerType(): string {
    return this.data.providerType;
  }

  /**
   * User storage size on the server for uploading files.
   *
   * Only administrators can change storage size.
   */
  get storageLimit(): number {
    return this.data.storageLimit;
  }

  set storageLimit(value: number) {
    this._data.storageLimit = value;
  }

  /**
   * The total size of the user's files in the storage.
   *
   * @readonly
   */
  get storageUsed(): number {
    return this.data.storageUsed;
  }

  /**
   * The user's access token (API key). Use {@link Client.signInWithToken | Client.signInWithToken()} to
   * sign in to the server using this token.
   *
   * @readonly
   */
  get token(): string {
    return this.data.tokenInfo.token;
  }

  /**
   * User name.
   */
  get userName(): string {
    return this.data.userName;
  }

  set userName(value: string) {
    this._data.userName = value;
  }

  /**
   * Reloads user data from the server.
   *
   * Only administrators can checkout other users. If the current logged in user is not an administrator,
   * they can only checkout themselves, otherwise an exception will be thrown.
   */
  async checkout(): Promise<this> {
    if (this.httpClient.signInUserIsAdmin) {
      const response = await this.get(`/users/${this.id}`);
      const data = await response.json();
      this.data = { id: data.id, ...data.userBrief };
    } else if (this.id === this.httpClient.signInUserId) {
      const response = await this.get("/user");
      const data = await response.json();
      this.data = { id: this.id, ...data };
    } else {
      return Promise.reject(new FetchError(403));
    }
    return this;
  }

  /**
   * Updates user data on the server.
   *
   * Only administrators can update other users. If the current logged in user is not an administrator,
   * they can only update themselves, otherwise an exception will be thrown.
   *
   * @param data - Raw user data. For more information, see
   *   {@link https://cloud.opendesign.com/docs//pages/server/api.html#Users | Open Cloud Users API}.
   */
  async update(data: any): Promise<this> {
    if (this.httpClient.signInUserIsAdmin) {
      const response = await this.put(`/users/${this.id}`, { isAdmin: data.isAdmin, userBrief: data });
      const newData = await response.json();
      this.data = { id: newData.id, ...newData.userBrief };
    } else if (this.id === this.httpClient.signInUserId) {
      const response = await this.put("/user", data);
      const newData = await response.json();
      this.data = { id: this.id, ...newData };
    } else {
      return Promise.reject(new FetchError(403));
    }
    return this;
  }

  /**
   * Deletes a user from the server.
   *
   * Only administrators can delete users. If the current logged in user is not an administrator, an
   * exception will be thrown.
   *
   * Administrators can delete themselves or other administrators. An administrator can only delete
   * themselves if they are not the last administrator.
   *
   * You need to re-login after deleting the current logged in user.
   *
   * @returns Returns the raw data of a deleted user. For more information, see
   *   {@link https://cloud.opendesign.com/docs//pages/server/api.html#Users | Open Cloud Users API}.
   */
  override delete(): Promise<any> {
    if (this.httpClient.signInUserIsAdmin) {
      return super
        .delete(`/users/${this.id}`)
        .then((response) => response.json())
        .then((data) => {
          if (this.id === this.httpClient.signInUserId) {
            delete this.httpClient.headers["Authorization"];
            this.httpClient.signInUserId = "";
            this.httpClient.signInUserIsAdmin = false;
          }
          return data;
        });
    } else {
      return Promise.reject(new FetchError(403));
    }
  }

  /**
   * Saves user properties changes to the server. Call this method to update user data on the server
   * after any property changes.
   */
  save(): Promise<this> {
    return this.update(this.data);
  }

  /**
   * Sets or removes the user avatar.
   *
   * Only administrators can set the avatar of other users. If the current logged in user is not an
   * administrator, they can only set their avatar, otherwise an exception will be thrown.
   *
   * @param image - Avatar image. Can be a
   *   {@link https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs | Data URL} string,
   *   {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer | ArrayBuffer},
   *   {@link https://developer.mozilla.org/docs/Web/API/Blob/Blob | Blob} or
   *   {@link https://developer.mozilla.org/docs/Web/API/File | Web API File} object. Setting the `image`
   *   to `null` will remove the avatar.
   */
  async setAvatar(image?: BodyInit | null): Promise<this> {
    if (!image) {
      await this.deleteAvatar();
    } else if (this.httpClient.signInUserIsAdmin) {
      const response = await this.post(`/users/${this.id}/avatar`, image);
      const data = await response.json();
      this.data = { id: data.id, ...data.userBrief };
    } else if (this.id === this.httpClient.signInUserId) {
      const response = await this.post("/user/avatar", image);
      const data = await response.json();
      this.data = { id: this.id, ...data };
    } else {
      return Promise.reject(new FetchError(403));
    }
    return this;
  }

  /**
   * Removes the user avatar.
   *
   * Only administrators can remove the avatar of other users. If the current logged in user is not an
   * administrator, they can only remove their avatar, otherwise an exception will be thrown.
   */
  async deleteAvatar(): Promise<this> {
    if (this.httpClient.signInUserIsAdmin) {
      const response = await super.delete(`/users/${this.id}/avatar`);
      const data = await response.json();
      this.data = { id: data.id, ...data.userBrief };
    } else if (this.id === this.httpClient.signInUserId) {
      const response = await super.delete("/user/avatar");
      const data = await response.json();
      this.data = { id: this.id, ...data };
    } else {
      return Promise.reject(new FetchError(403));
    }
    return this;
  }

  /**
   * Changes the user password.
   *
   * Only administrators can change the passwords of other users. If the current logged in user is not an
   * administrator, they can only change their password, otherwise an exception will be thrown.
   *
   * To change their password, non-administrator users must specify their old password.
   *
   * @param newPassword - New user password. Cannot be empty. Password can only contain Latin letters
   *   (a-z, A-Z), numbers (0-9), and special characters (~!@#$%^&*()_-+={}[]<>|/'":;.,?).
   * @param oldPassword - Old user password. Only required for non-administrator users to change their
   *   password.
   */
  async changePassword(newPassword: string, oldPassword?: string): Promise<this> {
    if (this.httpClient.signInUserIsAdmin) {
      const response = await this.put(`/users/${this.id}/password`, { new: newPassword });
      const data = await response.json();
      this.data = { id: data.id, ...data.userBrief };
    } else if (this.id === this.httpClient.signInUserId) {
      const response = await this.put("/user/password", { old: oldPassword, new: newPassword });
      const data = await response.json();
      this.data = { id: this.id, ...data };
    } else {
      return Promise.reject(new FetchError(403));
    }
    return this;
  }
}
