import {
  createNavigationContainerRef,
  NavigationContainer,
  NavigationContainerRefWithCurrent,
  ParamListBase,
  RouteProp,
} from '@react-native-bedrock/native/@react-navigation/native';
import { NativeStackNavigationOptions } from '@react-native-bedrock/native/@react-navigation/native-stack';
import { ComponentProps, ComponentType, Fragment, PropsWithChildren, ReactElement, useCallback, useMemo } from 'react';
import { BedrockStack } from './components/BedrockStack';
import { CanGoBackGuard } from './components/CanGoBackGuard';
import { RouterBackButton, RouterBackButtonProps } from './components/RouterBackButton';
import { useBedrockRouterControls } from './hooks/useBedrockRouterControls';
import { useInitialRouteName } from './hooks/useInitialRouteName';
import { RequireContext } from './types';
import { BASE_STACK_NAVIGATOR_STYLE } from './types/screen-option';
import { InitialProps } from '../initial-props';

/**
 * @internal
 */
export interface InternalRouterProps {
  /**
   * @name context
   * @description
   * 파일 기반 라우팅에 사용되는 화면 정보를 담은 객체예요.
   */
  context: RequireContext;
  /**
   * @name prefix
   * @description
   * 스킴이 실행될 때 사용할 prefix 이에요. 예를 들어, `intoss://my-service/intro` 를 진입하기 위해서는 `intoss://my-service`를 prefix로 설정해야 해요.
   */
  prefix: string;
  /**
   * @name canGoBack
   * @description
   * 뒤로 갈 수 있는지 여부예요. 기본값은 `true`이고, `true` 로 설정하면 `@react-navigation/native` 의 뒤로 가기 제스처나 뒤로가기 버튼을 사용할 수 있어요.
   * @default true
   */
  canGoBack?: boolean;
  /**
   * @name onBack
   * @description
   * 뒤로 이동했을 때 호출되는 콜백 함수예요.
   */
  onBack?: () => void;
  /**
   * @name container
   * @description
   * `@react-navigation/native`의 `Navigator`를 감싸는 컨테이너 컴포넌트예요.
   */
  container: ComponentType<PropsWithChildren<InitialProps>>;
  initialProps: InitialProps;
}

export type RouterProps = StackNavigatorProps & NavigationContainerProps;

interface StackNavigatorProps {
  /**
   * @name navigationContainerRef
   * @description
   * `@react-navigation/native`의 `NavigationContainerRef`를 외부에서 만들어 전달할 수 있어요. 이렇게 하면 외부에서 라우터를 제어할 수 있어요.
   */
  navigationContainerRef?: NavigationContainerRefWithCurrent<any>;
  /**
   * @name defaultScreenOption
   * @description
   * 화면에 대한 기본 옵션이에요. 공통적으로 화면에 적용할 옵션을 설정할 수 있어요. 예를 들어, `title`이나 `headerStyle` 등을 설정할 수 있어요.
   */
  defaultScreenOption?:
    | NativeStackNavigationOptions
    | ((props: { route: RouteProp<ParamListBase>; navigation: any }) => NativeStackNavigationOptions);
  /**
   * @name screenContainer
   * @description
   * 각 `Screen` 컴포넌트를 감싸는 컨테이너 컴포넌트예요.
   */
  screenContainer?: ComponentType<PropsWithChildren<any>>;
}

type NavigationContainerProps = Pick<
  ComponentProps<typeof NavigationContainer>,
  'ref' | 'documentTitle' | 'fallback' | 'onReady' | 'onUnhandledAction' | 'onStateChange'
>;

/**
 * @category Components
 * @kind function
 * @name Router
 * @description
 * React Native 환경에서 페이지 이동을 위한 라우터 컴포넌트예요.
 * `pages/*`의 파일 이름 규칙에 따라 화면에 맞는 경로를 자동으로 지정해줘요.
 * 이 컴포넌트를 사용하면 Next.js 의 파일 기반 라우팅과 같은 방식으로 화면을 관리할 수 있어요.
 *
 * @param {string} prefix 스킴이 실행될 때 사용할 prefix 예요. 예를 들어, `intoss://my-service/intro` 경로로 진입하려면 `intoss://my-service`를 prefix로 설정해야 해요.
 * @param {RequireContext} context 파일 기반 라우팅을 위한 화면들에 대한 정보를 담은 객체예요.
 * @param {NavigationContainerRefWithCurrent<any>} [navigationContainerRef] `@react-navigation/native`의 `NavigationContainerRef`를 외부에서 만들어 전달할 수 있어요. 이렇게 하면 외부에서 라우터를 제어할 수 있어요.
 * @param {NativeStackNavigationOptions | ((props: { route: RouteProp<ParamListBase>; navigation: any }) => NativeStackNavigationOptions)} [defaultScreenOption] 화면에 대한 기본 옵션이에요. 공통적으로 화면에 적용할 옵션을 설정할 수 있어요. 예를 들어, title 이나 headerStyle 등을 설정할 수 있어요.
 * @param {boolean} [canGoBack=true] 뒤로 갈 수 있는지 여부예요. 기본값은 `true`이며, `true` 로 설정되면 `@react-navigation/native` 의 뒤로 가기 제스처 또는 뒤로가기 버튼을 사용할 수 있어요.
 * @param {() => void} [onBack] 사용자가 뒤로가기 버튼을 누르거나 뒤로가기 제스처를 사용했을 때 호출되는 콜백 함수예요. 예를 들어, 사용자가 뒤로가기 버튼을 눌렀을 때 이 함수에서 로그를 남기도록 설정할 수 있어요.
 * @param {ComponentType<{ children: ReactNode }>} [container=Fragment] `@react-navigation/native`의 `Navigator`를 감싸는 컨테이너 컴포넌트예요.
 * @param {NavigationContainerProps} [navigationContainerProps] - `@react-navigation/native`의 `NavigationContainer`에 전달할 props를 설정할 수 있어요.
 *
 * @returns {ReactElement} - 라우터 컴포넌트를 반환해요.
 * @example
 * ```tsx
 * import { Router } from 'react-native-bedrock';
 * import { context } from '../require.context';
 *
 * function App() {
 *  return <Router context={context} prefix={'intoss://testbench'} />;
 * }
 * ```
 */
export function Router({
  // Internal props
  prefix,
  context,
  canGoBack = true,
  onBack,
  container: BedrockContainer = Fragment,
  initialProps,
  // Public props (NavigationContainer)
  navigationContainerRef,
  defaultScreenOption,
  screenContainer,
  // Public props (StackNavigator)
  ...navigationContainerProps
}: InternalRouterProps & RouterProps): ReactElement {
  const initialRouteName = useInitialRouteName(prefix);
  const { BedrockScreens, linkingOptions } = useBedrockRouterControls({ prefix, context, screenContainer });

  const ref = useMemo(() => navigationContainerRef ?? createNavigationContainerRef<any>(), [navigationContainerRef]);

  const headerLeft = useCallback(
    (backButtonProps: Omit<RouterBackButtonProps, 'navigationContainerRef'>) => (
      <RouterBackButton {...backButtonProps} onBack={onBack} canGoBack={canGoBack} navigationContainerRef={ref} />
    ),
    [onBack, canGoBack, ref]
  );

  const screenOptions = useCallback(
    (screenProps: any) => ({
      ...BASE_STACK_NAVIGATOR_STYLE,
      gestureEnabled: canGoBack,
      headerLeft,
      ...(typeof defaultScreenOption === 'function' ? defaultScreenOption(screenProps) : defaultScreenOption),
    }),
    [canGoBack, defaultScreenOption, headerLeft]
  );

  return (
    <NavigationContainer ref={ref} {...navigationContainerProps} linking={linkingOptions}>
      <CanGoBackGuard canGoBack={canGoBack} onBack={onBack}>
        <BedrockContainer {...initialProps}>
          <BedrockStack.Navigator initialRouteName={initialRouteName} screenOptions={screenOptions}>
            {BedrockScreens}
          </BedrockStack.Navigator>
        </BedrockContainer>
      </CanGoBackGuard>
    </NavigationContainer>
  );
}
