import Document from "@common/abstract/document.mjs";
import CanvasDocumentMixin from "./abstract/canvas-document.mjs";
import {BaseRegion, BaseScene, BaseUser} from "@common/documents/_module.mjs";
import RegionDocument from "./region.mjs";
import GridlessGrid from "@common/grid/gridless.mjs";
import {deepClone, mergeObject, setProperty} from "@client/utils/_module.mjs";
import * as CONST from "@common/constants.mjs";
import * as fields from "@common/data/fields.mjs";

/**
 * @import Scene from "./scene.mjs";
 * @import {RegionData} from "./_types.mjs";
 */

/**
 * @deprecated since v14
 * @ignore
 */
export class BaseMeasuredTemplate extends Document {

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

  /** @inheritdoc */
  static metadata = mergeObject(super.metadata, {
    name: "MeasuredTemplate",
    collection: "templates",
    label: "Measured Template",
    labelPlural: "Measured Templates",
    isEmbedded: true,
    permissions: {
      create: this.#canCreate,
      delete: "OWNER"
    },
    schemaVersion: "13.341"
  }, {inplace: false});

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

  /** @override */
  static get TYPES() {
    return this.metadata.coreTypes;
  }

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

  /** @override */
  static defineSchema() {
    return {
      _id: new fields.DocumentIdField(),
      author: new fields.DocumentAuthorField(BaseUser),
      t: new fields.StringField({required: true, choices: Object.values(CONST.MEASURED_TEMPLATE_TYPES),
        initial: "circle",
        validationError: "must be a value in CONST.MEASURED_TEMPLATE_TYPES",
        label: "DOCUMENT.FIELDS.type.label"
      }),
      x: new fields.NumberField({required: true, integer: true, nullable: false, initial: 0}),
      y: new fields.NumberField({required: true, integer: true, nullable: false, initial: 0}),
      elevation: new fields.NumberField({required: true, nullable: false, initial: 0}),
      sort: new fields.NumberField({required: true, integer: true, nullable: false, initial: 0}),
      distance: new fields.NumberField({required: true, nullable: false, initial: 0, min: 0,
        label: "MEASUREMENT.Distance"}),
      direction: new fields.AngleField({label: "MEASUREMENT.Direction"}),
      angle: new fields.AngleField({normalize: false, label: "MEASUREMENT.Angle"}),
      width: new fields.NumberField({required: true, nullable: false, initial: 0, min: 0, step: 0.01}),
      borderColor: new fields.ColorField({nullable: false, initial: "#000000", label: "Border Color"}),
      fillColor: new fields.ColorField({nullable: false, initial: () => game.user?.color.css || "#ffffff"}),
      texture: new fields.FilePathField({categories: ["IMAGE", "VIDEO"]}),
      hidden: new fields.BooleanField(),
      flags: new fields.DocumentFlagsField()
    };
  }

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

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

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

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

  /** @inheritDoc */
  getUserLevel(user) {
    user ||= game.user;
    if ( user.id === this._source.author ) return CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER;
    return super.getUserLevel(user);
  }

  /* -------------------------------------------- */
  /*  Deprecations and Compatibility              */
  /* -------------------------------------------- */

  /** @inheritDoc */
  static migrateData(source, options) {
    /** @deprecated since 12 */
    this._addDataFieldMigration(source, "user", "author");
    return super.migrateData(source, options);
  }

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

  /** @inheritDoc */
  static shimData(data, options) {
    /** @deprecated since 12 */
    this._addDataFieldShim(data, "user", "author", {since: 12, until: 16});
    return super.shimData(data, options);
  }

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

  /**
   * @deprecated since 12
   * @ignore
   */
  get user() {
    this.constructor._logDataFieldMigration("user", "author", {since: 12, until: 16});
    return this.author;
  }
}

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

/**
 * @deprecated since v14
 * @ignore
 */
export default class MeasuredTemplateDocument extends CanvasDocumentMixin(BaseMeasuredTemplate) {
  constructor(...args) {
    const msg = "MeasuredTemplateDocument is deprecated because it has been merged into the functionality of the Region document.";
    foundry.utils.logCompatibilityWarning(msg, {since: 14, until: 16, once: true});
    super(...args);
  }

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

  /**
   * @param {RegionDocument} region
   * @returns {MeasuredTemplateDocument}
   * @internal
   */
  static _fromRegion(region) {
    const shape = region.shapes.at(0);
    let grid = this.parent?.grid ?? BaseScene.defaultGrid;
    if ( !shape?.gridBased && !grid.isGridless ) grid = new GridlessGrid({size: grid.size, distance: grid.distance});
    const distancePixels = grid.size / grid.distance;
    let t = "circle";
    let x = 0;
    let y = 0;
    let distance = 0;
    let direction = 0;
    let angle = 0;
    let width = 0;
    switch ( shape?.type ) {
      case "circle": {
        t = "circle";
        x = shape.x;
        y = shape.y;
        distance = shape.radius / distancePixels;
        direction = 0;
        break;
      }
      case "cone": {
        t = "cone";
        x = shape.x;
        y = shape.y;
        distance = shape.radius / distancePixels;
        direction = shape.rotation;
        angle = shape.angle;
        break;
      }
      case "rectangle": {
        t = "rect";
        x = shape.x;
        y = shape.y;
        distance = grid.measurePath([{x: 0, y: 0}, {x: shape.width, y: shape.height}]).distance;
        let width = grid.measurePath([{x: 0, y: 0}, {x: shape.width, y: 0}]).distance;
        let height = grid.measurePath([{x: 0, y: 0}, {x: 0, y: shape.height}]).distance;
        const rotation = shape.rotation.toNearest(90, "floor");
        if ( rotation === 90 ) [width, height] = [-height, width];
        else if ( rotation === 180 ) [width, height] = [-width, -height];
        else if ( rotation === 270 ) [width, height] = [height, -width];
        direction = Math.toDegrees(Math.atan2(height, width));
        break;
      }
      case "line": {
        t = "ray";
        x = shape.x;
        y = shape.y;
        distance = shape.length / distancePixels;
        direction = shape.rotation;
        width = shape.width;
        break;
      }
    }
    return this.implementation.fromSource({
      _id: region._id, author: Object.keys(region.ownership).filter(k => k !== "default")[0],
      t, x, y, elevation: region.elevation.bottom, sort: 0,
      distance, direction, angle, width, borderColor: "#000000",
      fillColor: region.color, texture: null,
      hidden: region.visibility !== CONST.REGION_VISIBILITY.ALWAYS,
      flags: deepClone(region.flags)
    }, {parent: region.parent});
  }

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

  /**
   * @param {object} data
   * @param {Scene} scene
   * @returns {RegionData}
   */
  static #migrateData(data, scene) {
    data = BaseRegion._migrateMeasuredTemplateData(data, {
      grid: scene?.grid ?? BaseScene.defaultGrid,
      gridTemplates: game.settings.get("core", "gridTemplates"),
      coneTemplateType: game.settings.get("core", "coneTemplateType"),
      users: game.users.contents
    });
    return data;
  }

  /* -------------------------------------------- */
  /*  Database Operations                         */
  /* -------------------------------------------- */

  /** @override */
  static async createDocuments(data=[], operation={}) {
    const scene = await this.database._getParent(operation);
    data = data.map(d => {
      d = this.migrateData(d);
      d = this.cleanData(d);
      d = MeasuredTemplateDocument.#migrateData(d, scene);
      setProperty(d, "flags.core.MeasuredTemplate", true);
      return d;
    });
    const created = await RegionDocument.implementation.createDocuments(data, operation);
    return created.map(region => this._fromRegion(region));
  }

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

  /** @override */
  static async updateDocuments(updates=[], operation={}) {
    const scene = await this.database._getParent(operation);
    updates = updates.reduce((updates, data) => {
      const region = scene?.regions.get(data._id);
      if ( region?.getFlag("core", "MeasuredTemplate") ) return updates;
      data = this.migrateData(data);
      data = this.cleanData(data, {partial: true});
      data = mergeObject(this._fromRegion(region)._source, data, {inplace: false});
      data = MeasuredTemplateDocument.#migrateData(data, scene);
      updates.push(data);
      return updates;
    }, []);
    const updated = await RegionDocument.implementation.updateDocuments(updates, operation);
    return updated.map(region => this._fromRegion(region));
  }

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

  /** @override */
  static async deleteDocuments(ids, operation) {
    const scene = await this.database._getParent(operation);
    const collection = scene.templates;
    if ( operation.deleteAll ) ids = Array.from(collection.index?.keys() ?? collection.keys());
    else ids = ids.filter(id => collection.has(id));
    const deleted = await RegionDocument.implementation.deleteDocuments(ids, operation);
    return deleted.map(region => this._fromRegion(region));
  }
}
