/*
 * Copyright (C) 2022 Savoir-faire Linux Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation; either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with this program.  If not, see
 * <https://www.gnu.org/licenses/>.
 */
import { Context, useCallback, useState } from 'react';

import HookComponent from '../hooks/HookComponent';
import { isRequired, PartialNotOptional, WithChildren } from '../utils/utils';

export type ConditionalContextProviderProps<T, B extends object> = WithChildren & {
  Context: Context<T>;
  initialValue: T;

  /**
   * Object containing the values used to get the provider value with `useProviderValue`.
   * If one or more field is undefined, the hook will not be called and `initialValue` will be used for the provider
   * value.
   *
   * Should be wrapped in `useMemo` to avoid unnecessary hook calls.
   */
  dependencies: PartialNotOptional<B>;
  useProviderValue: (dependencies: B) => T;
};

/**
 * A context provider with `initialValue` as its value if not all props of the dependencies object are defined.
 * If all props are defined, the provider value is the result of `useProviderValue`.
 */
const ConditionalContextProvider = <T, B extends object>({
  Context,
  initialValue,
  dependencies,
  useProviderValue,
  children,
}: ConditionalContextProviderProps<T, B>) => {
  const [value, setValue] = useState(initialValue);
  const unmountCallback = useCallback(() => setValue(initialValue), [initialValue]);

  return (
    <>
      {isRequired(dependencies) && (
        <HookComponent
          callback={setValue}
          unmountCallback={unmountCallback}
          useHook={useProviderValue}
          args={dependencies}
        />
      )}
      <Context.Provider value={value}>{children}</Context.Provider>
    </>
  );
};

export default ConditionalContextProvider;
