import is from '@sindresorhus/is';
import { parse } from './parser';
import type { Fragment, FragmentPath, FragmentUpdater } from './types';

export function findCodeFragment(
  input: string,
  path: FragmentPath,
): Fragment | null {
  const parsed = parse(input);
  if (!parsed) {
    return null;
  }

  const [ruleIndex, ...restPath] = path;
  let fragment: Fragment | undefined = parsed[ruleIndex];
  for (const key of restPath) {
    if (!fragment) {
      break;
    }

    if (fragment.type === 'array' && is.number(key)) {
      fragment = fragment.children[key];
    }

    if (fragment.type === 'record' && is.string(key)) {
      fragment = fragment.children[key];
    }
  }

  return fragment ?? null;
}

export function patchCodeAtFragment(
  input: string,
  fragment: Fragment,
  updater: FragmentUpdater,
): string {
  const { value, offset } = fragment;
  const left = input.slice(0, offset);
  const right = input.slice(offset + value.length);
  return is.string(updater)
    ? `${left}${updater}${right}`
    : `${left}${updater(value)}${right}`;
}

export function patchCodeAtFragments(
  input: string,
  fragments: Fragment[],
  updater: FragmentUpdater,
): string {
  const sortedFragments = fragments.sort(
    ({ offset: a }, { offset: b }) => b - a,
  );
  let result = input;
  for (const fragment of sortedFragments) {
    result = patchCodeAtFragment(result, fragment, updater);
  }
  return result;
}

export function updateCode(
  input: string,
  path: FragmentPath,
  updater: FragmentUpdater,
): string {
  const fragment = findCodeFragment(input, path);
  if (!fragment) {
    return input;
  }

  return patchCodeAtFragment(input, fragment, updater);
}
