import TokenDocument from "@client/documents/token.mjs";
import BaseLightSource from "./base-light-source.mjs";
import PointEffectSourceMixin from "./point-effect-source.mjs";

/**
 * @import {CanvasVisibilityTestConfiguration} from "../../_types.mjs";
 */

/**
 * A specialized subclass of the BaseLightSource which renders a source of light as a point-based effect.
 * @extends {BaseLightSource}
 * @mixes PointEffectSourceMixin
 */
export default class PointLightSource extends PointEffectSourceMixin(BaseLightSource) {

  /** @override */
  static effectsCollection = "lightSources";

  /** @override */
  get requiresEdges() {
    return this.priority > 0;
  }

  /* -------------------------------------------- */
  /*  Source Suppression Management               */
  /* -------------------------------------------- */

  /**
   * Update darkness suppression according to darkness sources collection.
   */
  #updateDarknessSuppression() {
    const condition = darknessSource => this.priority <= darknessSource.priority;
    this.suppression.darkness = canvas.effects.testInsideDarkness(this.origin, {condition});
  }

  /* -------------------------------------------- */
  /*  Light Source Initialization                 */
  /* -------------------------------------------- */

  /** @inheritDoc */
  _initialize(data) {
    super._initialize(data);
    Object.assign(this.data, {
      radius: Math.max(this.data.dim ?? 0, this.data.bright ?? 0)
    });
  }

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

  /** @inheritDoc */
  _createShapes() {
    this.#updateDarknessSuppression();
    super._createShapes();
  }

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

  /** @inheritDoc */
  _configure(changes) {
    this.ratio = Math.clamp(Math.abs(this.data.bright) / this.data.radius, 0, 1);
    super._configure(changes);
  }

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

  /** @inheritDoc */
  _getPolygonConfiguration() {
    return Object.assign(super._getPolygonConfiguration(), {
      useThreshold: true,
      surfaceExposure: {threshold: canvas.grid.distance}
    });
  }

  /* -------------------------------------------- */
  /*  Edge Management                             */
  /* -------------------------------------------- */

  /** @override */
  _getEdgeCreationOptions() {
    return {
      type: "source",
      object: this.object,
      direction: CONST.EDGE_DIRECTIONS.LEFT,
      darkness: CONST.EDGE_SENSE_TYPES.NORMAL,
      priority: this.data.priority - 0.5 // Ensure that light loses to darkness at the same priority
    };
  }

  /* -------------------------------------------- */
  /*  Visibility Testing                          */
  /* -------------------------------------------- */

  /** @inheritDoc */
  testPoint(point) {

    // Magical light sources are vertically unbounded and aren't affected by surfaces
    if ( this.data.priority > 0 ) {
      const shape = this.shape;
      if ( !shape ) return false;
      return shape.contains(point.x, point.y);
    }

    return super.testPoint(point);
  }

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

  /**
   * Test whether this LightSource provides visibility to see a certain target object.
   * @param {CanvasVisibilityTestConfiguration} config    The visibility test configuration
   * @returns {boolean}                                   Is the target object visible to this source?
   */
  testVisibility({tests, level, object}) {
    if ( !(this.data.vision && this._canDetectObject(object, level)) ) return false;
    return tests.some(test => test.level.isView && this.testPoint(test.point));
  }

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

  /**
   * Can this LightSource theoretically detect a certain object based on its properties?
   * This check should not consider the relative positions of either object, only their state.
   * @param {PlaceableObject} target      The target object being tested
   * @param {Level} level                 The level the target is in
   * @returns {boolean}                   Can the target object theoretically be detected by this vision source?
   * @protected
   */
  _canDetectObject(target, level) {
    const tgt = target?.document;
    const isInvisible = ((tgt instanceof TokenDocument) && tgt.hasStatusEffect(CONFIG.specialStatusEffects.INVISIBLE));
    return !isInvisible;
  }
}
