interface Node<TValue> {
  previous?: Node<TValue>;
  value: TValue;
  next?: Node<TValue>;
}

const push = <TValue extends any>(
  node: Node<TValue> | undefined,
  value: TValue
): Node<TValue> => {
  if (!node) {
    return { value };
  }
  if (node.next) {
    push(node.next, value);
    return node;
  }
  node.next = { previous: node, value };
  return node;
};

const pop = <TValue extends any>(
  node?: Node<TValue>
): { node: Node<TValue> | undefined; value: TValue | undefined } => {
  if (!node) {
    return { node: undefined, value: undefined };
  }
  if (node.next) {
    const { value } = pop(node.next);
    return { node, value };
  }
  if (node.previous) {
    node.previous.next = undefined;
  }
  return { node: undefined, value: node.value };
};

const shift = <TValue extends any>(
  node?: Node<TValue>
): { node: Node<TValue> | undefined; value: TValue | undefined } => {
  if (!node) {
    return { node: undefined, value: undefined };
  }
  if (node.next) {
    node.next.previous = undefined;
    return { node: node.next, value: node.value };
  }
  return { node: undefined, value: node.value };
};

const unshift = <TValue extends any>(
  node: Node<TValue> | undefined,
  value: TValue
): Node<TValue> => {
  if (!node) {
    return { value };
  }
  const root = { value, next: node };
  node.previous = root;
  return root;
};

const count = <TValue extends any>(node: Node<TValue> | undefined): number => {
  if (!node) {
    return 0;
  }
  return 1 + count(node.next);
};

const del = <TValue extends any>(
  node: Node<TValue> | undefined,
  value: TValue
): Node<TValue> | undefined => {
  if (!node) {
    return undefined;
  }
  if (node.next) {
    del(node.next, value);
  }
  if (node.value === value) {
    if (node.previous) {
      node.previous.next = node.next;
    }
    if (node.next) {
      node.next.previous = node.previous;
    }
    return undefined;
  }
  return node;
};

export class LinkedList<TElement> {
  public node: Node<TElement> | undefined;
  push(value: TElement): void {
    this.node = push(this.node, value);
  }
  pop(): TElement | undefined {
    const { node, value } = pop(this.node);
    this.node = node;
    return value;
  }
  shift(): TElement | undefined {
    const { node, value } = shift(this.node);
    this.node = node;
    return value;
  }
  unshift(value: TElement): void {
    this.node = unshift(this.node, value);
  }
  delete(element: TElement): void {
    this.node = del(this.node, element);
  }
  count(): number {
    return count(this.node);
  }
}
