'use strict';

var Axis2D = require('../coord/cartesian/Axis2D.js');
var model = require('./model.js');

function needFixJitter(seriesModel, axis) {
  var coordinateSystem = seriesModel.coordinateSystem;
  var coordType = coordinateSystem && coordinateSystem.type;
  var baseAxis = coordinateSystem && coordinateSystem.getBaseAxis && coordinateSystem.getBaseAxis();
  var scaleType = baseAxis && baseAxis.scale && baseAxis.scale.type;
  var seriesValid = coordType === 'cartesian2d' && scaleType === 'ordinal' || coordType === 'single';
  var axisValid = axis.model.get('jitter') > 0;
  return seriesValid && axisValid;
}
var inner = model.makeInner();
/**
 * Fix jitter for overlapping data points.
 *
 * @param fixedAxis The axis whose coord doesn't change with jitter.
 * @param fixedCoord The coord of fixedAxis.
 * @param floatCoord The coord of the other axis, which should be changed with jittering.
 * @param radius The radius of the data point, considering the symbol is a circle.
 * @returns updated floatCoord.
 */
function fixJitter(fixedAxis, fixedCoord, floatCoord, radius) {
  if (fixedAxis instanceof Axis2D.default) {
    var scaleType = fixedAxis.scale.type;
    if (scaleType !== 'category' && scaleType !== 'ordinal') {
      return floatCoord;
    }
  }
  var axisModel = fixedAxis.model;
  var jitter = axisModel.get('jitter');
  var jitterOverlap = axisModel.get('jitterOverlap');
  var jitterMargin = axisModel.get('jitterMargin') || 0;
  // Get band width to limit jitter range
  var bandWidth = fixedAxis.scale.type === 'ordinal' ? fixedAxis.getBandWidth() : null;
  if (jitter > 0) {
    if (jitterOverlap) {
      return fixJitterIgnoreOverlaps(floatCoord, jitter, bandWidth, radius);
    } else {
      return fixJitterAvoidOverlaps(fixedAxis, fixedCoord, floatCoord, radius, jitter, jitterMargin);
    }
  }
  return floatCoord;
}
function fixJitterIgnoreOverlaps(floatCoord, jitter, bandWidth, radius) {
  // Don't clamp single axis
  if (bandWidth === null) {
    return floatCoord + (Math.random() - 0.5) * jitter;
  }
  var maxJitter = bandWidth - radius * 2;
  var actualJitter = Math.min(Math.max(0, jitter), maxJitter);
  return floatCoord + (Math.random() - 0.5) * actualJitter;
}
function fixJitterAvoidOverlaps(fixedAxis, fixedCoord, floatCoord, radius, jitter, margin) {
  var store = inner(fixedAxis);
  if (!store.items) {
    store.items = [];
  }
  var items = store.items;
  // Try both positive and negative directions, choose the one with smaller movement
  var overlapA = placeJitterOnDirection(items, fixedCoord, floatCoord, radius, jitter, margin, 1);
  var overlapB = placeJitterOnDirection(items, fixedCoord, floatCoord, radius, jitter, margin, -1);
  var minFloat = Math.abs(overlapA - floatCoord) < Math.abs(overlapB - floatCoord) ? overlapA : overlapB;
  // Clamp only category axis
  var bandWidth = fixedAxis.scale.type === 'ordinal' ? fixedAxis.getBandWidth() : null;
  var distance = Math.abs(minFloat - floatCoord);
  if (distance > jitter / 2 || bandWidth && distance > bandWidth / 2 - radius) {
    // If the new item is moved too far, then give up.
    // Fall back to random jitter.
    return fixJitterIgnoreOverlaps(floatCoord, jitter, bandWidth, radius);
  }
  // Add new point to array
  items.push({
    fixedCoord: fixedCoord,
    floatCoord: minFloat,
    r: radius
  });
  return minFloat;
}
function placeJitterOnDirection(items, fixedCoord, floatCoord, radius, jitter, margin, direction) {
  var y = floatCoord;
  // Check all existing items for overlap and find the maximum adjustment needed
  for (var i = 0; i < items.length; i++) {
    var item = items[i];
    var dx = fixedCoord - item.fixedCoord;
    var dy = y - item.floatCoord;
    var d2 = dx * dx + dy * dy;
    var r = radius + item.r + margin;
    if (d2 < r * r) {
      // Has overlap, calculate required adjustment
      var requiredY = item.floatCoord + Math.sqrt(r * r - dx * dx) * direction;
      // Check if this adjustment would move too far
      if (Math.abs(requiredY - floatCoord) > jitter / 2) {
        return Number.MAX_VALUE; // Give up
      }
      // Update y only when it's larger to the center
      if (direction === 1 && requiredY > y || direction === -1 && requiredY < y) {
        y = requiredY;
        // Loop from the start again
        i = -1; // Reset index to recheck all items
        continue; // Recalculate with the new y position
      }
    }
  }
  return y;
}

exports.fixJitter = fixJitter;
exports.needFixJitter = needFixJitter;
