import { MODULE_ID } from './constants.js';
import { ImagePicker } from './ImagePicker.js';

const DEFAULT_VALUES = {
  pan: {
    max: 1,
    min: -1,
    step: 0.1,
  },
  zoom: {
    max: 2,
    min: 0.4,
    step: 0.1,
  },
  rotate: {
    max: 360,
    min: 0,
    step: 5,
  },
};

export class UnlimitedTerrain extends foundry.applications.api.HandlebarsApplicationMixin(
  foundry.applications.api.ApplicationV2
) {
  /**
   * @param {Array[Preset]} presets
   */
  constructor(placeable, utPaths = []) {
    super({});
    this._randomizerValues = DEFAULT_VALUES;
    this._history = {};
    this._placeable = placeable;
    this._utPaths = utPaths;
  }

  static DEFAULT_OPTIONS = {
    id: 'unlimited-terrain-controls',
    tag: 'form',
    form: {
      handler: UnlimitedTerrain._onSubmit,
      submitOnChange: true,
      closeOnSubmit: false,
    },
    window: {
      contentClasses: ['standard-form'],
      title: 'Unlimited Terrain Controls',
      resizable: true,
    },
    position: {
      width: 600,
      height: 'auto',
    },
    actions: {
      pan: UnlimitedTerrain._onPan,
      zoom: UnlimitedTerrain._onZoom,
      rotate: UnlimitedTerrain._onRotate,
      randomize: UnlimitedTerrain._onRandomize,
      animate: UnlimitedTerrain._onAnimate,
      undo: UnlimitedTerrain._onUndo,
      changeImage: UnlimitedTerrain._onChangeImage,
      reset: UnlimitedTerrain._onReset,
      disable: UnlimitedTerrain._onDisable,
    },
  };

  /**
   * Handles pan button clicks
   * @param {PointerEvent} event
   * @param {HTMLElement} target
   */
  static _onPan(event, target) {
    const direction = target.dataset.direction;
    const increment = this._randomizerValues.pan.step;

    this._modifyTMFXSpriteParam(function (oldParams, newParams) {
      let vec;

      if (direction === 'left') vec = [-1, 0];
      else if (direction === 'right') vec = [1, 0];
      else if (direction === 'up') vec = [0, -1];
      else if (direction === 'down') vec = [0, 1];

      vec = UnlimitedTerrain._rotateVector(vec, oldParams.rotation);
      newParams.translationX = oldParams.translationX + (vec[0] * increment) / oldParams.scaleX;
      newParams.translationY = oldParams.translationY + (vec[1] * increment) / oldParams.scaleY;
      if (oldParams.repeat) {
        newParams.translationX %= 1;
        newParams.translationY %= 1;
      }
    });
  }

  /**
   * Handles zoom button clicks
   * @param {PointerEvent} event
   * @param {HTMLElement} target
   */
  static _onZoom(event, target) {
    const increment = this._randomizerValues.zoom.step;
    const flip = target.dataset.direction === 'in' ? 1 : -1;

    this._modifyTMFXSpriteParam(function (oldParams, newParams) {
      newParams.scaleX = oldParams.scaleX + flip * (oldParams.scaleX * increment);
      newParams.scaleY = oldParams.scaleY + flip * (oldParams.scaleY * increment);
    });
  }

  /**
   * Handles rotate button clicks
   * @param {PointerEvent} event
   * @param {HTMLElement} target
   */
  static _onRotate(event, target) {
    const increment = this._randomizerValues.rotate.step;
    const flip = target.dataset.direction === 'clockwise' ? -1 : 1;

    this._modifyTMFXSpriteParam(function (oldParams, newParams) {
      newParams.rotation = (oldParams.rotation + flip * increment) % 360;
    });
  }

  /**
   * Handles randomizer button clicks
   * @param {PointerEvent} event
   * @param {HTMLElement} target
   */
  static _onRandomize(event, target) {
    const aspect = target.dataset.aspect;
    const randomizerValues = this._randomizerValues;

    this._modifyTMFXSpriteParam(function (oldParams, newParams) {
      if (aspect === 'pan' || aspect === 'all') {
        newParams.translationX = UnlimitedTerrain.getRandomNumber(randomizerValues.pan);
        newParams.translationY = UnlimitedTerrain.getRandomNumber(randomizerValues.pan);
      }
      if (aspect === 'zoom' || aspect === 'all') {
        newParams.scaleX = UnlimitedTerrain.getRandomNumber(randomizerValues.zoom);
        if (newParams.scaleX === 0) newParams.scaleX = 0.01;
        newParams.scaleY = newParams.scaleX;
      }
      if (aspect === 'rotate' || aspect === 'all') {
        newParams.rotation = UnlimitedTerrain.getRandomNumber(randomizerValues.rotate);
      }
    });
  }

  /**
   * Handles playback button clicks
   * @param {PointerEvent} event
   * @param {HTMLElement} target
   */
  static _onAnimate(event, target) {
    const aspect = target.dataset.aspect;
    const increment = 0.00005;

    this._modifyTMFXSpriteParam(function (oldParams, newParams) {
      const animated = oldParams.animated ?? { translationX: { active: false, speed: -0.0000012, animType: 'move' } };
      newParams.animated = foundry.utils.deepClone(animated);

      if (aspect === 'play' || aspect === 'pause') {
        Object.keys(animated).forEach((k) => {
          newParams.animated[k].active = aspect === 'play';
        });
      } else if (aspect === 'forward' || aspect === 'backward') {
        Object.keys(animated).forEach((k) => {
          if (animated[k].animType === 'move') {
            newParams.animated[k].speed = animated[k].speed + (aspect === 'forward' ? -increment : increment);
          }
        });
      }
    });
  }

  /**
   * Handles undo button clicks
   * @param {PointerEvent} event
   * @param {HTMLElement} target
   */
  static _onUndo(event, target) {
    const placeable = this.getPlaceable();
    if (!placeable) return;

    if (this._history[placeable.id]?.length) {
      const undoUpdate = this._history[placeable.id].pop();
      TokenMagic.addUpdateFilters(placeable, undoUpdate);
    }
  }

  /**
   * Handles change image button clicks
   * @param {PointerEvent} event
   * @param {HTMLElement} target
   */
  static _onChangeImage(event, target) {
    new ImagePicker(this._updateImage.bind(this), this._utPaths).render(true);
  }

  /**
   * Handles Reset to Defaults button clicks
   * @param {*} event
   * @param {*} target
   */
  static _onReset(event, target) {
    this._randomizerValues = DEFAULT_VALUES;
    this.render();
  }

  /**
   * Disables underlying tile
   * @param {*} event
   * @param {*} target
   */
  static async _onDisable(event, target) {
    if (this._placeable) {
      await this._placeable.document.setFlag('monks-active-tiles', 'active', false);
      ui.notifications.info('Tile interactivity disabled.');
      this.render(true);
    }
  }

  /**
   * Updates sprite's imagePath parameter
   * @param {String} src
   * @returns
   */
  async _updateImage(src) {
    if (!src) return;

    const placeable = this.getPlaceable();
    if (!placeable) return;

    // If the currently selected placeable does not have a 'base-terrain' filter we will apply it automatically
    if (!TokenMagic.hasFilterId(placeable, 'base-terrain')) {
      await placeable.document.update({
        texture: { src: 'modules/baileywiki-maps-premium-towns/maps/active-tiles/black-square.png' },
      });

      let params = [
        {
          filterType: 'sprite',
          filterId: 'base-terrain',
          repeat: true,
          alphaDiscard: true,
          inverse: true,
          gridPadding: 0,
          maintainAspectRatio: true,
          maintainScale: true,
          translationX: -1,
          translationY: -1,
          top: true,
          randomized: {
            active: true,
            imagePath: [src],
            scaleX: {
              val1: 2,
              val2: 2,
              step: 0.1,
              link: 'scaleY',
            },
          },
        },
      ];

      await TokenMagic.addUpdateFilters(placeable, params);
      return;
    }

    this._modifyTMFXSpriteParam(function (oldParams, newParams) {
      newParams.imagePath = src;
    });
  }

  static getRandomNumber({ min, max, step } = {}) {
    const stepsInRange = (max - min + (Number.isInteger(step) ? 1 : 0)) / step;
    return Math.floor(Math.random() * stepsInRange) * step + min;
  }

  _preserveAniProperties(obj, newParams, oldParams) {
    if (!oldParams.animated) return;

    const f = obj._TMFXgetSprite().filters?.find((f) => f.filterType === 'sprite' && f.filterId === oldParams.filterId);
    if (f?.anime?.puppet) {
      Object.keys(oldParams.animated).forEach((k) => {
        newParams[k] = f.anime.puppet[k];
      });
    }
  }

  static _rotateVector(vec, ang) {
    const prec = 10000;
    ang = -ang * (Math.PI / 180);
    var cos = Math.cos(ang);
    var sin = Math.sin(ang);
    return new Array(
      Math.round(prec * (vec[0] * cos - vec[1] * sin)) / prec,
      Math.round(prec * (vec[0] * sin + vec[1] * cos)) / prec
    );
  }

  getPlaceable() {
    if (this._placeable) return this._placeable;

    const controlled = TokenMagic.getControlledPlaceables();
    return controlled?.[0];
  }

  _modifyTMFXSpriteParam(transformFunc, storeHistory = true) {
    const placeable = this.getPlaceable();
    if (!placeable) return;

    let filters = placeable.document.getFlag('tokenmagic', 'filters') || [];
    filters = filters.filter((f) => f.tmFilters.tmFilterType === 'sprite');
    if (!filters.length) return;

    let update = [];
    let undo = [];
    filters.forEach((f) => {
      let oldParams = f.tmFilters.tmParams;
      let newParams = { filterId: f.tmFilters.tmFilterId, filterType: 'sprite' };
      this._preserveAniProperties(placeable, newParams, oldParams);

      transformFunc(oldParams, newParams);

      update.push(newParams);

      if (storeHistory) {
        // Store a diff to enable undo
        const uUpdate = { filterId: newParams.filterId, filterType: 'sprite' };
        Object.keys(foundry.utils.diffObject(oldParams, newParams)).forEach((k) => {
          uUpdate[k] = foundry.utils.deepClone(oldParams[k]);
        });
        undo.push(uUpdate);
      }
    });

    if (undo.length) {
      if (!this._history[placeable.id]) this._history[placeable.id] = [];
      this._history[placeable.id].push(undo);
    }

    TokenMagic.addUpdateFilters(placeable, update);
  }

  /** @override */
  static PARTS = {
    main: { template: `modules/${MODULE_ID}/templates/UnlimitedTerrain.hbs` },
  };

  /** @override */
  _attachPartListeners(partId, element, options) {
    super._attachPartListeners(partId, element, options);
    switch (partId) {
      case 'main':
        $(element).on('input', '.percent', function (event) {
          let value = event.target.value.replace(/[^0-9%-]/gi, '');
          let sRange;

          let isNegative = value.startsWith('-');

          if (!value.includes('%')) {
            value = value.slice(0, value.length - 1);
            sRange = value.length;
          } else if (!value.endsWith('%')) {
            value = value.replaceAll('%', '');
            sRange = value.length;
          }
          value = value.replaceAll('%', '').replaceAll('-', '');

          event.target.value = (isNegative ? '-' : '') + value + '%';
          if (sRange) event.target.setSelectionRange(sRange, sRange);
        });
        break;
    }
  }

  /** @override */
  async _prepareContext(options) {
    const context = await super._prepareContext(options);

    let data = foundry.utils.deepClone(this._randomizerValues);
    ['pan.max', 'pan.min', 'pan.step', 'zoom.max', 'zoom.min', 'zoom.step'].forEach((property) => {
      foundry.utils.setProperty(data, property, String(foundry.utils.getProperty(data, property) * 100) + '%');
    });

    context.enableDisableButton = this._placeable?.document.getFlag('monks-active-tiles', 'active');

    return Object.assign(context, data);
  }

  static async _onSubmit(event, form, formData) {
    const data = foundry.utils.deepClone(formData.object);
    ['pan.max', 'pan.min', 'pan.step', 'zoom.max', 'zoom.min', 'zoom.step'].forEach((property) => {
      data[property] = Number(data[property].replaceAll('%', '').trim()) / 100;
    });
    this._randomizerValues = foundry.utils.expandObject(data);
  }
}
