'use strict';
import { ReanimatedError } from '../../errors';
import type {
  NormalizedTransformOrigin,
  TransformOrigin,
  ValueProcessor,
} from '../../types';

type Axis = 'x' | 'y' | 'z';
type ConvertedValue = `${number}%` | number;
type KeywordConversions = Record<string, ConvertedValue>;
type CustomParse = (value: string) => ConvertedValue | null;

const HORIZONTAL_CONVERSIONS = {
  left: 0,
  center: '50%',
  right: '100%',
} satisfies KeywordConversions;

const VERTICAL_CONVERSIONS = {
  top: 0,
  center: '50%',
  bottom: '100%',
} satisfies KeywordConversions;

function getAllowedValues(axis: Axis, isArray: boolean): string {
  'worklet';
  const allowed: string[] = [];

  if (isArray) {
    allowed.push('numbers');
  } else {
    allowed.push('numbers with px unit');
  }

  allowed.push('percentages');

  let keywords: string[] = [];
  switch (axis) {
    case 'x':
      keywords = Object.keys(HORIZONTAL_CONVERSIONS);
      break;
    case 'y':
      keywords = Object.keys(VERTICAL_CONVERSIONS);
      break;
  }
  if (keywords.length) {
    allowed.push(`keywords (${keywords.join(', ')})`);
  }

  // Add "or" before the last item
  allowed[allowed.length - 1] = `or ${allowed[allowed.length - 1]}`;

  return allowed.join(', ');
}

export const ERROR_MESSAGES = {
  invalidTransformOrigin: (value: Readonly<TransformOrigin>) => {
    'worklet';
    return `Invalid transformOrigin: ${JSON.stringify(value)}. Expected 1-3 values.`;
  },
  invalidValue: (
    value: string | number,
    axis: Axis,
    origin: Readonly<TransformOrigin>,
    isArray: boolean
  ) => {
    'worklet';
    return `Invalid value "${value}" for the ${axis}-axis in transformOrigin ${JSON.stringify(
      origin
    )}. Allowed values: ${getAllowedValues(axis, isArray)}.`;
  },
};

function maybeSwapComponents(components: ReadonlyArray<string | number>) {
  'worklet';
  if (
    components[0] in VERTICAL_CONVERSIONS &&
    (components[1] === undefined || components[1] in HORIZONTAL_CONVERSIONS)
  ) {
    const copy = [...components];
    [copy[0], copy[1]] = [copy[1], copy[0]];
    return copy;
  }

  return components;
}

function parseValue(
  value: string | number,
  allowPercentages: true,
  customParse: CustomParse,
  getError: () => string,
  keywordConversions?: KeywordConversions
): `${number}%` | number;

function parseValue(
  value: string | number,
  allowPercentages: false,
  customParse: CustomParse,
  getError: () => string,
  keywordConversions?: KeywordConversions
): number;

function parseValue(
  value: string | number,
  allowPercentages: boolean,
  customParse: CustomParse,
  getError: () => string,
  keywordConversions?: KeywordConversions
): `${number}%` | number {
  'worklet';
  if (typeof value === 'number') {
    return value;
  }
  if (keywordConversions && value in keywordConversions) {
    return keywordConversions[value];
  }
  if (allowPercentages && value.endsWith('%')) {
    const num = parseFloat(value);
    if (num === 0) {
      return 0;
    }
    if (!isNaN(num)) {
      return `${num}%`;
    }
  }

  const parsed = customParse(value);
  if (parsed === null) {
    throw new ReanimatedError(getError());
  }

  return parsed;
}

function parsePx(component: string) {
  'worklet';
  if (component.endsWith('px') || component === '0') {
    const num = parseFloat(component);
    if (!isNaN(num)) {
      return num;
    }
  }
  return null;
}

export const processTransformOrigin: ValueProcessor<
  TransformOrigin,
  NormalizedTransformOrigin
> = (value) => {
  'worklet';
  const isArray = Array.isArray(value);
  let components = typeof value === 'string' ? value.split(/\s+/) : value;
  const customParse = isArray ? () => null : parsePx;

  if (components.length < 1 || components.length > 3) {
    throw new ReanimatedError(ERROR_MESSAGES.invalidTransformOrigin(value));
  }

  components = maybeSwapComponents(components);

  return [
    parseValue(
      components[0] ?? '50%',
      true,
      customParse,
      () => ERROR_MESSAGES.invalidValue(components[0], 'x', value, isArray),
      HORIZONTAL_CONVERSIONS
    ),
    parseValue(
      components[1] ?? '50%',
      true,
      customParse,
      () => ERROR_MESSAGES.invalidValue(components[1], 'y', value, isArray),
      VERTICAL_CONVERSIONS
    ),
    parseValue(components[2] ?? 0, false, customParse, () =>
      ERROR_MESSAGES.invalidValue(components[2], 'z', value, isArray)
    ),
  ];
};
