/** @typedef {'linear'|'monotone'|'step'} LineCurveType */
/** @typedef {'number'|'percent'} AxisValueType */

/**
 * @typedef {Object} SmartLineConfig
 * @property {boolean} [grid]
 * @property {boolean} [showLegend]
 * @property {boolean} [showXAxis]
 * @property {boolean} [showYAxis]
 * @property {boolean} [showGrid]
 * @property {boolean} [showDots]
 * @property {boolean} [showLabel] — 是否外显折线点数据标签，默认 false
 * @property {boolean} [showValueLabel] — showLabel 的兼容别名
 * @property {LineCurveType} [curveType]
 * @property {LineCurveType} [curve]
 * @property {'top'|'bottom'|'left'|'right'} [legendPosition]
 * @property {number} [height]
 * @property {{ top?: number, right?: number, bottom?: number, left?: number }} [margin]
 * @property {Element} [resolveRoot]
 * @property {boolean} [dualAxis]
 * @property {boolean} [dualYAxis]
 * @property {AxisValueType} [leftAxisType]
 * @property {AxisValueType} [rightAxisType]
 * @property {boolean} [leftPercentAsFraction]
 * @property {boolean} [rightPercentAsFraction]
 * @property {(string|number)[]} [rightAxisSeries]
 * @property {number|null} [rightYAxisSeriesIndex]
 * @property {string} [rightYAxisTickSuffix]
 * @property {number} [yAxisTickCount]
 * @property {number} [strokeWidth] — 序列线宽（px），默认 {@link CHART_SERIES_LINE_STROKE_WIDTH}
 * @property {number} [yAxisWidth] — 纵轴占位宽度（px），默认 {@link CHART_Y_AXIS_WIDTH_DEFAULT}
 * @property {'axis'|'left'} [yAxisTickAlign] — `left` 时数字左对齐，便于与卡片内容左缘对齐
 * @property {'preserveStartEnd'|0|number} [xAxisInterval] — 横轴刻度抽稀策略，默认 `preserveStartEnd`
 * @property {number} [xAxisMinTickGap] — 横轴刻度最小间距，默认 24
 */

export const Y_LEFT = 'left';
export const Y_RIGHT = 'right';

/** Dashboard 折线 / 面积上边线标准线宽（px），与图例线段高度一致 */
export const CHART_SERIES_LINE_STROKE_WIDTH = 1.5;

/** 开启数据标签时，折点自动补同色圆点的半径（px），直径 6px */
export const CHART_DATA_LABEL_POINT_RADIUS = 3;

/** 纵轴刻度区默认宽度（右对齐刻度，通用预览） */
export const CHART_Y_AXIS_WIDTH_DEFAULT = 44;

/** 纵轴刻度区紧凑宽度（左对齐数字，与大屏卡片内容左缘对齐） */
export const CHART_Y_AXIS_WIDTH_COMPACT = 28;

/**
 * @param {SmartLineConfig | Record<string, unknown> | undefined} c
 */
export function pickCurveType(c) {
  const raw = c?.curveType ?? c?.curve;
  return ['linear', 'monotone', 'step'].includes(raw) ? raw : 'linear';
}

/**
 * @param {number|null|undefined} idx
 * @param {number} n
 */
export function clampSeriesIndex(idx, n) {
  if (!Number.isFinite(n) || n < 1) return 0;
  if (idx === null || idx === undefined || Number.isNaN(Number(idx))) return Math.max(0, n - 1);
  const i = Math.floor(Number(idx));
  return Math.min(n - 1, Math.max(0, i));
}

/**
 * @param {unknown} v
 * @returns {v is AxisValueType}
 */
export function isAxisValueType(v) {
  return v === 'number' || v === 'percent';
}

/**
 * @param {AxisValueType} type
 * @param {boolean} fraction
 */
export function makePercentTickFormatter(type, fraction) {
  if (type !== 'percent') return undefined;
  return (/** @type {number} */ raw) => {
    const v = Number(raw);
    if (!Number.isFinite(v)) return '';
    const p = fraction ? Math.round(v * 100) : Math.round(v);
    return `${p}%`;
  };
}

/**
 * @param {(v: number, i: number) => string} [tickFormatter]
 * @param {{ align?: 'axis'|'left', insetPx?: number, fill?: string, fontSize?: number }} [options]
 */
export function createCartesianYAxisTickRenderer(tickFormatter, options) {
  const alignLeft = options?.align === 'left';
  const insetPx = Number.isFinite(Number(options?.insetPx))
    ? Math.max(0, Number(options.insetPx))
    : 0;

  return function CartesianYAxisTickRenderer(/** @type {Record<string, unknown>} */ props) {
    const {
      x,
      y,
      fill,
      fontSize,
      textAnchor,
      index,
      visibleTicksCount,
      payload,
      className,
    } = props;
    const raw = payload?.value;
    const text =
      typeof tickFormatter === 'function'
        ? tickFormatter(/** @type {number} */ (raw), /** @type {number} */ (index))
        : raw;
    return (
      <text
        x={alignLeft ? insetPx : x}
        y={y}
        dy={0}
        className={className}
        fill={fill ?? options?.fill ?? '#667085'}
        fontSize={fontSize ?? options?.fontSize ?? 12}
        textAnchor={alignLeft ? 'start' : textAnchor}
        dominantBaseline="middle"
      >
        {text}
      </text>
    );
  };
}

/**
 * @param {unknown} rawList
 * @param {{ dataKey: string, name: string }} item
 * @param {number} idx
 * @param {number} fallbackRightIndex
 */
export function seriesUsesRightAxis(rawList, item, idx, fallbackRightIndex) {
  const list = Array.isArray(rawList) ? rawList.filter((x) => x !== '' && x !== null && x !== undefined) : [];
  if (list.length === 0) {
    return idx === fallbackRightIndex;
  }
  const dataKey = String(item.dataKey ?? '');
  const name = String(item.name ?? '');
  return list.some((entry) => {
    if (typeof entry === 'number' && Number.isFinite(entry)) return entry === idx;
    const s = String(entry);
    if (s === String(idx)) return true;
    if (s === dataKey) return true;
    if (s === name) return true;
    return false;
  });
}

/**
 * @param {unknown} ex
 * @returns {'left'|'right'|null}
 */
export function normalizeSeriesYAxisSide(ex) {
  if (ex === Y_RIGHT || ex === 1 || ex === '1') return Y_RIGHT;
  if (ex === Y_LEFT || ex === 0 || ex === '0') return Y_LEFT;
  return null;
}

/**
 * @param {SmartLineConfig | undefined} c
 */
export function mergeSmartLineChartConfig(c) {
  const defaults = {
    grid: true,
    showLegend: true,
    showXAxis: true,
    showYAxis: true,
    showDots: false,
    showLabel: false,
    showValueLabel: false,
    curveType: 'linear',
    legendPosition: 'bottom',
    height: 340,
    margin: { top: 20, right: 20, left: 10, bottom: 10 },
    dualAxis: false,
    dualYAxis: false,
    leftAxisType: 'number',
    rightAxisType: 'number',
    leftPercentAsFraction: false,
    rightPercentAsFraction: false,
    rightAxisSeries: [],
    rightYAxisSeriesIndex: null,
    rightYAxisTickSuffix: '',
    legendShape: 'line',
    yAxisTickCount: 7,
    strokeWidth: CHART_SERIES_LINE_STROKE_WIDTH,
    yAxisWidth: CHART_Y_AXIS_WIDTH_DEFAULT,
    yAxisTickAlign: 'axis',
    xAxisInterval: 'preserveStartEnd',
    xAxisMinTickGap: 24,
  };
  if (!c || typeof c !== 'object') return defaults;
  const merged = { ...defaults, ...c };
  merged.curveType = pickCurveType(merged);
  merged.grid =
    typeof merged.grid === 'boolean'
      ? merged.grid
      : typeof merged.showGrid === 'boolean'
        ? merged.showGrid
        : defaults.grid;
  merged.showLabel = !!(merged.showLabel ?? merged.showValueLabel);
  merged.showValueLabel = merged.showLabel;
  merged.height = Number(merged.height ?? defaults.height) || defaults.height;
  merged.margin = { ...defaults.margin, ...(merged.margin || {}) };
  merged.dualAxis = !!(merged.dualAxis ?? merged.dualYAxis);
  merged.dualYAxis = merged.dualAxis;
  merged.leftAxisType = isAxisValueType(merged.leftAxisType) ? merged.leftAxisType : 'number';
  merged.rightAxisType = isAxisValueType(merged.rightAxisType) ? merged.rightAxisType : 'number';
  merged.leftPercentAsFraction = !!merged.leftPercentAsFraction;
  merged.rightPercentAsFraction = !!merged.rightPercentAsFraction;
  merged.rightAxisSeries = Array.isArray(merged.rightAxisSeries) ? merged.rightAxisSeries : [];
  merged.rightYAxisTickSuffix = typeof merged.rightYAxisTickSuffix === 'string' ? merged.rightYAxisTickSuffix : '';
  merged.legendShape = merged.legendShape === 'dot' ? 'dot' : 'line';
  const rawR = merged.rightYAxisSeriesIndex;
  merged.rightYAxisSeriesIndex =
    rawR === null || rawR === undefined || rawR === '' ? null : Number(rawR);
  if (!Number.isFinite(merged.rightYAxisSeriesIndex)) merged.rightYAxisSeriesIndex = null;
  const sw = Number(merged.strokeWidth);
  merged.strokeWidth =
    Number.isFinite(sw) && sw > 0 ? sw : CHART_SERIES_LINE_STROKE_WIDTH;
  const yw = Number(merged.yAxisWidth);
  merged.yAxisWidth =
    Number.isFinite(yw) && yw > 0 ? Math.max(20, yw) : CHART_Y_AXIS_WIDTH_DEFAULT;
  merged.yAxisTickAlign = merged.yAxisTickAlign === 'left' ? 'left' : 'axis';
  merged.xAxisInterval =
    merged.xAxisInterval === 0 || Number.isFinite(Number(merged.xAxisInterval))
      ? Number(merged.xAxisInterval)
      : 'preserveStartEnd';
  const xMinTickGap = Number(merged.xAxisMinTickGap);
  merged.xAxisMinTickGap = Number.isFinite(xMinTickGap) ? Math.max(0, xMinTickGap) : 24;
  return merged;
}
