import Document from "../abstract/document.mjs";
import {mergeObject} from "../utils/helpers.mjs";
import * as fields from "../data/fields.mjs";
import * as CONST from "../constants.mjs";
import {ShapeData} from "../data/data.mjs";

/**
 * @import {DrawingData} from "./_types.mjs";
 * @import {DocumentPermissionTest} from "@common/abstract/_types.mjs";
 */

/**
 * The Drawing Document.
 * Defines the DataSchema and common behaviors for a Drawing which are shared between both client and server.
 * @extends {Document<DrawingData>}
 * @mixes DrawingData
 * @category Documents
 */
export default class BaseDrawing extends Document {

  /* ---------------------------------------- */
  /*  Model Configuration                     */
  /* ---------------------------------------- */

  /** @inheritdoc */
  static metadata = Object.freeze(mergeObject(super.metadata, {
    name: "Drawing",
    collection: "drawings",
    label: "DOCUMENT.Drawing",
    labelPlural: "DOCUMENT.Drawings",
    isEmbedded: true,
    permissions: {
      create: this.#canCreate,
      delete: "OWNER"
    },
    schemaVersion: "14.355"
  }, {inplace: false}));

  /** @override */
  static LOCALIZATION_PREFIXES = ["DOCUMENT", "DRAWING"];

  /* ---------------------------------------- */

  /** @inheritDoc */
  static defineSchema() {
    return {
      _id: new fields.DocumentIdField(),
      name: new fields.StringField({textSearch: true}),
      author: new fields.DocumentAuthorField(foundry.documents.BaseUser),
      shape: new fields.EmbeddedDataField(ShapeData),
      x: new fields.NumberField({required: true, nullable: false, initial: 0}),
      y: new fields.NumberField({required: true, nullable: false, initial: 0}),
      elevation: new fields.NumberField({required: true, nullable: false, initial: 0}),
      levels: new fields.SceneLevelsSetField(),
      sort: new fields.NumberField({required: true, integer: true, nullable: false, initial: 0}),
      rotation: new fields.AngleField(),
      bezierFactor: new fields.AlphaField({initial: 0, max: 0.5}),
      fillType: new fields.NumberField({
        required: true,
        nullable: false,
        initial: CONST.DRAWING_FILL_TYPES.NONE,
        choices: Object.values(CONST.DRAWING_FILL_TYPES),
        validationError: "must be a value in CONST.DRAWING_FILL_TYPES"
      }),
      fillColor: new fields.ColorField({nullable: false, initial: () => game.user?.color.css || "#ffffff"}),
      fillAlpha: new fields.AlphaField({initial: 0.5}),
      strokeWidth: new fields.NumberField({nullable: false, integer: true, initial: 8, min: 0}),
      strokeColor: new fields.ColorField({nullable: false, initial: () => game.user?.color.css || "#ffffff"}),
      strokeAlpha: new fields.AlphaField({initial: 1}),
      texture: new fields.FilePathField({categories: ["IMAGE"]}),
      text: new fields.StringField(),
      fontFamily: new fields.StringField({required: true, blank: true}),
      fontSize: new fields.NumberField({nullable: false, integer: true, min: 8, max: 256, initial: 48,
        validationError: "must be an integer between 8 and 256"}),
      textColor: new fields.ColorField({nullable: false, initial: "#ffffff"}),
      textAlpha: new fields.AlphaField(),
      hidden: new fields.BooleanField(),
      locked: new fields.BooleanField(),
      interface: new fields.BooleanField(),
      flags: new fields.DocumentFlagsField()
    };
  }

  /* ---------------------------------------- */

  /**
   * Validate whether the drawing has some visible content (as required by validation).
   * @param {Partial<Pick<DrawingData, "shape">> & Pick<DrawingData, "text"|"textAlpha"|"fillType"|"fillAlpha"
   *   |"strokeWidth"|"strokeAlpha">} data
   * @returns {boolean}
   * @internal
   */
  static _validateVisibleContent(data) {
    let isEmpty;
    switch ( data.shape?.type ) {
      case "r":
      case "e": isEmpty = (data.shape.width <= data.strokeWidth) || (data.shape.height <= data.strokeWidth); break;
      case "p": isEmpty = ((data.shape.width === 0) && (data.shape.height === 0)) || (data.shape.points.length < 4); break;
      case "c": isEmpty = (data.shape.radius <= (data.strokeWidth / 2)); break;
    }
    if ( isEmpty ) return false;
    const hasText = (data.text !== "") && (data.textAlpha > 0);
    const hasFill = (data.fillType !== CONST.DRAWING_FILL_TYPES.NONE) && (data.fillAlpha > 0);
    const hasLine = (data.strokeWidth > 0) && (data.strokeAlpha > 0);
    return hasText || hasFill || hasLine;
  }

  /* ---------------------------------------- */

  /** @inheritdoc */
  static validateJoint(data) {
    if ( !BaseDrawing._validateVisibleContent(data) ) {
      throw new Error(_loc("DRAWING.JointValidationError"));
    }
  }

  /* -------------------------------------------- */

  /** @override */
  static canUserCreate(user) {
    return user.hasPermission("DRAWING_CREATE");
  }

  /* ---------------------------------------- */

  /**
   * Is a user able to create a new Drawing?
   * @type {DocumentPermissionTest}
   */
  static #canCreate(user, doc) {
    if ( !user.isGM && (doc._source.author !== user.id) ) return false;
    return user.hasPermission("DRAWING_CREATE");
  }

  /* ---------------------------------------- */
  /*  Model Methods                           */
  /* ---------------------------------------- */

  /** @override */
  getUserLevel(user) {
    user ||= game.user;
    if ( this.pack ) return this.compendium.getUserLevel(user);
    if ( user.isGM || (user.id === this._source.author) ) return CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER;
    return CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE;
  }
}
