/**
 * SI decimal prefixes, ordered from smallest to largest. Indexed by {@link magnitude_prefix_index}.
 *
 * Covers the full SI prefix range: `q` (quecto, 1e-30) through `Q` (quetta, 1e30).
 *
 * @type {string[]}
 */
export const MAGNITUDE_PREFIXES = [
    'q',  // 1e-30 quecto
    'r',  // 1e-27 ronto
    'y',  // 1e-24 yocto
    'z',  // 1e-21 zepto
    'a',  // 1e-18 atto
    'f',  // 1e-15 femto
    'p',  // 1e-12 pico
    'n',  // 1e-9  nano
    'µ',  // 1e-6  micro
    'm',  // 1e-3  milli
    '',   // 1
    'k',  // 1e3   kilo
    'M',  // 1e6   mega
    'G',  // 1e9   giga
    'T',  // 1e12  tera
    'P',  // 1e15  peta
    'E',  // 1e18  exa
    'Z',  // 1e21  zetta
    'Y',  // 1e24  yotta
    'R',  // 1e27  ronna
    'Q'   // 1e30  quetta
];

/**
 * Decimal scale that matches each prefix in {@link MAGNITUDE_PREFIXES}, same indexing.
 *
 * A non-prefixed value at index `i` corresponds to scale `10^(3*(i - 10))`.
 *
 * @type {number[]}
 */
export const MAGNITUDE_SCALES = [
    1e-30, 1e-27, 1e-24, 1e-21, 1e-18, 1e-15, 1e-12, 1e-9, 1e-6, 1e-3,
    1,
    1e3, 1e6, 1e9, 1e12, 1e15, 1e18, 1e21, 1e24, 1e27, 1e30
];

/**
 * Index of the unit-scale (`1`) entry in {@link MAGNITUDE_PREFIXES} / {@link MAGNITUDE_SCALES}.
 * @type {number}
 */
const NEUTRAL_INDEX = 10;

/**
 * Smallest representable scale index.
 * @type {number}
 */
const MIN_INDEX = 0;

/**
 * Largest representable scale index.
 * @type {number}
 */
const MAX_INDEX = 20;

/**
 * Pick the index into {@link MAGNITUDE_PREFIXES} / {@link MAGNITUDE_SCALES} that best represents
 * `value`.
 *
 * "Best" here means: when `value` is divided by `MAGNITUDE_SCALES[i]`, the result lies in
 * `[1, 1000)` whenever possible. The sign of `value` is ignored — magnitude is what matters.
 *
 * Edge cases:
 *  - `0`, `NaN` or any non-finite value returns the neutral index (no prefix, scale = 1)
 *  - values smaller than `MAGNITUDE_SCALES[0]` clamp to the smallest prefix
 *  - values larger than `MAGNITUDE_SCALES[MAX_INDEX]` clamp to the largest prefix
 *
 * @param {number} value
 * @returns {number}
 */
export function magnitude_prefix_index(value) {
    if (!Number.isFinite(value) || value === 0) {
        return NEUTRAL_INDEX;
    }

    const abs = value < 0 ? -value : value;

    // log1000(abs) -> shift relative to the neutral index (which represents 10^0)
    const exponent_step = Math.floor(Math.log10(abs) / 3);

    let index = NEUTRAL_INDEX + exponent_step;

    if (index < MIN_INDEX) {
        index = MIN_INDEX;
    } else if (index > MAX_INDEX) {
        index = MAX_INDEX;
    }

    return index;
}

/**
 * Get the SI prefix string that best represents `value`.
 *
 * @example
 * magnitude_prefix(1000)   // 'k'
 * magnitude_prefix(0.001)  // 'm'
 * magnitude_prefix(1)      // ''
 * magnitude_prefix(1.5e9)  // 'G'
 *
 * @param {number} value
 * @returns {string}
 */
export function magnitude_prefix(value) {
    return MAGNITUDE_PREFIXES[magnitude_prefix_index(value)];
}

/**
 * Get the decimal scale that pairs with {@link magnitude_prefix} for the same input.
 *
 * @example
 * magnitude_scale(1000)   // 1000
 * magnitude_scale(0.001)  // 0.001
 *
 * @param {number} value
 * @returns {number}
 */
export function magnitude_scale(value) {
    return MAGNITUDE_SCALES[magnitude_prefix_index(value)];
}
