import type { $Subtract, $Tuple } from './helpers.js';
import type {
  ReactOptions,
  i18n,
  Resource,
  FlatNamespace,
  Namespace,
  TypeOptions,
  TFunction,
  KeyPrefix,
  KeyPrefixSelector,
  NsResource,
  ParseKeys,
  TOptions,
  InterpolationMap,
  TFunctionReturn,
  TOptionsBase,
} from 'i18next';
import * as React from 'react';
import {
  Trans,
  TransProps,
  TransSelectorProps,
  ErrorCode,
  ErrorArgs,
} from './TransWithoutContext.js';
export { initReactI18next } from './initReactI18next.js';

export function nodesToString(
  children: React.ReactNode,
  i18nOptions?: ReactOptions,
  i18n?: i18n,
  i18nKey?: string,
): string;

export const TransWithoutContext: typeof Trans;
export { Trans, TransProps, TransSelectorProps, ErrorArgs, ErrorCode };

/**
 * Content declaration for IcuTrans component
 * Describes React components as type + props blueprints that will be rendered
 */
export interface IcuTransContentDeclaration {
  type: string | React.ComponentType<any>;
  props?: {
    children?: IcuTransContentDeclaration | IcuTransContentDeclaration[];
    [key: string]: any;
  };
}

/**
 * Props for IcuTransWithoutContext component (no React context)
 */
export type IcuTransWithoutContextProps<
  Key extends ParseKeys<Ns, TOpt, KPrefix>,
  Ns extends Namespace = _DefaultNamespace,
  KPrefix = undefined,
  TContext extends string | undefined = undefined,
  TOpt extends TOptions & { context?: TContext } = { context: TContext },
> = {
  /** The i18n key to look up the translation */
  i18nKey: Key;
  /** The default translation in ICU format with numbered tags (e.g., "<0>Click here</0>") */
  defaultTranslation: string;
  /** Declaration tree describing React components and their props */
  content: IcuTransContentDeclaration[];
  /** Optional namespace(s) for the translation */
  // see TransProps.ns: keep single-namespace values assignable when `t` fixes Ns to an array
  ns?: Ns | (Ns extends readonly (infer S extends string)[] ? S : never);
  /**
   * Values for ICU variable interpolation. Under `parseInterpolation: 'icu'`
   * (or `'mf2'`) they are typed from the message.
   */
  values?: [TypeOptions['parseInterpolation']] extends ['icu'] | ['mf2']
    ? InterpolationMap<
        TFunctionReturn<
          Ns,
          KPrefix extends string ? `${KPrefix}${TypeOptions['keySeparator']}${Key & string}` : Key,
          TOpt
        >
      >
    : Record<string, any>;
  /** i18next instance. If not provided, uses global instance */
  i18n?: i18n;
  /** Custom translation function */
  t?: TFunction<Ns, KPrefix>;
};

/**
 * Props for IcuTrans component (with React context support)
 */
export type IcuTransProps<
  Key extends ParseKeys<Ns, TOpt, KPrefix>,
  Ns extends Namespace = _DefaultNamespace,
  KPrefix = undefined,
  TContext extends string | undefined = undefined,
  TOpt extends TOptions & { context?: TContext } = { context: TContext },
> = IcuTransWithoutContextProps<Key, Ns, KPrefix, TContext, TOpt>;

/**
 * IcuTrans component for rendering ICU MessageFormat translations
 * This is the context-aware version that works with I18nextProvider
 *
 * @example
 * ```tsx
 * // Type-safe usage with namespace
 * <IcuTrans<'welcome.message', 'common'>
 *   i18nKey="welcome.message"
 *   defaultTranslation="Welcome <0>friend</0>!"
 *   content={[{ type: 'strong', props: {} }]}
 * />
 * ```
 */
export interface IcuTransComponent {
  <
    const Key extends ParseKeys<Ns, TOpt, KPrefix>,
    Ns extends Namespace = _DefaultNamespace,
    KPrefix = undefined,
    TContext extends string | undefined = undefined,
    TOpt extends TOptions & { context?: TContext } = { context: TContext },
  >(
    props: IcuTransProps<Key, Ns, KPrefix, TContext, TOpt>,
  ): React.ReactElement;
}

/**
 * IcuTransWithoutContext component for rendering ICU MessageFormat translations
 * This version does not use React context and requires explicit i18n instance
 *
 * @example
 * ```tsx
 * // Type-safe usage with namespace
 * <IcuTransWithoutContext<'welcome.message', 'common'>
 *   i18nKey="welcome.message"
 *   defaultTranslation="Welcome <0>back</0>!"
 *   content={[{ type: 'strong', props: {} }]}
 *   i18n={i18nInstance}
 * />
 * ```
 */
export interface IcuTransWithoutContextComponent {
  <
    const Key extends ParseKeys<Ns, TOpt, KPrefix>,
    Ns extends Namespace = _DefaultNamespace,
    KPrefix = undefined,
    TContext extends string | undefined = undefined,
    TOpt extends TOptions & { context?: TContext } = { context: TContext },
  >(
    props: IcuTransWithoutContextProps<Key, Ns, KPrefix, TContext, TOpt>,
  ): React.ReactElement;
}

export const IcuTrans: IcuTransComponent;
export const IcuTransWithoutContext: IcuTransWithoutContextComponent;

export function setDefaults(options: ReactOptions): void;
export function getDefaults(): ReactOptions;
export function setI18n(instance: i18n): void;
export function getI18n(): i18n;
export function composeInitialProps(ForComponent: any): (ctx: unknown) => Promise<any>;
export function getInitialProps(): {
  initialI18nStore: {
    [ns: string]: {};
  };
  initialLanguage: string;
};

export interface ReportNamespaces {
  addUsedNamespaces(namespaces: Namespace): void;
  getUsedNamespaces(): string[];
}

declare module 'i18next' {
  // interface i18n {
  //   reportNamespaces?: ReportNamespaces;
  // }
  interface CustomInstanceExtensions {
    reportNamespaces?: ReportNamespaces;
  }

  // i18next 27 declares `ReactOptions` empty and leaves its shape to react-i18next. These are
  // exactly i18next 26's property types: TypeScript accepts the repeated declarations on i18next 26
  // and they fill in the empty interface on i18next 27.
  interface ReactOptions {
    /**
     * Set it to fallback to let passed namespaces to translated hoc act as fallbacks
     * @default 'default'
     */
    nsMode?: 'default' | 'fallback';
    /**
     * Set it to the default parent element created by the Trans component.
     * @default 'div'
     */
    defaultTransParent?: string;
    /**
     * Set which events trigger a re-render, can be set to false or string of events
     * @default 'languageChanged'
     */
    bindI18n?: string | false;
    /**
     * Set which events on store trigger a re-render, can be set to false or string of events
     * @default ''
     */
    bindI18nStore?: string | false;
    /**
     * Set fallback value for Trans components without children
     * @default undefined
     */
    transEmptyNodeValue?: string;
    /**
     * Set it to false if you do not want to use Suspense
     * @default true
     */
    useSuspense?: boolean;
    /**
     * Function to generate an i18nKey from the defaultValue (or Trans children)
     * when no key is provided.
     * By default, the defaultValue (Trans text) itself is used as the key.
     * If you want to require keys for all translations, supply a function
     * that always throws an error.
     * @default undefined
     */
    hashTransKey?(defaultValue: TOptionsBase['defaultValue']): TOptionsBase['defaultValue'];
    /**
     * Convert eg. <br/> found in translations to a react component of type br
     * @default true
     */
    transSupportBasicHtmlNodes?: boolean;
    /**
     * Which nodes not to convert in defaultValue generation in the Trans component.
     * @default ['br', 'strong', 'i', 'p']
     */
    transKeepBasicHtmlNodesFor?: readonly string[];
    /**
     * Wrap text nodes in a user-specified element.
     * @default ''
     */
    transWrapTextNodes?: string;
    /**
     * Default props to apply to all Trans components.
     * Component-level props will override these defaults.
     */
    transDefaultProps?: {
      tOptions?: TOptions;
      values?: object;
      shouldUnescape?: boolean;
      components?: readonly unknown[] | { readonly [tagName: string]: unknown }; // Use `unknown` (or `any`) to be permissive without importing React.
    };
    /**
     * Optional keyPrefix that will be automatically applied to returned t function in useTranslation for example.
     * Accepts a string or a selector function (e.g. `$ => $.deeply.nested`).
     * @default undefined
     */
    keyPrefix?: string | (($: any) => any);
    /**
     * Unescape function
     * by default it unescapes some basic html entities
     */
    unescape?(str: string): string;
  }
}

type ObjectOrNever = TypeOptions['allowObjectInHTMLChildren'] extends true
  ? Record<string, unknown>
  : never;

type ReactI18NextChildren = React.ReactNode | ObjectOrNever;

declare module 'react' {
  namespace JSX {
    interface IntrinsicAttributes {
      i18nIsDynamicList?: boolean;
    }
  }

  interface HTMLAttributes<T> {
    // Without allowObjectInHTMLChildren this is React's own children type, so it follows @types/react (#1928).
    // The union is inspired by the typings for React.ReactNode. We do this to fix "This JSX tag's 'children' prop
    // expects a single child of type 'ReactI18NextChildren', but multiple children were provided":
    // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/5a1e9f91ed0143adede394adb3f540e650455f71/types/react/index.d.ts#L268
    children?: TypeOptions['allowObjectInHTMLChildren'] extends true
      ? ReactI18NextChildren | Iterable<ReactI18NextChildren>
      : DOMAttributes<T>['children'];
  }
}

type _DefaultNamespace = TypeOptions['defaultNS'];

export function useSSR(initialI18nStore: Resource, initialLanguage: string): void;

// If the version is earlier than i18next v25.4.0, enableSelector does not exist in TypeOptions, so a conditional type is used to maintain type compatibility.
type _EnableSelector = TypeOptions extends { enableSelector: infer U } ? U : false;

export type UseTranslationOptions<KPrefix> = {
  i18n?: i18n;
  useSuspense?: boolean;
  keyPrefix?: KPrefix;
  bindI18n?: string | false;
  nsMode?: 'fallback' | 'default';
  lng?: string;
  // other of these options might also work: https://github.com/i18next/i18next/blob/master/index.d.ts#L127
};

export type UseTranslationResponse<Ns extends Namespace, KPrefix> = [
  t: TFunction<Ns, KPrefix>,
  i18n: i18n,
  ready: boolean,
] & {
  t: TFunction<Ns, KPrefix>;
  i18n: i18n;
  ready: boolean;
};

// Workaround to make code completion to work when suggesting namespaces.
// This is a typescript limitation when using generics with default values,
// it'll be addressed in this issue: https://github.com/microsoft/TypeScript/issues/52516
export type FallbackNs<Ns> = Ns extends undefined
  ? _DefaultNamespace
  : Ns extends Namespace
    ? Ns
    : _DefaultNamespace;

export const useTranslation: _EnableSelector extends true | 'optimize' | 'strict'
  ? UseTranslationSelector
  : UseTranslationLegacy;

interface UseTranslationLegacy {
  <
    const Ns extends FlatNamespace | $Tuple<FlatNamespace> | undefined = undefined,
    const KPrefix extends KeyPrefix<FallbackNs<Ns>> = undefined,
  >(
    ns?: Ns,
    options?: UseTranslationOptions<KPrefix>,
  ): UseTranslationResponse<FallbackNs<Ns>, KPrefix>;
}

interface UseTranslationSelector {
  // Overload: selector function as keyPrefix — provides type-safe key narrowing
  <
    const Ns extends FlatNamespace | $Tuple<FlatNamespace> | undefined,
    const KPrefix extends KeyPrefixSelector<FallbackNs<Ns>>,
  >(
    ns: Ns,
    options: UseTranslationOptions<KPrefix> & { keyPrefix: KPrefix },
  ): UseTranslationResponse<FallbackNs<Ns>, KPrefix>;

  // Overload: string or undefined keyPrefix (original behavior)
  <
    const Ns extends FlatNamespace | $Tuple<FlatNamespace> | undefined = undefined,
    const KPrefix = undefined,
  >(
    ns?: Ns,
    options?: UseTranslationOptions<KPrefix>,
  ): UseTranslationResponse<FallbackNs<Ns>, KPrefix>;
}

// Need to see usage to improve this
export function withSSR(): <Props>(WrappedComponent: React.ComponentType<Props>) => {
  ({
    initialI18nStore,
    initialLanguage,
    ...rest
  }: {
    initialI18nStore: Resource;
    initialLanguage: string;
  } & Props): React.FunctionComponentElement<Props>;
  getInitialProps: (ctx: unknown) => Promise<any>;
};

export interface WithTranslation<
  Ns extends FlatNamespace | $Tuple<FlatNamespace> | undefined = undefined,
  KPrefix extends KeyPrefix<FallbackNs<Ns>> = undefined,
> {
  t: TFunction<FallbackNs<Ns>, KPrefix>;
  i18n: i18n;
  tReady: boolean;
}

export interface WithTranslationProps {
  i18n?: i18n;
  useSuspense?: boolean;
}

export function withTranslation<
  Ns extends FlatNamespace | $Tuple<FlatNamespace> | undefined = undefined,
  KPrefix extends KeyPrefix<FallbackNs<Ns>> = undefined,
>(
  ns?: Ns,
  options?: {
    withRef?: boolean;
    keyPrefix?: KPrefix;
  },
): <
  C extends React.ComponentType<React.ComponentProps<any> & WithTranslationProps>,
  ResolvedProps = React.JSX.LibraryManagedAttributes<
    C,
    $Subtract<React.ComponentProps<C>, WithTranslationProps>
  >,
>(
  component: C,
) => React.ComponentType<Omit<ResolvedProps, keyof WithTranslation<Ns>> & WithTranslationProps>;

export interface I18nextProviderProps {
  children?: React.ReactNode;
  i18n: i18n;
  defaultNS?: string | string[];
}

export const I18nextProvider: React.FunctionComponent<I18nextProviderProps>;
export const I18nContext: React.Context<{ i18n: i18n }>;

export interface TranslationProps<
  Ns extends FlatNamespace | $Tuple<FlatNamespace> | undefined = undefined,
  KPrefix extends KeyPrefix<FallbackNs<Ns>> = undefined,
> {
  children: (
    t: TFunction<FallbackNs<Ns>, KPrefix>,
    options: {
      i18n: i18n;
      lng: string;
    },
    ready: boolean,
  ) => React.ReactNode;
  ns?: Ns;
  i18n?: i18n;
  useSuspense?: boolean;
  keyPrefix?: KPrefix;
  nsMode?: 'fallback' | 'default';
}

export function Translation<
  Ns extends FlatNamespace | $Tuple<FlatNamespace> | undefined = undefined,
  KPrefix extends KeyPrefix<FallbackNs<Ns>> = undefined,
>(props: TranslationProps<Ns, KPrefix>): any;
