import { isNumber, isArray, eqNaN } from '../../../../../../zrender@6.0.0/node_modules/zrender/lib/core/util.mjs';
import { XY, WH } from '../../util/graphic.mjs';
import { mathMax, mathMin } from '../../util/number.mjs';

var MatrixCellLayoutInfoType = {
  level: 1,
  leaf: 2,
  nonLeaf: 3
};
/**
 * @public Public to users in `chart.convertFromPixel`.
 */
var MatrixClampOption = {
  // No clamp, be falsy, equals to null/undefined. It means if the input part is
  // null/undefined/NaN/outOfBoundary, the result part is NaN, rather than clamp to
  // the boundary of the matrix.
  none: 0,
  // Clamp, where null/undefined/NaN/outOfBoundary can be used to cover the entire row/column.
  all: 1,
  body: 2,
  corner: 3
};
/**
 * For the x direction,
 *  - find dimension cell from `xMatrixDim`,
 *      - If `xDimCell` or `yDimCell` is not a leaf, return the non-leaf cell itself.
 *  - otherwise find level from `yMatrixDim`.
 *  - otherwise return `NullUndefined`.
 *
 * For the y direction, it's the opposite.
 */
function coordDataToAllCellLevelLayout(coordValue, dims, thisDimIdx // 0 | 1
) {
  // Find in body.
  var result = dims[XY[thisDimIdx]].getCell(coordValue);
  // Find in corner or dimension area.
  if (!result && isNumber(coordValue) && coordValue < 0) {
    result = dims[XY[1 - thisDimIdx]].getUnitLayoutInfo(thisDimIdx, Math.round(coordValue));
  }
  return result;
}
function resetXYLocatorRange(out) {
  var rg = out || [];
  rg[0] = rg[0] || [];
  rg[1] = rg[1] || [];
  rg[0][0] = rg[0][1] = rg[1][0] = rg[1][1] = NaN;
  return rg;
}
/**
 * If illegal or out of boundary, set NaN to `locOut`. See `isXYLocatorRangeInvalidOnDim`.
 * x dimension and y dimension are calculated separately.
 */
function parseCoordRangeOption(locOut,
// If illegal input or can not find any target, save reason to it.
// Do nothing if `NullUndefined`.
reasonOut, data, dims, clamp) {
  // x and y are supported to be handled separately - if one dimension is invalid
  // (may be users do not need that), the other one should also be calculated.
  parseCoordRangeOptionOnOneDim(locOut[0], reasonOut, clamp, data, dims, 0);
  parseCoordRangeOptionOnOneDim(locOut[1], reasonOut, clamp, data, dims, 1);
}
function parseCoordRangeOptionOnOneDim(locDimOut, reasonOut, clamp, data, dims, dimIdx) {
  locDimOut[0] = Infinity;
  locDimOut[1] = -Infinity;
  var dataOnDim = data[dimIdx];
  var coordValArr = isArray(dataOnDim) ? dataOnDim : [dataOnDim];
  var len = coordValArr.length;
  var hasClamp = !!clamp;
  if (len >= 1) {
    parseCoordRangeOptionOnOneDimOnePart(locDimOut, reasonOut, coordValArr, hasClamp, dims, dimIdx, 0);
    if (len > 1) {
      // Users may intuitively input the coords like `[[x1, x2, x3], ...]`;
      // consider the range as `[x1, x3]` in this case.
      parseCoordRangeOptionOnOneDimOnePart(locDimOut, reasonOut, coordValArr, hasClamp, dims, dimIdx, len - 1);
    }
  } else {
    if (process.env.NODE_ENV !== 'production') {
      if (reasonOut) {
        reasonOut.push('Should be like [["x1", "x2"], ["y1", "y2"]], or ["x1", "y1"], rather than empty.');
      }
    }
    locDimOut[0] = locDimOut[1] = NaN;
  }
  if (hasClamp) {
    // null/undefined/NaN or illegal data represents the entire row/column;
    // Cover the entire locator regardless of body or corner, and confine it later.
    var locLowerBound = -dims[XY[1 - dimIdx]].getLocatorCount(dimIdx);
    var locUpperBound = dims[XY[dimIdx]].getLocatorCount(dimIdx) - 1;
    if (clamp === MatrixClampOption.body) {
      locLowerBound = mathMax(0, locLowerBound);
    } else if (clamp === MatrixClampOption.corner) {
      locUpperBound = mathMin(-1, locUpperBound);
    }
    if (locUpperBound < locLowerBound) {
      // Also considered that both x and y has no cell.
      locLowerBound = locUpperBound = NaN;
    }
    if (eqNaN(locDimOut[0])) {
      locDimOut[0] = locLowerBound;
    }
    if (eqNaN(locDimOut[1])) {
      locDimOut[1] = locUpperBound;
    }
    locDimOut[0] = mathMax(mathMin(locDimOut[0], locUpperBound), locLowerBound);
    locDimOut[1] = mathMax(mathMin(locDimOut[1], locUpperBound), locLowerBound);
  }
}
// The return val must be finite or NaN.
function parseCoordRangeOptionOnOneDimOnePart(locDimOut, reasonOut, coordValArr, hasClamp, dims, dimIdx, partIdx) {
  var layout = coordDataToAllCellLevelLayout(coordValArr[partIdx], dims, dimIdx);
  if (!layout) {
    if (process.env.NODE_ENV !== 'production') {
      if (!hasClamp && reasonOut) {
        reasonOut.push("Can not find cell by coord[" + dimIdx + "][" + partIdx + "].");
      }
    }
    locDimOut[0] = locDimOut[1] = NaN;
    return;
  }
  var locatorA = layout.id[XY[dimIdx]];
  var locatorB = locatorA;
  var dimCell = cellLayoutInfoToDimCell(layout);
  if (dimCell) {
    // Handle non-leaf
    locatorB += dimCell.span[XY[dimIdx]] - 1;
  }
  locDimOut[0] = mathMin(locDimOut[0], locatorA, locatorB);
  locDimOut[1] = mathMax(locDimOut[1], locatorA, locatorB);
}
/**
 * @param locatorRange Must be the return of `parseCoordRangeOption`,
 *  where if not NaN, it must be a valid locator.
 */
function isXYLocatorRangeInvalidOnDim(locatorRange, dimIdx) {
  return eqNaN(locatorRange[dimIdx][0]) || eqNaN(locatorRange[dimIdx][1]);
}
// `locatorRange` will be expanded (modified) if an intersection is encountered.
function resolveXYLocatorRangeByCellMerge(inOutLocatorRange,
// Item indices coorespond to mergeDefList (len: mergeDefListTravelLen).
// Indicating whether each item has be merged into the `locatorRange`
outMergedMarkList, mergeDefList, mergeDefListTravelLen) {
  outMergedMarkList = outMergedMarkList || _tmpOutMergedMarkList;
  for (var idx = 0; idx < mergeDefListTravelLen; idx++) {
    outMergedMarkList[idx] = false;
  }
  // In most case, cell merging definition list length is smaller than the range extent,
  // therefore, to detection intersection, travelling cell merging definition list is probably
  // performant than traveling the four edges of the rect formed by the locator range.
  while (true) {
    var expanded = false;
    for (var idx = 0; idx < mergeDefListTravelLen; idx++) {
      var mergeDef = mergeDefList[idx];
      if (!outMergedMarkList[idx] && mergeDef.cellMergeOwner && expandXYLocatorRangeIfIntersect(inOutLocatorRange, mergeDef.locatorRange)) {
        outMergedMarkList[idx] = true;
        expanded = true;
      }
    }
    if (!expanded) {
      break;
    }
  }
}
var _tmpOutMergedMarkList = [];
// Return whether intersect.
// `thisLocRange` will be expanded (modified) if an intersection is encountered.
function expandXYLocatorRangeIfIntersect(thisLocRange, otherLocRange) {
  if (!locatorRangeIntersectOneDim(thisLocRange[0], otherLocRange[0]) || !locatorRangeIntersectOneDim(thisLocRange[1], otherLocRange[1])) {
    return false;
  }
  thisLocRange[0][0] = mathMin(thisLocRange[0][0], otherLocRange[0][0]);
  thisLocRange[0][1] = mathMax(thisLocRange[0][1], otherLocRange[0][1]);
  thisLocRange[1][0] = mathMin(thisLocRange[1][0], otherLocRange[1][0]);
  thisLocRange[1][1] = mathMax(thisLocRange[1][1], otherLocRange[1][1]);
  return true;
}
// Notice: If containing NaN, not intersect.
function locatorRangeIntersectOneDim(locRange1OneDim, locRange2OneDim) {
  return locRange1OneDim[1] >= locRange2OneDim[0] && locRange1OneDim[0] <= locRange2OneDim[1];
}
function fillIdSpanFromLocatorRange(owner, locatorRange) {
  owner.id.set(locatorRange[0][0], locatorRange[1][0]);
  owner.span.set(locatorRange[0][1] - owner.id.x + 1, locatorRange[1][1] - owner.id.y + 1);
}
function cloneXYLocatorRange(target, source) {
  target[0][0] = source[0][0];
  target[0][1] = source[0][1];
  target[1][0] = source[1][0];
  target[1][1] = source[1][1];
}
/**
 * If illegal, the corresponding x/y/width/height is set to `NaN`.
 * `x/width` or `y/height` is supported to be calculated separately,
 * i.e., one side are NaN, the other side are normal.
 * @param oneDimOut only write to `x/width` or `y/height`, depending on `dimIdx`.
 */
function xyLocatorRangeToRectOneDim(oneDimOut, locRange, dims, dimIdx) {
  var layoutMin = coordDataToAllCellLevelLayout(locRange[dimIdx][0], dims, dimIdx);
  var layoutMax = coordDataToAllCellLevelLayout(locRange[dimIdx][1], dims, dimIdx);
  oneDimOut[XY[dimIdx]] = oneDimOut[WH[dimIdx]] = NaN;
  if (layoutMin && layoutMax) {
    oneDimOut[XY[dimIdx]] = layoutMin.xy;
    oneDimOut[WH[dimIdx]] = layoutMax.xy + layoutMax.wh - layoutMin.xy;
  }
}
// No need currently, since `span` is not allowed to be defined directly by users.
// /**
//  * If either span x or y is valid and > 1, return parsed span, otherwise return `NullUndefined`.
//  */
// export function parseSpanOption(
//     spanOptionHost: MatrixCellSpanOptionHost,
//     dimCellPair: MatrixCellLayoutInfo[]
// ): Point | NullUndefined {
//     const spanX = parseSpanOnDim(spanOptionHost.spanX, dimCellPair[0], 0);
//     const spanY = parseSpanOnDim(spanOptionHost.spanY, dimCellPair[1], 1);
//     if (!eqNaN(spanX) || !eqNaN(spanY)) {
//         return new Point(spanX || 1, spanY || 1);
//     }
//     function parseSpanOnDim(spanOption: unknown, dimCell: MatrixCellLayoutInfo, dimIdx: number): number {
//         if (!isNumber(spanOption)) {
//             return NaN;
//         }
//         // Ensure positive integer (not NaN) to avoid dead loop.
//         const span = mathMax(1, Math.round(spanOption || 1)) || 1;
//         // Clamp, and consider may also be specified as `Infinity` to span the entire col/row.
//         return mathMin(span, mathMax(1, dimCell.dim.getLocatorCount(dimIdx) - dimCell.id[XY[dimIdx]]));
//     }
// }
/**
 * @usage To get/set on dimension, use:
 *  `xyVal[XY[dim]] = val;` // set on this dimension.
 *  `xyVal[XY[1 - dim]] = val;` // set on the perpendicular dimension.
 */
function setDimXYValue(out, dimIdx,
// 0 | 1
valueOnThisDim, valueOnOtherDim) {
  out[XY[dimIdx]] = valueOnThisDim;
  out[XY[1 - dimIdx]] = valueOnOtherDim;
  return out;
}
/**
 * Return NullUndefined if not dimension cell.
 */
function cellLayoutInfoToDimCell(cellLayoutInfo) {
  return cellLayoutInfo && (cellLayoutInfo.type === MatrixCellLayoutInfoType.leaf || cellLayoutInfo.type === MatrixCellLayoutInfoType.nonLeaf) ? cellLayoutInfo : null;
}
function createNaNRectLike() {
  return {
    x: NaN,
    y: NaN,
    width: NaN,
    height: NaN
  };
}

export { MatrixCellLayoutInfoType, MatrixClampOption, cloneXYLocatorRange, coordDataToAllCellLevelLayout, createNaNRectLike, fillIdSpanFromLocatorRange, isXYLocatorRangeInvalidOnDim, parseCoordRangeOption, resetXYLocatorRange, resolveXYLocatorRangeByCellMerge, setDimXYValue, xyLocatorRangeToRectOneDim };
