/**
 * @license
 * Copyright 2024 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import {CommentMove} from '../events/events_comment_move.js';
import {CommentResize} from '../events/events_comment_resize.js';
import {EventType} from '../events/type.js';
import * as eventUtils from '../events/utils.js';
import {Coordinate} from '../utils/coordinate.js';
import * as idGenerator from '../utils/idgenerator.js';
import {Size} from '../utils/size.js';
import {Workspace} from '../workspace.js';
import {CommentView} from './comment_view.js';

export class WorkspaceComment {
  /** The unique identifier for this comment. */
  public readonly id: string;

  /** The text of the comment. */
  private text = '';

  /** The size of the comment in workspace units. */
  private size: Size;

  /** Whether the comment is collapsed or not. */
  private collapsed = false;

  /** Whether the comment is editable or not. */
  private editable = true;

  /** Whether the comment is movable or not. */
  private movable = true;

  /** Whether the comment is deletable or not. */
  private deletable = true;

  /** The location of the comment in workspace coordinates. */
  protected location = new Coordinate(0, 0);

  /** Whether this comment has been disposed or not. */
  protected disposed = false;

  /** Whether this comment is being disposed or not. */
  protected disposing = false;

  /**
   * Constructs the comment.
   *
   * @param workspace The workspace to construct the comment in.
   * @param id An optional ID to give to the comment. If not provided, one will
   *     be generated.
   */
  constructor(
    public readonly workspace: Workspace,
    id?: string,
  ) {
    this.id = id && !workspace.getCommentById(id) ? id : idGenerator.genUid();
    this.size = CommentView.defaultCommentSize;

    workspace.addTopComment(this);

    this.fireCreateEvent();
  }

  private fireCreateEvent() {
    if (eventUtils.isEnabled()) {
      eventUtils.fire(new (eventUtils.get(EventType.COMMENT_CREATE))(this));
    }
  }

  private fireDeleteEvent() {
    if (eventUtils.isEnabled()) {
      eventUtils.fire(new (eventUtils.get(EventType.COMMENT_DELETE))(this));
    }
  }

  /** Fires a comment change event. */
  private fireChangeEvent(oldText: string, newText: string) {
    if (eventUtils.isEnabled()) {
      eventUtils.fire(
        new (eventUtils.get(EventType.COMMENT_CHANGE))(this, oldText, newText),
      );
    }
  }

  /** Fires a comment collapse event. */
  private fireCollapseEvent(newCollapsed: boolean) {
    if (eventUtils.isEnabled()) {
      eventUtils.fire(
        new (eventUtils.get(EventType.COMMENT_COLLAPSE))(this, newCollapsed),
      );
    }
  }

  /** Sets the text of the comment. */
  setText(text: string) {
    const oldText = this.text;
    this.text = text;
    this.fireChangeEvent(oldText, text);
  }

  /** Returns the text of the comment. */
  getText(): string {
    return this.text;
  }

  /** Sets the comment's size in workspace units. */
  setSize(size: Size) {
    const event = new (eventUtils.get(EventType.COMMENT_RESIZE))(
      this,
    ) as CommentResize;

    this.size = size;

    event.recordCurrentSizeAsNewSize();
    eventUtils.fire(event);
  }

  /** Returns the comment's size in workspace units. */
  getSize(): Size {
    return this.size;
  }

  /** Sets whether the comment is collapsed or not. */
  setCollapsed(collapsed: boolean) {
    this.collapsed = collapsed;
    this.fireCollapseEvent(collapsed);
  }

  /** Returns whether the comment is collapsed or not. */
  isCollapsed(): boolean {
    return this.collapsed;
  }

  /** Sets whether the comment is editable or not. */
  setEditable(editable: boolean) {
    this.editable = editable;
  }

  /**
   * Returns whether the comment is editable or not, respecting whether the
   * workspace is read-only.
   */
  isEditable(): boolean {
    return this.isOwnEditable() && !this.workspace.isReadOnly();
  }

  /**
   * Returns whether the comment is editable or not, only examining its own
   * state and ignoring the state of the workspace.
   */
  isOwnEditable(): boolean {
    return this.editable;
  }

  /** Sets whether the comment is movable or not. */
  setMovable(movable: boolean) {
    this.movable = movable;
  }

  /**
   * Returns whether the comment is movable or not, respecting whether the
   * workspace is read-only.
   */
  isMovable() {
    return (
      this.isOwnMovable() &&
      !this.workspace.isReadOnly() &&
      !this.workspace.isFlyout
    );
  }

  /**
   * Returns whether the comment is movable or not, only examining its own
   * state and ignoring the state of the workspace.
   */
  isOwnMovable() {
    return this.movable;
  }

  /** Sets whether the comment is deletable or not. */
  setDeletable(deletable: boolean) {
    this.deletable = deletable;
  }

  /**
   * Returns whether the comment is deletable or not, respecting whether the
   * workspace is read-only.
   */
  isDeletable(): boolean {
    return (
      this.isOwnDeletable() &&
      !this.isDeadOrDying() &&
      !this.workspace.isReadOnly() &&
      !this.workspace.isFlyout
    );
  }

  /**
   * Returns whether the comment is deletable or not, only examining its own
   * state and ignoring the state of the workspace.
   */
  isOwnDeletable(): boolean {
    return this.deletable;
  }

  /** Moves the comment to the given location in workspace coordinates. */
  moveTo(location: Coordinate, reason?: string[] | undefined) {
    const event = new (eventUtils.get(EventType.COMMENT_MOVE))(
      this,
    ) as CommentMove;
    if (reason) event.setReason(reason);

    this.location = location;

    event.recordNew();
    eventUtils.fire(event);
  }

  /** Returns the position of the comment in workspace coordinates. */
  getRelativeToSurfaceXY(): Coordinate {
    return this.location.clone();
  }

  /** Disposes of this comment. */
  dispose() {
    this.disposing = true;
    this.fireDeleteEvent();
    this.workspace.removeTopComment(this);
    this.disposed = true;
  }

  /** Returns whether the comment has been disposed or not. */
  isDisposed() {
    return this.disposed;
  }

  /**
   * Returns true if this comment view is currently being disposed or has
   * already been disposed.
   */
  isDeadOrDying(): boolean {
    return this.disposing || this.disposed;
  }
}
